{
"cells": [
{
"cell_type": "markdown",
"id": "76e3985c-11b2-4a28-9ae5-f6586c3dd4ed",
"metadata": {},
"source": [
"# Distillation with Llama 4 and Synthetic Data Kit\n",
"\n",
"*Copyright (c) Meta Platforms, Inc. and affiliates.\n",
"This software may be used and distributed according to the terms of the Llama Community License Agreement.*"
]
},
{
"cell_type": "markdown",
"id": "65ef02bc",
"metadata": {},
"source": [
""
]
},
{
"cell_type": "markdown",
"id": "3c0fffb5",
"metadata": {},
"source": [
"This notebook will walk you through [distilling](https://www.llama.com/docs/how-to-guides/distillation/) model knowledge from [Llama 4](https://www.llama.com/docs/model-cards-and-prompt-formats/llama4) into a smaller [Llama 3.2](https://www.llama.com/docs/model-cards-and-prompt-formats/llama3_2/) model using synthetic training data from [Synthetic Data Kit](https://github.com/meta-llama/synthetic-data-kit). \n",
"\n",
"### The goal\n",
"The goal of this notebook is to distill knowledge from a more powerful model (Llama 4 Scout) into a smaller, less powerful model (Llama 3.2 3B).\n",
"\n",
"Smaller models have several advantages when compared with larger models: they're faster to generate text, have lower time to first token, and cost less to host since they need less hardware. However, larger models tend to be generalists – that is, they have the ability to perform a wide variety of tasks well. On specific or specialized tasks, smaller models can be just as good as the generalist, larger models. Distillation allows you to take knowledge present in a larger model and transfer it to a smaller model with a minimal drop in quality for narrow tasks.\n",
"\n",
"### The data\n",
"This notebook uses air traffic control data to demonstrate tuning a model towards a specialized field. During distillation, we will fully generate pairs from scratch, because our generalist teacher model has a strong understanding of ATC phraseology. During evaluation, we will evaluate both synthetic pairs as well as actual ATC data.\n",
"\n",
"We will use the [ATCO2 corpus](https://github.com/idiap/atco2-corpus/tree/main) of air traffic data, an MIT-licensed dataset that contains audio, transcriptions, and additional contextual and metadata for each interaction. For this exercise we will only use the text transcripts, and will use the small (1h) sample dataset to demonstrate how only a small amount of data is actually necessary for fine-tuning the model.\n",
"\n",
"### Evaluation\n",
"To evaluate our model, we will use standard language evaluation metrics such as [perplexity](https://en.wikipedia.org/wiki/Perplexity) and accuracy. We will also use [BLEU](https://en.wikipedia.org/wiki/BLEU) (bilingual evaluation understudy) to measure similarity without requiring that the model matches exactly every word. While originally designed for machine translation, BLEU compares n-gram similarity, meaning that minor word order differences are not penalized."
]
},
{
"cell_type": "markdown",
"id": "1fa99e42-5556-4b46-ab10-a4bc80c9f578",
"metadata": {},
"source": [
"## Prerequisites\n",
"#### Hardware Requirements:\n",
"\n",
"- NVIDIA GPU with at least 80GB VRAM (H100, A100, or similar)\n",
" - 8x GPU to run Llama 4 Scout and create the dataset\n",
" - 1x GPU to distill and fine-tune the model\n",
"- 200GB+ disk space\n",
"- 64GB+ system RAM\n",
"\n",
"#### Software Requirements:\n",
"\n",
"- CUDA 12.x\n",
"- HuggingFace account and token\n",
"- Fast internet connection for downloading models\n"
]
},
{
"cell_type": "markdown",
"id": "f6c1aa12-d54f-4b3f-936f-57580b9cf9e2",
"metadata": {},
"source": [
"## Preparing your environment"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a525b411-35a9-4cd3-8e89-355a1e85014e",
"metadata": {},
"outputs": [],
"source": [
"# Install dependencies\n",
"# Some Ubuntu setups may require you to uninstall blinker if it's managed\n",
"# by the system package manager. If you see an error about blinker, try\n",
"# uninstalling it with `apt remove python3-blinker`.\n",
"!apt remove -y python3-blinker\n",
"!pip install unsloth_zoo unsloth==2025.8.9 transformers==4.55.4 nltk synthetic-data-kit -q --upgrade"
]
},
{
"cell_type": "markdown",
"id": "1d82a68b-495d-4e56-a854-e42a6e16727d",
"metadata": {},
"source": [
"## Generate the synthetic dataset\n",
"We will use the synthetic data kit to produce synthetic data to distill our model.\n",
"\n",
"First, set up the VLLM server. You will need to run this in a separate terminal window\n",
"since Jupyter doesn't support long running tasks/servers. Make sure to install vLLM with\n",
"`pip install vllm`\n",
"\n",
"```shell\n",
"HF_HOME=/workspace/huggingface_cache \\\n",
"HF_TOKEN=$HF_TOKEN \\\n",
"vllm serve meta-llama/Llama-4-Scout-17B-16E-Instruct \\\n",
" --port 8000 \\\n",
" --max-model-len 8192 \\\n",
" --gpu-memory-utilization 0.95 \\\n",
" --tensor-parallel-size 8\n",
"```\n",
"\n",
"Then check that the server is working properly."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "fbde635c-2f15-4efc-90a1-1efbbb6261a1",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Loading config from: /usr/local/lib/python3.10/dist-packages/synthetic_data_kit/config.yaml\n",
"Config has LLM provider set to: api-endpoint\n",
"Loading config from: /usr/local/lib/python3.10/dist-packages/synthetic_data_kit/config.yaml\n",
"Config has LLM provider set to: api-endpoint\n",
"Loading config from: config.yaml\n",
"Config has LLM provider set to: vllm\n",
"\u001b[1;34mEnvironment variable check:\u001b[0m\n",
"API_ENDPOINT_KEY: Not found\n",
"get_llm_provider returning: vllm\n",
"\u001b[?25l\u001b[32m vLLM server is running at \u001b[0m\u001b[4;94mhttp://localhost:8000/v1\u001b[0m\n",
"\u001b[2KAvailable models: \u001b[1m{\u001b[0m\u001b[32m'object'\u001b[0m: \u001b[32m'list'\u001b[0m, \u001b[32m'data'\u001b[0m: \u001b[1m[\u001b[0m\u001b[1m{\u001b[0m\u001b[32m'id'\u001b[0m: \n",
"\u001b[32m'meta-llama/Llama-4-Scout-17B-16E-Instruct'\u001b[0m, \u001b[32m'object'\u001b[0m: \u001b[32m'model'\u001b[0m, \u001b[32m'created'\u001b[0m: \n",
"\u001b[1;36m1752251909\u001b[0m, \u001b[32m'owned_by'\u001b[0m: \u001b[32m'vllm'\u001b[0m, \u001b[32m'root'\u001b[0m: \n",
"\u001b[32m'meta-llama/Llama-4-Scout-17B-16E-Instruct'\u001b[0m, \u001b[32m'parent'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'max_model_len'\u001b[0m: \n",
"\u001b[1;36m8192\u001b[0m, \u001b[32m'permission'\u001b[0m: \u001b[1m[\u001b[0m\u001b[1m{\u001b[0m\u001b[32m'id'\u001b[0m: \u001b[32m'modelperm-3c8eafb867bb4df4b4d65b45a899ae7a'\u001b[0m, \n",
"\u001b[32m'object'\u001b[0m: \u001b[32m'model_permission'\u001b[0m, \u001b[32m'created'\u001b[0m: \u001b[1;36m1752251909\u001b[0m, \u001b[32m'allow_create_engine'\u001b[0m: \n",
"\u001b[3;91mFalse\u001b[0m, \u001b[32m'allow_sampling'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'allow_logprobs'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'allow_search_indices'\u001b[0m: \n",
"\u001b[3;91mFalse\u001b[0m, \u001b[32m'allow_view'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'allow_fine_tuning'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'organization'\u001b[0m: \u001b[32m'*'\u001b[0m, \n",
"\u001b[32m'group'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'is_blocking'\u001b[0m: \u001b[3;91mFalse\u001b[0m\u001b[1m}\u001b[0m\u001b[1m]\u001b[0m\u001b[1m}\u001b[0m\u001b[1m]\u001b[0m\u001b[1m}\u001b[0m\n",
"\u001b[2K\u001b[32m⠋\u001b[0m Checking vLLM server at http://localhost:8000/v1...\n",
"\u001b[1A\u001b[2K"
]
}
],
"source": [
"# Test that the server is working\n",
"!synthetic-data-kit -c config.yaml system-check"
]
},
{
"cell_type": "markdown",
"id": "f31d66cc-aa8a-4a08-9422-0425e739fed5",
"metadata": {},
"source": [
"If the model is working correctly you should see `VLLM server is running`.\n",
"\n",
"Next, we will set up our configuration file for generating the data. We will use the QA task for our task, giving an example set of data and then asking the model to create call/response pairs similar to the examples. This is slightly different than an actual QA dataset but demonstrates different tasks can fit into the general framework that synthetic data kit provides."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "ba722599-4b0b-4dd9-b43b-fcc16699b0d5",
"metadata": {},
"outputs": [],
"source": [
"%%bash\n",
"\n",
"cat > config.yaml << 'EOF'\n",
"# generation: Content generation parameters\n",
"generation:\n",
" temperature: 0.6\n",
" top_p: 0.95\n",
" chunk_size: 4000\n",
" overlap: 200\n",
" max_tokens: 4096\n",
" num_pairs: 25\n",
" batch_size: 2\n",
"\n",
"llm:\n",
" # Provider selection: \"vllm\" or \"api-endpoint\"\n",
" provider: \"vllm\"\n",
"\n",
"# vllm: Configure VLLM server settings\n",
"vllm:\n",
" api_base: \"http://localhost:8000/v1\"\n",
" port: 8000\n",
" model: \"meta-llama/Llama-4-Scout-17B-16E-Instruct\"\n",
" max_retries: 3\n",
" retry_delay: 1.0\n",
"\n",
"# format: Export format parameters\n",
"format:\n",
" default: \"jsonl\"\n",
" include_metadata: true\n",
" pretty_json: true\n",
"\n",
"# prompts: LLM prompts for different tasks, we have\n",
"# to include all of them but we modify the QA generation\n",
"prompts:\n",
" qa_generation: |\n",
" Create {num_pairs} pairs of simulated ATC call/response transcripts.\n",
" \n",
" Rules:\n",
" 1. Use full words instead of numbers, i.e. seven thirty two not 732\n",
" 2. Include all phases of flight, first contact/handover, and ground/tower/TRACON\n",
" 3. Return JSON format only\n",
"\n",
" Here are some examples:\n",
"\n",
" {text}\n",
" \n",
" summary: |\n",
" Summarize this document in 3-5 sentences, focusing on the main topic and key concepts.\n",
"\n",
" qa_rating: |\n",
" You are a helpful JSON processor that rates question-answer pairs.\n",
" \n",
" Your task is to rate each pair on a scale from 1-10 and return valid JSON with added ratings.\n",
" \n",
" ONLY return a valid JSON array with the original pairs plus ratings. Do not include any explanations or text outside the JSON.\n",
" \n",
" Here are the pairs to rate:\n",
" \n",
" {pairs}\n",
"EOF"
]
},
{
"cell_type": "markdown",
"id": "ef65c213-3c65-45eb-ac31-118c9ae8e0b5",
"metadata": {},
"source": [
"We also create a dataset of examples to guide the model to producing better synthetic data. We provide 20 examples to produce 500+ training examples from synthetic data kit."
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "b4aa06f2-9081-4694-aaa6-0a3096fcf124",
"metadata": {},
"outputs": [],
"source": [
"%%bash\n",
"\n",
"cat > examples.txt << 'EOF'\n",
"JetBlue Eight Three Two, cleared to Boston via LENDO Seven, maintain five thousand, one two four point eight five, squawk four two one five\n",
"Cleared to Boston via LENDO Seven, maintain five thousand, one two four point eight five, squawk four two one five, JetBlue Eight Three Two\n",
"\n",
"Cessna Seven Four Romeo Tango, taxi to Runway Two Four via Alpha, hold short of Runway Two Four\n",
"Taxi Runway Two Four via Alpha, hold short Two Four, Seven Four Romeo Tango\n",
"\n",
"Southwest Two Twenty-Nine, Runway One Six Right, cleared for take-off, wind one niner zero at six\n",
"Cleared for take-off One Six Right, Southwest Two Twenty-Nine\n",
"\n",
"Delta Four Zero Six, contact Departure one two six point niner five\n",
"One two six point niner five, Delta Four Zero Six\n",
"\n",
"FedEx Four Eight Four Heavy, climb and maintain flight level three five zero\n",
"Climb and maintain flight level three five zero, FedEx Four Eight Four Heavy\n",
"\n",
"American One Eight, turn right heading zero niner zero, descend and maintain three thousand, expect ILS Runway Two Seven Left\n",
"Right heading zero niner zero, descend three thousand, expect ILS Two Seven Left, American One Eight\n",
"\n",
"American One Eight, cleared to land Runway Two Seven Left, wind two five zero at one four\n",
"Cleared to land Two Seven Left, American One Eight\n",
"\n",
"American One Eight, cross Runway Two Seven Right at Kilo, then taxi to Gate Alpha Four\n",
"Cross Two Seven Right at Kilo, to Alpha Four, American One Eight\n",
"\n",
"Emirates One Seven Four Heavy, cleared Dubai via the LONAM Two Foxtrot departure, initial climb five thousand feet, QNH one zero zero six, squawk five three five one\n",
"Cleared Dubai via LONAM Two Foxtrot, climb five thousand feet, QNH one zero zero six, squawk five three five one, Emirates One Seven Four Heavy\n",
"\n",
"Qatar Four One Six, push back and start approved, facing south\n",
"Push back and start approved, facing south, Qatar Four One Six\n",
"\n",
"Ryanair Eight Four, taxi to holding point Runway Two Four via Bravo and Delta, hold short\n",
"Holding short Two Four via Bravo and Delta, Ryanair Eight Four\n",
"\n",
"KLM Six Zero Three, line up and wait Runway Two Seven\n",
"Line up and wait Two Seven, KLM Six Zero Three\n",
"\n",
"British Airways Two Seven, cleared to enter oceanic airspace via Track Alpha, flight level three five zero, Mach decimal eight two\n",
"Cleared Track Alpha, flight level three five zero, Mach decimal eight two, British Airways Two Seven\n",
"\n",
"Air France Four Six, climb flight level three eight zero\n",
"Climb flight level three eight zero, Air France Four Six\n",
"\n",
"Singapore Three One, descend to altitude six thousand feet, QNH one zero zero nine, cleared ILS approach Runway Zero Four Right via AKOMA One\n",
"Descend six thousand feet, QNH one zero zero nine, cleared ILS Zero Four Right via AKOMA One, Singapore Three One\n",
"\n",
"Singapore Three One, vacate left via Alpha Seven, contact Ground one two one decimal seven five\n",
"Vacate left Alpha Seven, Ground one two one decimal seven five, Singapore Three One\n",
"\n",
"Speedbird Four Niner, cleared to enter controlled airspace, proceed direct MALBY, climb altitude four thousand feet, QNH one zero one five\n",
"Direct MALBY, climb four thousand feet, QNH one zero one five, Speedbird Four Niner\n",
"\n",
"Lufthansa Three Two, descend and maintain two thousand five hundred, cleared visual approach Runway One Six Left, QNH one zero one eight\n",
"Descend two thousand five hundred, cleared visual One Six Left, QNH one zero one eight, Lufthansa Three Two\n",
"\n",
"Emirates One Seven Four Heavy, taxi stand Alpha Seven via Mike and Echo, contact Apron on one two two decimal four\n",
"Taxi to stand Alpha Seven via Mike and Echo, one two two decimal four, Emirates One Seven Four Heavy\n",
"\n",
"Air Canada Eight Eight, Runway Two Four, cleared to land, wind two six zero degrees at eight knots\n",
"Cleared to land Runway Two Four, Air Canada Eight Eight\n",
"\n",
"EOF"
]
},
{
"cell_type": "markdown",
"id": "c6030c2c-1ded-46d4-b76c-d6d5972b51a3",
"metadata": {},
"source": [
"We create our synthetic dataset using synthetic-data-kit, running the command in batches in order to create enough examples. This is because weaker models have issues generating large numbers of examples."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f942fc5a-1f13-4c46-a6cc-f094c558de12",
"metadata": {},
"outputs": [],
"source": [
"%%bash\n",
"\n",
"NUM_BATCHES=10\n",
"\n",
"# Generate synthetic data using `create`\n",
"for i in $(seq 1 $NUM_BATCHES); do\n",
" synthetic-data-kit -c config.yaml create -n 50 examples.txt -o data/train/$i\n",
"done\n",
"\n",
"# Convert generated data to JSONL format using `save-as`\n",
"for i in $(seq 1 $NUM_BATCHES); do\n",
" synthetic-data-kit save-as data/train/$i/examples_qa_pairs.json -f jsonl -o data/train/$i/output.jsonl\n",
"done\n",
"\n",
"# Concatenate all output files into one with `cat`\n",
"cat $(for i in $(seq 1 $NUM_BATCHES); do echo -n \"data/train/$i/outpxut.jsonl \"; done) > data/train.jsonl\n",
"\n",
"# Eval doesn't need multiple runs\n",
"synthetic-data-kit -c config.yaml create -n 50 examples.txt -o data/eval\n",
"synthetic-data-kit save-as data/eval/examples_qa_pairs.json -f jsonl -o data/eval/output.jsonl"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "7d24c81d-9629-4863-bd72-41381978774d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"500\n",
"50\n"
]
}
],
"source": [
"!cat data/train.jsonl | wc -l\n",
"!cat data/eval/output.jsonl | wc -l"
]
},
{
"cell_type": "markdown",
"id": "da9671c7-da3e-48ad-98d2-3ad1689b1288",
"metadata": {},
"source": [
"## Preparing the eval dataset\n",
"Our human curated eval dataset contains text annotations in the form of XML files. We want to just produce transcripts of the conversation, and do not need to include any other metadata or audio."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7a4e1dd2-32dc-4e0b-bddd-b6b6eba4cbb5",
"metadata": {},
"outputs": [],
"source": [
"# Download the dataset\n",
"!mkdir Datasets && cd Datasets && wget https://www.replaywell.com/atco2/download/ATCO2-ASRdataset-v1_beta.tgz && tar xf ATCO2-ASRdataset-v1_beta.tgz >/dev/null 2>&1"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "303d6b4e-44c1-4154-828e-6e50fa613d1d",
"metadata": {},
"outputs": [],
"source": [
"import xml.etree.ElementTree as ET\n",
"import os\n",
"import glob\n",
"import re\n",
"\n",
"def parse_xml_files(directory_path: str):\n",
" \"\"\"\n",
" Parse all XML files in the specified directory and extract text entries.\n",
" \n",
" Args:\n",
" directory_path: Path to the directory containing XML files\n",
" \n",
" Returns:\n",
" A nested list where each item represents an XML file,\n",
" containing a list of text entries from that file\n",
" \"\"\"\n",
" xml_files = glob.glob(os.path.join(directory_path, \"*.xml\"))\n",
" results = []\n",
" \n",
" for xml_file in xml_files:\n",
" try:\n",
" tree = ET.parse(xml_file)\n",
" root = tree.getroot()\n",
" \n",
" file_texts = []\n",
" \n",
" for segment in root.findall('segment'):\n",
" text_element = segment.find('text')\n",
" if text_element is not None and text_element.text:\n",
" # Remove any part of speech details or metadata included in square brackets\n",
" raw_text = text_element.text\n",
" cleaned_text = re.sub(r\"\\[.*?\\]\", \"\", raw_text)\n",
" # Fix some weirdness with non breaking spaces\n",
" cleaned_text = cleaned_text.replace('\\xa0', '').replace('\\n', '')\n",
" file_texts.append(cleaned_text.strip())\n",
" \n",
" if file_texts and len(file_texts) >= 2:\n",
" results.append(file_texts)\n",
" \n",
" except ET.ParseError as e:\n",
" print(f\"Error parsing {xml_file}: {e}\")\n",
" except Exception as e:\n",
" print(f\"Error processing {xml_file}: {e}\")\n",
" \n",
" return results"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "f57c09c2-70e7-414b-a9ce-b6fc6419553d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Parsed 244\n"
]
}
],
"source": [
"parsed = parse_xml_files(\"Datasets/ATCO2-ASRdataset-v1_beta/DATA\")\n",
"print(f\"Parsed {len(parsed)}\")"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "192dc7c6-7e19-4958-8673-4999a3a02282",
"metadata": {},
"outputs": [],
"source": [
"# Llama 3 prompt template\n",
"def format_llama(instruction: str, first_message: str, reply: str):\n",
" instruction = f\"\"\"<|begin_of_text|><|start_header_id|>system<|end_header_id|>\n",
"{instruction}\n",
"<|eot_id|><|start_header_id|>user<|end_header_id|>\n",
"{first_message}\n",
"<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n",
"{reply}\"\"\"\n",
" return instruction.format(first_message, reply)\n",
"\n",
"# Format for our saved json format\n",
"def format_json(first_message: str, reply: str):\n",
" return {\n",
" \"instruction\": \"You are a helpful controller who responds to air traffic control messages.\",\n",
" \"input\": first_message,\n",
" \"output\": reply,\n",
" }\n",
"\n",
"# Converts the saved json format to llama format for ingestion\n",
"def json_to_llama(examples):\n",
" instructions = examples[\"instruction\"]\n",
" inputs = examples[\"input\"]\n",
" outputs = examples[\"output\"]\n",
" texts = []\n",
" for instruction, input, output in zip(instructions, inputs, outputs):\n",
" text = format_llama(instruction, input, output) + tokenizer.eos_token\n",
" texts.append(text)\n",
" return { \"text\" : texts, }"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "45e61b34-82d6-434c-a3ca-c2a6e9ed6603",
"metadata": {},
"outputs": [],
"source": [
"import json\n",
"\n",
"# Grab 100 of the examples for evaluation\n",
"messages_eval = []\n",
"for message in parsed[0:100]:\n",
" messages_eval.append(format_json(message[0], message[1]))\n",
"\n",
"# Save the dataset in our custom json format\n",
"os.makedirs(\"Datasets\", exist_ok=True)\n",
"with open(\"Datasets/dataset_eval.json\", 'w') as f:\n",
" json.dump(messages_eval, f)"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "64f08b0c-ff3a-44f4-982c-45528b71365b",
"metadata": {},
"outputs": [],
"source": [
"from datasets import Dataset\n",
"\n",
"def json_dataset(path: str):\n",
" \"\"\"Create a dataset from a JSON file, used for the ATC dataset.\"\"\"\n",
" with open(path, 'r') as f:\n",
" data = json.load(f)\n",
"\n",
" return Dataset.from_list(data)\n",
" \n",
"def jsonl_dataset(path: str):\n",
" \"\"\"Create a dataset from a JSONL file, used for synthetic data.\"\"\"\n",
" lines = []\n",
" with open(path, 'r') as f:\n",
" for line in f:\n",
" data = json.loads(line)\n",
" lines.append(format_json(data[\"atc\"], data[\"response\"]))\n",
"\n",
" return Dataset.from_list(lines)"
]
},
{
"cell_type": "markdown",
"id": "69fc4dfb-6f2e-4f71-bdb6-1bfa0193af99",
"metadata": {},
"source": [
"## Evaluating the baseline model\n",
"To evaluate the baseline results of the model we will use the HuggingFace transformers package and Unsloth for inference. We use two metrics here, **perplexity** and **BLEU**. Perplexity captures the \"surprise\" of the model, and applies on a per-token basis. BLEU is typically used for machine translation, but here is capturing if the response gets the gist of the correct answer, accounting for differences in word order."
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "314eca4b-9d67-4364-9f68-da9831cce117",
"metadata": {},
"outputs": [],
"source": [
"# This is where Model weights will be downloaded/used from\n",
"cache_dir = \"Models\""
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "87f62594-1ed3-4d15-9c95-3b52e25b5d03",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"🦥 Unsloth: Will patch your computer to enable 2x faster free finetuning.\n",
"🦥 Unsloth Zoo will now patch everything to make training faster!\n",
"INFO 07-11 18:16:50 [__init__.py:244] Automatically detected platform cuda.\n"
]
}
],
"source": [
"from unsloth import FastLanguageModel"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "83c2fea5-2576-49d6-8c6c-75353ecd68ec",
"metadata": {},
"outputs": [],
"source": [
"import torch\n",
"import torch.nn.functional as F\n",
"from nltk.translate.bleu_score import sentence_bleu, SmoothingFunction\n",
"\n",
"def compute_bleu(reference: str, candidate: str) -> float:\n",
" \"\"\"\n",
" Compute BLEU score between reference and candidate strings.\n",
"\n",
" Args:\n",
" reference: Ground-truth text.\n",
" candidate: Generated text to evaluate.\n",
"\n",
" Returns:\n",
" bleu_score: BLEU score (0 to 1).\n",
" \"\"\"\n",
" reference_tokens = reference.strip().split()\n",
" candidate_tokens = candidate.strip().split()\n",
"\n",
" smoothie = SmoothingFunction().method4\n",
" bleu_score = sentence_bleu(\n",
" [reference_tokens],\n",
" candidate_tokens,\n",
" smoothing_function=smoothie\n",
" )\n",
" return bleu_score\n",
"\n",
"def compute_loss(model, tokenizer, prompt: str, target: str) -> float:\n",
" \"\"\"\n",
" Compute loss for a target response given a prompt.\n",
"\n",
" Args:\n",
" model: Pretrained language model.\n",
" tokenizer: Tokenizer for the model.\n",
" prompt: Input text prompt.\n",
" target: Ground-truth text continuation.\n",
"\n",
" Returns:\n",
" loss: Computed loss value.\n",
" \"\"\"\n",
" # Tokenize separately to keep the prompt boundary\n",
" prompt_ids = tokenizer(prompt, return_tensors=\"pt\").input_ids.to(model.device)\n",
" target_ids = tokenizer(target, return_tensors=\"pt\").input_ids.to(model.device)\n",
"\n",
" # Create the combined input\n",
" input_ids = torch.cat((prompt_ids, target_ids), dim=1)\n",
"\n",
" # Labels are the complete prompt and target response\n",
" labels = input_ids.clone()\n",
"\n",
" # Set the tokens up to the end of the prompt to -100 to prevent loss computation there\n",
" # This is because we don't care how the model predicts the prompt, just how well it\n",
" # completes the text from the end of the prompt onwards\n",
" prompt_len = prompt_ids.shape[1]\n",
" labels[:, :prompt_len] = -100\n",
"\n",
" # Use the model to compute the loss\n",
" with torch.no_grad():\n",
" outputs = model(input_ids=input_ids, labels=labels)\n",
" loss = outputs.loss\n",
"\n",
" # Perplexity is the exponentiated negative log-likelihood\n",
" return loss.item()"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "02570852-cf81-400a-874c-7a39be88313a",
"metadata": {},
"outputs": [],
"source": [
"from trl import SFTTrainer\n",
"from transformers import TrainingArguments\n",
"import torch\n",
"\n",
"def generate(model, tokenizer, text: str, max_new_tokens: int = 100) -> str:\n",
" \"\"\"\n",
" Generate text from model given an input prompt.\n",
" \n",
" Args:\n",
" model: Pretrained language model.\n",
" tokenizer: Corresponding tokenizer.\n",
" text: Prompt text.\n",
" max_new_tokens: Number of tokens to generate.\n",
" \n",
" Returns:\n",
" str: Generated output text.\n",
" \"\"\"\n",
" inputs = tokenizer(text, return_tensors=\"pt\").to(model.device)\n",
" input_ids = inputs[\"input_ids\"]\n",
" \n",
" outputs = model.generate(\n",
" **inputs,\n",
" max_new_tokens=max_new_tokens,\n",
" temperature=0.7,\n",
" use_cache=True\n",
" )\n",
" \n",
" # Decode only the newly generated tokens (the part after the prompt)\n",
" return tokenizer.decode(outputs[0][input_ids.shape[1]:], skip_special_tokens=True)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9581816b-11cb-4990-a34b-31793ed31ca5",
"metadata": {},
"outputs": [],
"source": [
"from tqdm.notebook import tqdm\n",
"import numpy as np\n",
"\n",
"def evaluate(model, tokenizer, debug=False):\n",
" \"\"\"\n",
" This function loads the eval dataset and then loops over it to compute the\n",
" metrics. Enable `debug` to show the text generated and the ground truth.\n",
" \"\"\"\n",
" # Load the dataset\n",
" dataset = json_dataset(\"Datasets/dataset_eval.json\")\n",
" \n",
" # Compute Perplexity and BLEU scores\n",
" losses, bleus = [], []\n",
" \n",
" for convo in tqdm(dataset, desc=\"Evaluating\"):\n",
" prompt = format_llama(convo[\"instruction\"], convo[\"input\"], \"\")\n",
" output = generate(model, tokenizer, prompt)\n",
" ground_truth = convo[\"output\"]\n",
"\n",
" if debug:\n",
" print(\"Input:\\n\", prompt)\n",
" print(\"Output\\n\", output)\n",
" print(\"GT\\n\", ground_truth)\n",
" \n",
" loss = compute_loss(model, tokenizer, output, ground_truth)\n",
" bleu = compute_bleu(output, ground_truth)\n",
" \n",
" losses.append(loss)\n",
" bleus.append(bleu)\n",
" \n",
" # Report metrics\n",
" mean_loss = np.mean(loss)\n",
" mean_bleu = np.mean(bleus)\n",
" mean_ppl = np.exp(mean_loss)\n",
" \n",
" print(f\"\\n=== Evaluation Results ===\")\n",
" print(f\"Average Perplexity: {mean_ppl:.2f}\")\n",
" print(f\"Average BLEU Score: {mean_bleu:.2f}\")\n",
"\n",
" return mean_ppl, mean_bleu"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "d37c6d11-a55e-4a88-b890-b0fc178ed69c",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"==((====))== Unsloth 2025.7.3: Fast Llama patching. Transformers: 4.53.2. vLLM: 0.9.2.\n",
" \\\\ /| NVIDIA H100 80GB HBM3. Num GPUs = 1. Max memory: 79.209 GB. Platform: Linux.\n",
"O^O/ \\_/ \\ Torch: 2.7.0+cu126. CUDA: 9.0. CUDA Toolkit: 12.6. Triton: 3.3.0\n",
"\\ / Bfloat16 = TRUE. FA [Xformers = 0.0.30. FA2 = False]\n",
" \"-____-\" Free license: http://github.com/unslothai/unsloth\n",
"Unsloth: Fast downloading is enabled - ignore downloading bars which are red colored!\n"
]
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "426deee9742e4e5485233895cd175ea2",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"config.json: 0.00B [00:00, ?B/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "1903e5cafed64c019b3485c7bf2b47be",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"model.safetensors: 0%| | 0.00/2.35G [00:00, ?B/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "e9da1148aefb4941afc97499cfc91348",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"generation_config.json: 0%| | 0.00/234 [00:00, ?B/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "d0a8444e571a46038ff1a3806453c795",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"tokenizer_config.json: 0.00B [00:00, ?B/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "b2054429d44a4edcb81d9ad78049047d",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"special_tokens_map.json: 0%| | 0.00/454 [00:00, ?B/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "3e7dc17b283245bdabf56d1be19002b7",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"tokenizer.json: 0%| | 0.00/17.2M [00:00, ?B/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "1ef72f70c51847859b01cbdf79db91a9",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"chat_template.jinja: 0.00B [00:00, ?B/s]"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# Load base model and compute the base metrics\n",
"model, tokenizer = FastLanguageModel.from_pretrained(\n",
" model_name=\"unsloth/Llama-3.2-3B-Instruct\",\n",
" max_seq_length=2048,\n",
" cache_dir=cache_dir,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "9b0fc072-8466-40fe-8678-c65ad5498a77",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "c6581270b5de45e6ad5e9a5beff8c140",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Map: 0%| | 0/100 [00:00, ? examples/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "3c30c18109884448bca5367a6ff6a030",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Evaluating: 0%| | 0/100 [00:00, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"=== Evaluation Results ===\n",
"Average Perplexity: 597.31\n",
"Average BLEU Score: 0.04\n"
]
}
],
"source": [
"base_ppl, base_bleu = evaluate(model, tokenizer)"
]
},
{
"cell_type": "markdown",
"id": "7a5ba4f9-e3d0-4efe-841b-d109203c0bed",
"metadata": {},
"source": [
"## Fine-tuning the model"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "3f768c02-8a21-469d-9b8f-1cb616a4e451",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"🚀 Starting fine-tuning process...\n",
"==((====))== Unsloth 2025.7.3: Fast Llama patching. Transformers: 4.53.2. vLLM: 0.9.2.\n",
" \\\\ /| NVIDIA H100 80GB HBM3. Num GPUs = 1. Max memory: 79.209 GB. Platform: Linux.\n",
"O^O/ \\_/ \\ Torch: 2.7.0+cu126. CUDA: 9.0. CUDA Toolkit: 12.6. Triton: 3.3.0\n",
"\\ / Bfloat16 = TRUE. FA [Xformers = 0.0.30. FA2 = False]\n",
" \"-____-\" Free license: http://github.com/unslothai/unsloth\n",
"Unsloth: Fast downloading is enabled - ignore downloading bars which are red colored!\n"
]
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "89048f5c1c7146428ae31e49e4c0cc5d",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Map: 0%| | 0/500 [00:00, ? examples/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"Not an error, but Unsloth cannot patch MLP layers with our manual autograd engine since either LoRA adapters\n",
"are not enabled or a bias term (like in Qwen) is used.\n",
"Unsloth 2025.7.3 patched 28 layers with 28 QKV layers, 28 O layers and 0 MLP layers.\n"
]
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "af7ff3b17c7341e58143352c86d9a018",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Unsloth: Tokenizing [\"text\"]: 0%| | 0/500 [00:00, ? examples/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"🏋️ Training started...\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"==((====))== Unsloth - 2x faster free finetuning | Num GPUs used = 1\n",
" \\\\ /| Num examples = 500 | Num Epochs = 4 | Total steps = 250\n",
"O^O/ \\_/ \\ Batch size per device = 8 | Gradient accumulation steps = 1\n",
"\\ / Data Parallel GPUs = 1 | Total batch size (8 x 1 x 1) = 8\n",
" \"-____-\" Trainable parameters = 9,175,040 of 3,221,924,864 (0.28% trained)\n"
]
},
{
"data": {
"text/html": [
"\n",
"
| Step | \n", "Training Loss | \n", "
|---|---|
| 1 | \n", "4.762000 | \n", "
| 2 | \n", "4.686100 | \n", "
| 3 | \n", "4.880100 | \n", "
| 4 | \n", "4.702700 | \n", "
| 5 | \n", "4.964900 | \n", "
| 6 | \n", "4.541600 | \n", "
| 7 | \n", "4.337800 | \n", "
| 8 | \n", "4.433600 | \n", "
| 9 | \n", "4.554600 | \n", "
| 10 | \n", "4.621800 | \n", "
| 11 | \n", "4.455400 | \n", "
| 12 | \n", "4.431100 | \n", "
| 13 | \n", "4.350000 | \n", "
| 14 | \n", "4.214200 | \n", "
| 15 | \n", "3.840500 | \n", "
| 16 | \n", "4.140100 | \n", "
| 17 | \n", "4.391500 | \n", "
| 18 | \n", "3.875400 | \n", "
| 19 | \n", "4.048800 | \n", "
| 20 | \n", "3.957800 | \n", "
| 21 | \n", "3.801900 | \n", "
| 22 | \n", "3.897500 | \n", "
| 23 | \n", "4.079000 | \n", "
| 24 | \n", "3.890600 | \n", "
| 25 | \n", "3.748000 | \n", "
| 26 | \n", "3.964100 | \n", "
| 27 | \n", "3.799400 | \n", "
| 28 | \n", "3.737300 | \n", "
| 29 | \n", "3.767900 | \n", "
| 30 | \n", "3.581700 | \n", "
| 31 | \n", "3.740300 | \n", "
| 32 | \n", "3.673100 | \n", "
| 33 | \n", "3.786100 | \n", "
| 34 | \n", "3.637700 | \n", "
| 35 | \n", "3.529000 | \n", "
| 36 | \n", "3.500600 | \n", "
| 37 | \n", "3.431700 | \n", "
| 38 | \n", "3.717500 | \n", "
| 39 | \n", "3.484600 | \n", "
| 40 | \n", "3.530600 | \n", "
| 41 | \n", "3.299400 | \n", "
| 42 | \n", "3.246600 | \n", "
| 43 | \n", "3.221300 | \n", "
| 44 | \n", "3.216600 | \n", "
| 45 | \n", "3.400700 | \n", "
| 46 | \n", "3.295000 | \n", "
| 47 | \n", "3.328800 | \n", "
| 48 | \n", "3.212400 | \n", "
| 49 | \n", "3.186700 | \n", "
| 50 | \n", "3.111700 | \n", "
| 51 | \n", "3.135700 | \n", "
| 52 | \n", "3.061300 | \n", "
| 53 | \n", "3.129500 | \n", "
| 54 | \n", "2.812900 | \n", "
| 55 | \n", "3.027100 | \n", "
| 56 | \n", "2.946300 | \n", "
| 57 | \n", "2.958200 | \n", "
| 58 | \n", "2.732000 | \n", "
| 59 | \n", "2.803700 | \n", "
| 60 | \n", "2.888600 | \n", "
| 61 | \n", "2.803900 | \n", "
| 62 | \n", "2.687000 | \n", "
| 63 | \n", "2.918200 | \n", "
| 64 | \n", "2.666000 | \n", "
| 65 | \n", "2.898900 | \n", "
| 66 | \n", "2.530400 | \n", "
| 67 | \n", "2.655500 | \n", "
| 68 | \n", "2.520800 | \n", "
| 69 | \n", "2.613300 | \n", "
| 70 | \n", "2.581700 | \n", "
| 71 | \n", "2.527300 | \n", "
| 72 | \n", "2.625500 | \n", "
| 73 | \n", "2.444100 | \n", "
| 74 | \n", "2.388400 | \n", "
| 75 | \n", "2.464300 | \n", "
| 76 | \n", "2.569800 | \n", "
| 77 | \n", "2.422900 | \n", "
| 78 | \n", "2.323000 | \n", "
| 79 | \n", "2.240800 | \n", "
| 80 | \n", "2.399400 | \n", "
| 81 | \n", "2.173600 | \n", "
| 82 | \n", "2.413500 | \n", "
| 83 | \n", "2.152700 | \n", "
| 84 | \n", "2.108300 | \n", "
| 85 | \n", "2.072800 | \n", "
| 86 | \n", "2.102800 | \n", "
| 87 | \n", "2.032800 | \n", "
| 88 | \n", "2.071700 | \n", "
| 89 | \n", "2.120400 | \n", "
| 90 | \n", "2.062100 | \n", "
| 91 | \n", "2.100300 | \n", "
| 92 | \n", "2.098300 | \n", "
| 93 | \n", "1.833700 | \n", "
| 94 | \n", "1.849400 | \n", "
| 95 | \n", "1.876600 | \n", "
| 96 | \n", "1.950500 | \n", "
| 97 | \n", "1.743500 | \n", "
| 98 | \n", "1.921800 | \n", "
| 99 | \n", "1.850400 | \n", "
| 100 | \n", "1.943800 | \n", "
| 101 | \n", "1.799600 | \n", "
| 102 | \n", "1.829700 | \n", "
| 103 | \n", "1.723000 | \n", "
| 104 | \n", "1.851800 | \n", "
| 105 | \n", "1.768400 | \n", "
| 106 | \n", "1.820100 | \n", "
| 107 | \n", "1.785700 | \n", "
| 108 | \n", "1.708200 | \n", "
| 109 | \n", "1.731400 | \n", "
| 110 | \n", "1.659000 | \n", "
| 111 | \n", "1.579200 | \n", "
| 112 | \n", "1.616000 | \n", "
| 113 | \n", "1.578700 | \n", "
| 114 | \n", "1.805600 | \n", "
| 115 | \n", "1.627700 | \n", "
| 116 | \n", "1.551300 | \n", "
| 117 | \n", "1.486400 | \n", "
| 118 | \n", "1.509400 | \n", "
| 119 | \n", "1.468300 | \n", "
| 120 | \n", "1.492500 | \n", "
| 121 | \n", "1.523300 | \n", "
| 122 | \n", "1.486100 | \n", "
| 123 | \n", "1.417800 | \n", "
| 124 | \n", "1.560400 | \n", "
| 125 | \n", "1.564300 | \n", "
| 126 | \n", "1.411400 | \n", "
| 127 | \n", "1.370100 | \n", "
| 128 | \n", "1.469700 | \n", "
| 129 | \n", "1.287900 | \n", "
| 130 | \n", "1.350700 | \n", "
| 131 | \n", "1.394000 | \n", "
| 132 | \n", "1.502800 | \n", "
| 133 | \n", "1.333300 | \n", "
| 134 | \n", "1.352500 | \n", "
| 135 | \n", "1.335000 | \n", "
| 136 | \n", "1.324200 | \n", "
| 137 | \n", "1.407700 | \n", "
| 138 | \n", "1.359600 | \n", "
| 139 | \n", "1.305500 | \n", "
| 140 | \n", "1.170300 | \n", "
| 141 | \n", "1.315400 | \n", "
| 142 | \n", "1.458400 | \n", "
| 143 | \n", "1.265300 | \n", "
| 144 | \n", "1.197200 | \n", "
| 145 | \n", "1.494000 | \n", "
| 146 | \n", "1.410200 | \n", "
| 147 | \n", "1.256400 | \n", "
| 148 | \n", "1.372300 | \n", "
| 149 | \n", "1.445100 | \n", "
| 150 | \n", "1.341300 | \n", "
| 151 | \n", "1.226100 | \n", "
| 152 | \n", "1.437600 | \n", "
| 153 | \n", "1.241700 | \n", "
| 154 | \n", "1.257800 | \n", "
| 155 | \n", "1.440200 | \n", "
| 156 | \n", "1.268700 | \n", "
| 157 | \n", "1.378500 | \n", "
| 158 | \n", "1.270300 | \n", "
| 159 | \n", "1.258500 | \n", "
| 160 | \n", "1.372400 | \n", "
| 161 | \n", "1.240800 | \n", "
| 162 | \n", "1.133500 | \n", "
| 163 | \n", "1.394800 | \n", "
| 164 | \n", "1.188500 | \n", "
| 165 | \n", "1.184400 | \n", "
| 166 | \n", "1.266000 | \n", "
| 167 | \n", "1.457400 | \n", "
| 168 | \n", "1.314500 | \n", "
| 169 | \n", "1.251400 | \n", "
| 170 | \n", "1.383400 | \n", "
| 171 | \n", "1.183600 | \n", "
| 172 | \n", "1.211000 | \n", "
| 173 | \n", "1.225000 | \n", "
| 174 | \n", "1.204000 | \n", "
| 175 | \n", "1.256200 | \n", "
| 176 | \n", "1.253400 | \n", "
| 177 | \n", "1.223100 | \n", "
| 178 | \n", "1.180300 | \n", "
| 179 | \n", "1.135800 | \n", "
| 180 | \n", "1.187200 | \n", "
| 181 | \n", "1.231800 | \n", "
| 182 | \n", "1.144100 | \n", "
| 183 | \n", "1.262200 | \n", "
| 184 | \n", "1.140800 | \n", "
| 185 | \n", "1.266800 | \n", "
| 186 | \n", "0.986200 | \n", "
| 187 | \n", "1.313600 | \n", "
| 188 | \n", "1.104600 | \n", "
| 189 | \n", "1.229700 | \n", "
| 190 | \n", "1.147400 | \n", "
| 191 | \n", "1.135100 | \n", "
| 192 | \n", "1.285700 | \n", "
| 193 | \n", "1.224500 | \n", "
| 194 | \n", "1.145700 | \n", "
| 195 | \n", "1.263500 | \n", "
| 196 | \n", "1.137600 | \n", "
| 197 | \n", "1.259100 | \n", "
| 198 | \n", "1.126000 | \n", "
| 199 | \n", "1.156700 | \n", "
| 200 | \n", "1.153400 | \n", "
| 201 | \n", "1.174400 | \n", "
| 202 | \n", "1.107700 | \n", "
| 203 | \n", "1.199500 | \n", "
| 204 | \n", "1.265000 | \n", "
| 205 | \n", "1.268700 | \n", "
| 206 | \n", "1.104300 | \n", "
| 207 | \n", "1.157800 | \n", "
| 208 | \n", "1.187900 | \n", "
| 209 | \n", "1.155200 | \n", "
| 210 | \n", "1.165400 | \n", "
| 211 | \n", "1.097800 | \n", "
| 212 | \n", "1.162000 | \n", "
| 213 | \n", "1.080000 | \n", "
| 214 | \n", "1.142100 | \n", "
| 215 | \n", "1.091300 | \n", "
| 216 | \n", "1.062000 | \n", "
| 217 | \n", "1.119800 | \n", "
| 218 | \n", "1.088700 | \n", "
| 219 | \n", "1.103000 | \n", "
| 220 | \n", "1.161300 | \n", "
| 221 | \n", "1.214800 | \n", "
| 222 | \n", "1.140900 | \n", "
| 223 | \n", "1.129000 | \n", "
| 224 | \n", "1.189400 | \n", "
| 225 | \n", "1.185300 | \n", "
| 226 | \n", "1.146400 | \n", "
| 227 | \n", "1.077500 | \n", "
| 228 | \n", "1.247100 | \n", "
| 229 | \n", "1.231900 | \n", "
| 230 | \n", "1.093400 | \n", "
| 231 | \n", "1.140400 | \n", "
| 232 | \n", "1.214400 | \n", "
| 233 | \n", "1.236600 | \n", "
| 234 | \n", "1.187500 | \n", "
| 235 | \n", "1.050100 | \n", "
| 236 | \n", "1.288500 | \n", "
| 237 | \n", "1.114800 | \n", "
| 238 | \n", "1.173000 | \n", "
| 239 | \n", "1.178500 | \n", "
| 240 | \n", "1.220100 | \n", "
| 241 | \n", "1.211500 | \n", "
| 242 | \n", "1.148000 | \n", "
| 243 | \n", "1.240400 | \n", "
| 244 | \n", "1.106200 | \n", "
| 245 | \n", "1.237700 | \n", "
| 246 | \n", "1.134400 | \n", "
| 247 | \n", "1.116100 | \n", "
| 248 | \n", "1.268500 | \n", "
| 249 | \n", "1.129200 | \n", "
| 250 | \n", "1.107700 | \n", "
"
],
"text/plain": [
"