Concepts / Implementing Automated Evaluation: Scoring Model Responses

Implementing Automated Evaluation: Scoring Model Responses

The generation loop visits each test entry, builds a prompt, calls generate, decodes the output, and cleans it so that only the model's answer remains.

  • Programming

Why Collected Answers Matter

Once a model has been instruction-tuned, the next question is how well it follows instructions on examples it has not seen during training. A common approach is to take a held-out test set. Each entry in that set already contains an instruction, an optional input, and a reference output. The model is asked to answer every instruction, and those answers are stored alongside the original entries so they can be compared later.

This article follows that practical loop from start to finish: generating a response for each entry, writing it into the dataset, saving the result to a file, and confirming that the response landed where it should. Before you read on, think about what a single pass through the loop has to accomplish. The loop starts with an entry that holds an instruction, an input, and a reference output. It has to finish with a model answer attached to that same entry. Keep that goal in mind as you trace the steps.

One Pass Through the Loop

The loop reuses the same generate method that was used earlier in the chapter. The difference is that it now runs once for each entry in the test set instead of once for a single example. The loop is driven by enumerate over test_data, so each iteration has both an index i and the entry itself. A tqdm progress bar wraps the loop, which is why the run reports how many of the 110 entries have been processed.

For each entry, the code first builds a prompt from that entry using format_input. That prompt is converted into token IDs with text_to_token_ids and moved to the target device. Those IDs are passed to generate, which is told to produce at most 256 new tokens, to use the context length stored in BASE_CONFIG, and to stop when it reaches the end-of-sequence token whose ID is 50256. The output of generate is a sequence of token IDs that contains the prompt followed by the newly generated continuation.

Build promptformat_input(entry)Encode prompttext_to_token_ids, move todeviceCall generatemax 256 new tokens, stop atID 50256Decode outputtoken_ids_to_textClean responseslice, remove marker, stripStore model_responsewritten into entry iMore entries left?iterate over test_dataWrite JSON fileafter the loop endsprompt texttoken IDsprompt plus continuationdecoded stringresponse textnext indexyes, repeat bodyno, loop finished
What happens to a test entry, step by step, from building its prompt through calling generate, decoding, cleaning, and storing, and when the loop decides to repeat or stop?

Notice the shape of the loop. It visits each entry in order, performs the same sequence of steps for each one, and then moves on. The decision at the end of the body is what repeats the work until every entry is finished. The diagram above shows that order for a single entry.

Cleaning the Decoded Text

The decoded string is not yet the answer. The code converts the whole token sequence back into text with token_ids_to_text, so the decoded string still begins with the prompt. To isolate the answer, the code performs three operations. First, it slices off the first part of the string whose length equals the length of the prompt text. Second, it removes the Response marker wherever it appears in the remaining text. The marker is the line made of three hash signs, the word Response, and a colon. Third, it calls strip to trim surrounding whitespace. What remains is the response text alone.

Before

Instruction text and input text, then the Response marker, then the model's answer, with leading and trailing whitespace

Decoded string still contains the prompt, the Response marker, and surrounding whitespace

After

The model's answer only

Cleaned response ready to be stored under model_response

Prompt textModel answerResponse markerModel answerSurroundingwhitespace
How does the raw decoded text differ from the final stored response, and which parts are removed?

Storing the Response in Its Entry

The response is not returned from the loop or collected in a separate list. Instead, the code writes it directly into the entry it came from, using the index i to pick the right dictionary inside test_data. Because the dictionary is modified in place, the change is visible everywhere test_data is used afterward, including the file-writing step. Each cleaned response is stored under the key model_response, which keeps it paired with the original instruction, input, and output.

Worked example

Tracing one entry through a single pass

The loop is at index 3 of test_data. The entry holds an instruction, an input, and a reference output. Follow what happens to that entry during this pass.

  1. 1
    Read the entry

    Use the index i to access test_data at position 3. The loop body now has the dictionary for this entry.

  2. 2
    Build and encode the prompt

    format_input builds the prompt from the entry, text_to_token_ids converts it to token IDs, and the IDs are moved to the target device.

  3. 3
    Generate and decode

    generate returns the prompt followed by the new continuation. token_ids_to_text converts the full sequence back into a string that still starts with the prompt.

  4. 4
    Clean the string

    Slice off the prompt-length portion, remove the Response marker, and strip surrounding whitespace to leave only the answer.

  5. 5
    Store the answer

    Assign the cleaned text to the model_response key of the dictionary at index 3. No new list is created and nothing is returned.

Result

Entry 3 now has four keys: instruction, input, output, and model_response. The change lives in test_data itself, so the file-writing step will see it.

Writing the Dataset to Disk

The file is not written inside the loop. The write step comes after the last entry has been processed, so the complete dataset, with every model_response filled in, is serialized in one operation. After the loop finishes, the updated test_data is written to a file named instruction-data-with-response.json. The file is opened in write mode 'w', and json.dump serializes the list into that file using an indent of 4 spaces, which makes the saved structure easy to read.

python
Notes on the code
  • The file name is the output file for this run
  • Mode 'w' creates or overwrites the file
  • json.dump writes the whole list in one call
  • indent=4 makes the saved structure readable

Because the write happens once, after the loop, the file does not exist in a partial state during the generation work. The data is accumulated in memory in test_data as the loop runs, and the disk is touched a single time when everything is ready.

Reading the Saved JSON Structure

Each entry in the saved file keeps its instruction, input, and output, and gains the new model_response field. This makes the file a complete record of the test run rather than a bare list of answers. The structure is a list of entries, where each entry is a set of named fields. The diagram below shows one entry in that structure.

Saved JSON listinstruction-data-with-response.jsonEntryone dictionary per testiteminstructionoriginal fieldinputoriginal fieldoutputreference answermodel_responseadded by the loopcontainsfieldfieldfieldfield
How are the test entries, their fields, and the new model_response field nested within the saved file?

Checking One Entry After the Run

Once the run has finished, it is worth inspecting one entry directly rather than trusting that the loop worked. Look at a single entry from the updated test set. It should contain the original instruction, the input, the reference output, and a model_response field whose value is the model's answer. Seeing all four keys together confirms that the response was added to the correct dictionary and that the original fields were not disturbed.

Mistakes With the Output File

  • Common mistake

    Running the script again without saving the earlier results

    Why it fails

    Opening the file in 'w' mode overwrites earlier output.

    Do this instead

    Rename or copy the previous instruction-data-with-response.json before starting a new run.

  • Common mistake

    Expecting the file to contain responses while the loop is still running

    Why it fails

    The file is written once, after the last entry is processed. During the loop, responses exist only in test_data in memory.

    Do this instead

    Wait for the loop to finish before reading the saved file, and treat the file as the final record of the run.

  • Common mistake

    Assuming the decoded text is already the answer

    Why it fails

    generate returns the prompt followed by the continuation, so the decoded string still begins with the prompt.

    Do this instead

    Slice off the prompt-length portion, remove the Response marker, and strip whitespace before storing.

Predicting the Effect of a Second Run

What do you think happens?

You run the script to completion, then run it again with a different model. What happens to the file produced by the first run?

  • It is kept and the new results are appended to it

  • It is overwritten by the new run

  • It is merged entry by entry with the new results

Reveal answer
Answer:

It is overwritten by the new run.

The file is opened in 'w' mode, which overwrites earlier output. To keep the first run, rename or copy the file before starting the second run.

MEDIUM

Suppose the decoded text for one entry is the prompt, then the Response marker, then the answer, with a trailing newline. Describe in order the operations that turn this into the stored model_response, and state which of them depends on the length of the prompt text.

Hints
  • Start from the decode step, since the string still begins with the prompt.
  • The slice uses the length of the prompt text, not the length of the whole decoded string.
  • strip handles whitespace at both ends of the remaining text.

Key Takeaways

  • Each pass through the generation loop builds a prompt from one entry, calls generate, decodes the output, and cleans it so only the model's answer remains.
  • The cleaned response is stored under the key model_response inside the same dictionary as the original instruction, input, and output, by modifying test_data in place.
  • The full dataset is written once, after the loop, to instruction-data-with-response.json using json.dump with an indent of 4, and opening the file in 'w' mode overwrites earlier output.
  • Checking a single saved entry confirms that the response was attached to the correct dictionary and that the original fields were not changed.

Related concepts

More concepts starting with I