From 7a3ab1620a6b853f76fcdacdc35df308a255036e Mon Sep 17 00:00:00 2001 From: Anchen Date: Fri, 26 Jul 2024 04:01:17 +1000 Subject: [PATCH 001/188] support load model by custom get_model_classes (#899) * feature(mlx_lm): support load model by custom get classes * rename the param --- llms/mlx_lm/utils.py | 10 ++++-- llms/tests/test_utils_load_model.py | 50 +++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 llms/tests/test_utils_load_model.py diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index 229ee238..cffa2a89 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -9,7 +9,7 @@ import shutil import time from pathlib import Path from textwrap import dedent -from typing import Any, Callable, Dict, Generator, Optional, Tuple, Union +from typing import Any, Callable, Dict, Generator, Optional, Tuple, Type, Union import mlx.core as mx import mlx.nn as nn @@ -355,6 +355,7 @@ def load_model( model_path: Path, lazy: bool = False, model_config: dict = {}, + get_model_classes: Callable[[dict], Tuple[Type[nn.Module], Type]] = _get_classes, ) -> nn.Module: """ Load and initialize the model from a given path. @@ -364,8 +365,11 @@ def load_model( lazy (bool): If False eval the model parameters to make sure they are loaded in memory before returning, otherwise they will be loaded when needed. Default: ``False`` - model_config(dict, optional): Configuration parameters for the model. + model_config (dict, optional): Configuration parameters for the model. Defaults to an empty dictionary. + get_model_classes (Callable[[dict], Tuple[Type[nn.Module], Type]], optional): + A function that returns the model class and model args class given a config. + Defaults to the _get_classes function. Returns: nn.Module: The loaded and initialized model. @@ -392,7 +396,7 @@ def load_model( for wf in weight_files: weights.update(mx.load(wf)) - model_class, model_args_class = _get_classes(config=config) + model_class, model_args_class = get_model_classes(config=config) model_args = model_args_class.from_dict(config) model = model_class(model_args) diff --git a/llms/tests/test_utils_load_model.py b/llms/tests/test_utils_load_model.py new file mode 100644 index 00000000..73ee1352 --- /dev/null +++ b/llms/tests/test_utils_load_model.py @@ -0,0 +1,50 @@ +import unittest +from pathlib import Path + +import mlx.nn as nn +from mlx_lm.models.qwen2 import Model as Qwen2Model +from mlx_lm.utils import get_model_path, load_model + +HF_MODEL_PATH = "mlx-community/Qwen1.5-0.5B-Chat-4bit" + + +class TestLoadModelCustomGetClasses(unittest.TestCase): + + def test_load_model_with_custom_get_classes(self): + class CustomQwenModel(nn.Module): + def __init__(self, args): + super().__init__() + self.config = args + self.custom_attribute = "This is a custom model" + + def load_weights(self, weights): + self.qwenWeights = weights + + class CustomQwenConfig: + @classmethod + def from_dict(cls, config): + instance = cls() + for k, v in config.items(): + setattr(instance, k, v) + return instance + + def custom_get_classes(config): + return CustomQwenModel, CustomQwenConfig + + model_path = get_model_path(HF_MODEL_PATH) + model = load_model(model_path, get_model_classes=custom_get_classes) + + self.assertIsInstance(model, CustomQwenModel) + self.assertTrue(hasattr(model, "custom_attribute")) + self.assertEqual(model.custom_attribute, "This is a custom model") + self.assertTrue(hasattr(model, "qwenWeights")) + + def test_load_model_with_default_get_classes(self): + model_path = get_model_path(HF_MODEL_PATH) + model = load_model(model_path) + + self.assertIsInstance(model, Qwen2Model) + + +if __name__ == "__main__": + unittest.main() From 46da74fea2404eb987e8070d84f5745a124dbbc8 Mon Sep 17 00:00:00 2001 From: otriscon <165947759+otriscon@users.noreply.github.com> Date: Thu, 25 Jul 2024 19:45:22 -0400 Subject: [PATCH 002/188] Unify attention mask in LLMs (#911) * Unify attention mask creation in LLMs. Currently, each model implementation in `mlx-examples/llms/models` has ad-hoc code to create a mask for the attention mechanism. This usually takes the form: ``` mask = None if h.shape[1] > 1: mask = nn.MultiHeadAttention.create_additive_causal_mask(h.shape[1]) mask = mask.astype(h.dtype) ``` This correctly creates a mask only if the input consists of more than one token. But this code assumes the multi-token input is at the beginning of inference. If, for example, we are evaluating multiple tokens because of speculative decoding or prompt cache reuse, this mask will not have the correct shape and and will cause the raising of an exception in the attention computation. Some of the models correctly implement the mask creation with code like this: ``` mask = None if h.shape[1] > 1: mask = create_additive_causal_mask( h.shape[1], cache[0].offset if cache is not None else 0 ) mask = mask.astype(h.dtype) ``` This commit unifies the attention mask creation for all models with a new function `create_attention_mask`, reducing code duplication and helping all models support inference performance enhancements like those mentioned above. * Allow batches in LLM key-value cache The current implementation of the LLM key-value cache assumes that the input batch is of size 1. Input batching (evaluating multiple alterative inputs at the same time) can be a valuable tool for speculative sampling and other techniques. This change removes the hard-coded batch size from the code that resizes the key-value cache. * Simplify causal mask creation Use the same codepath regardless of whether there's an offset or not. Addresses [this comment](https://github.com/ml-explore/mlx-examples/pull/911#discussion_r1691459717). * Use old-style type annotation to avoid linter error --- llms/mlx_lm/models/base.py | 35 +++++++++++++++++++++++-------- llms/mlx_lm/models/cohere.py | 7 ++----- llms/mlx_lm/models/dbrx.py | 8 ++----- llms/mlx_lm/models/deepseek_v2.py | 8 ++----- llms/mlx_lm/models/gemma.py | 7 ++----- llms/mlx_lm/models/gemma2.py | 7 ++----- llms/mlx_lm/models/gpt2.py | 7 ++----- llms/mlx_lm/models/gpt_bigcode.py | 7 ++----- llms/mlx_lm/models/gpt_neox.py | 9 ++------ llms/mlx_lm/models/internlm2.py | 7 ++----- llms/mlx_lm/models/llama.py | 9 ++------ llms/mlx_lm/models/minicpm.py | 7 ++----- llms/mlx_lm/models/mixtral.py | 8 ++----- llms/mlx_lm/models/olmo.py | 7 ++----- llms/mlx_lm/models/openelm.py | 7 ++----- llms/mlx_lm/models/phi.py | 10 ++++----- llms/mlx_lm/models/phi3.py | 7 ++----- llms/mlx_lm/models/phi3small.py | 7 ++----- llms/mlx_lm/models/phixtral.py | 6 ++---- llms/mlx_lm/models/plamo.py | 7 ++----- llms/mlx_lm/models/qwen.py | 8 ++----- llms/mlx_lm/models/qwen2.py | 7 ++----- llms/mlx_lm/models/qwen2_moe.py | 7 ++----- llms/mlx_lm/models/stablelm.py | 8 ++----- llms/mlx_lm/models/starcoder2.py | 7 ++----- 25 files changed, 76 insertions(+), 138 deletions(-) diff --git a/llms/mlx_lm/models/base.py b/llms/mlx_lm/models/base.py index 8c3ecc78..3fe276d2 100644 --- a/llms/mlx_lm/models/base.py +++ b/llms/mlx_lm/models/base.py @@ -1,14 +1,9 @@ import inspect from dataclasses import dataclass +from typing import List, Optional import mlx.core as mx - - -def create_additive_causal_mask(N: int, offset: int = 0): - rinds = mx.arange(offset + N) - linds = mx.arange(offset, offset + N) if offset else rinds - mask = linds[:, None] < rinds[None] - return mask * -1e9 +import mlx.nn as nn class KVCache: @@ -29,9 +24,10 @@ class KVCache: def update_and_fetch(self, keys, values): prev = self.offset if self.keys is None or (prev + keys.shape[2]) > self.keys.shape[2]: + B = keys.shape[0] n_steps = (self.step + keys.shape[2] - 1) // self.step - k_shape = (1, self.n_kv_heads, n_steps * self.step, self.k_head_dim) - v_shape = (1, self.n_kv_heads, n_steps * self.step, self.v_head_dim) + k_shape = (B, self.n_kv_heads, n_steps * self.step, self.k_head_dim) + v_shape = (B, self.n_kv_heads, n_steps * self.step, self.v_head_dim) new_k = mx.zeros(k_shape, keys.dtype) new_v = mx.zeros(v_shape, values.dtype) if self.keys is not None: @@ -60,3 +56,24 @@ class BaseModelArgs: if k in inspect.signature(cls).parameters } ) + + +def create_additive_causal_mask(N: int, offset: int = 0): + rinds = mx.arange(offset + N) + linds = mx.arange(offset, offset + N) if offset else rinds + mask = linds[:, None] < rinds[None] + return mask * -1e9 + + +def create_attention_mask(h: mx.array, cache: Optional[List[KVCache]] = None): + T = h.shape[1] + if T > 1: + # Input consists of multiple tokens, create a causal mask so that prior + # tokens do not give attention to later tokens. If a cache is in place + # (because e.g. prompt reuse), offset the mask accordingly. + offset = cache[0].offset if cache is not None and cache[0] is not None else 0 + mask = create_additive_causal_mask(T, offset) + mask = mask.astype(h.dtype) + else: + mask = None + return mask diff --git a/llms/mlx_lm/models/cohere.py b/llms/mlx_lm/models/cohere.py index 621a85a2..7dc2b9bf 100644 --- a/llms/mlx_lm/models/cohere.py +++ b/llms/mlx_lm/models/cohere.py @@ -4,7 +4,7 @@ from typing import Optional, Tuple import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs +from .base import BaseModelArgs, create_attention_mask @dataclass @@ -157,10 +157,7 @@ class CohereModel(nn.Module): ): h = self.embed_tokens(inputs) - mask = None - if h.shape[1] > 1: - mask = nn.MultiHeadAttention.create_additive_causal_mask(h.shape[1]) - mask = mask.astype(h.dtype) + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) diff --git a/llms/mlx_lm/models/dbrx.py b/llms/mlx_lm/models/dbrx.py index dc310ca4..7a2a7a7d 100644 --- a/llms/mlx_lm/models/dbrx.py +++ b/llms/mlx_lm/models/dbrx.py @@ -5,7 +5,7 @@ import mlx.core as mx import mlx.nn as nn import numpy as np -from .base import BaseModelArgs +from .base import BaseModelArgs, create_attention_mask @dataclass @@ -199,11 +199,7 @@ class DBRX(nn.Module): ): h = self.wte(inputs) - mask = None - T = h.shape[1] - if T > 1: - mask = nn.MultiHeadAttention.create_additive_causal_mask(T) - mask = mask.astype(h.dtype) + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.blocks) diff --git a/llms/mlx_lm/models/deepseek_v2.py b/llms/mlx_lm/models/deepseek_v2.py index 308b94ba..bd743e53 100644 --- a/llms/mlx_lm/models/deepseek_v2.py +++ b/llms/mlx_lm/models/deepseek_v2.py @@ -5,7 +5,7 @@ from typing import Dict, Optional, Tuple import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, KVCache +from .base import BaseModelArgs, KVCache, create_attention_mask from .switch_layers import SwitchGLU @@ -408,11 +408,7 @@ class DeepseekV2Model(nn.Module): cache: Optional[KVCache] = None, ) -> mx.array: h = self.embed_tokens(x) - mask = None - T = h.shape[1] - if T > 1: - mask = nn.MultiHeadAttention.create_additive_causal_mask(T) - mask = mask.astype(h.dtype) + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) diff --git a/llms/mlx_lm/models/gemma.py b/llms/mlx_lm/models/gemma.py index e48f1909..323ebaa6 100644 --- a/llms/mlx_lm/models/gemma.py +++ b/llms/mlx_lm/models/gemma.py @@ -4,7 +4,7 @@ from typing import Optional, Tuple import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs +from .base import BaseModelArgs, create_attention_mask @dataclass @@ -141,10 +141,7 @@ class GemmaModel(nn.Module): h = self.embed_tokens(inputs) h = h * (self.args.hidden_size**0.5) - mask = None - if h.shape[1] > 1: - mask = nn.MultiHeadAttention.create_additive_causal_mask(h.shape[1]) - mask = mask.astype(h.dtype) + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) diff --git a/llms/mlx_lm/models/gemma2.py b/llms/mlx_lm/models/gemma2.py index 1ab403da..d4bd8a5d 100644 --- a/llms/mlx_lm/models/gemma2.py +++ b/llms/mlx_lm/models/gemma2.py @@ -4,7 +4,7 @@ from typing import Optional, Tuple import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs +from .base import BaseModelArgs, create_attention_mask @dataclass @@ -165,10 +165,7 @@ class GemmaModel(nn.Module): h = self.embed_tokens(inputs) h = h * (self.args.hidden_size**0.5) - mask = None - if h.shape[1] > 1: - mask = nn.MultiHeadAttention.create_additive_causal_mask(h.shape[1]) - mask = mask.astype(h.dtype) + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) diff --git a/llms/mlx_lm/models/gpt2.py b/llms/mlx_lm/models/gpt2.py index ece7b6ec..81f71cac 100644 --- a/llms/mlx_lm/models/gpt2.py +++ b/llms/mlx_lm/models/gpt2.py @@ -5,7 +5,7 @@ import mlx.core as mx import mlx.nn as nn import numpy as np -from .base import BaseModelArgs, create_additive_causal_mask +from .base import BaseModelArgs, create_attention_mask @dataclass @@ -136,10 +136,7 @@ class GPT2Model(nn.Module): position_ids = mx.array(np.arange(L)) hidden_states += self.wpe(position_ids) - mask = create_additive_causal_mask( - hidden_states.shape[1], cache[0].offset if cache is not None else 0 - ) - mask = mask.astype(hidden_states.dtype) + mask = create_attention_mask(hidden_states, cache) if cache is None: cache = [None] * len(self.h) diff --git a/llms/mlx_lm/models/gpt_bigcode.py b/llms/mlx_lm/models/gpt_bigcode.py index 20af3d0b..a5336203 100644 --- a/llms/mlx_lm/models/gpt_bigcode.py +++ b/llms/mlx_lm/models/gpt_bigcode.py @@ -5,7 +5,7 @@ import mlx.core as mx import mlx.nn as nn import numpy as np -from .base import BaseModelArgs, create_additive_causal_mask +from .base import BaseModelArgs, create_attention_mask @dataclass @@ -147,10 +147,7 @@ class GPTBigCodeModel(nn.Module): position_ids = mx.array(np.arange(L)) hidden_states += self.wpe(position_ids) - mask = create_additive_causal_mask( - hidden_states.shape[1], cache[0].offset if cache is not None else 0 - ) - mask = mask.astype(hidden_states.dtype) + mask = create_attention_mask(hidden_states, cache) if cache is None: cache = [None] * len(self.h) diff --git a/llms/mlx_lm/models/gpt_neox.py b/llms/mlx_lm/models/gpt_neox.py index 9549f322..1d2f74b7 100644 --- a/llms/mlx_lm/models/gpt_neox.py +++ b/llms/mlx_lm/models/gpt_neox.py @@ -5,7 +5,7 @@ import mlx.core as mx import mlx.nn as nn import numpy as np -from .base import BaseModelArgs, create_additive_causal_mask +from .base import BaseModelArgs, create_attention_mask # Based on the transformers implementation at: # https://github.com/huggingface/transformers/blob/main/src/transformers/models/gpt_neox/modeling_gpt_neox.py @@ -150,12 +150,7 @@ class GPTNeoXModel(nn.Module): hidden_states = self.embed_in(inputs) - mask = None - if hidden_states.shape[1] > 1: - mask = create_additive_causal_mask( - hidden_states.shape[1], cache[0].offset if cache is not None else 0 - ) - mask = mask.astype(hidden_states.dtype) + mask = create_attention_mask(hidden_states, cache) if cache is None: cache = [None] * len(self.h) diff --git a/llms/mlx_lm/models/internlm2.py b/llms/mlx_lm/models/internlm2.py index fc1cfc03..2ee2af2d 100644 --- a/llms/mlx_lm/models/internlm2.py +++ b/llms/mlx_lm/models/internlm2.py @@ -4,7 +4,7 @@ from typing import Dict, Optional, Tuple, Union import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs +from .base import BaseModelArgs, create_attention_mask @dataclass @@ -195,10 +195,7 @@ class InternLM2Model(nn.Module): ): h = self.tok_embeddings(inputs) - mask = None - if h.shape[1] > 1: - mask = nn.MultiHeadAttention.create_additive_causal_mask(h.shape[1]) - mask = mask.astype(h.dtype) + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) diff --git a/llms/mlx_lm/models/llama.py b/llms/mlx_lm/models/llama.py index a697e2b7..2f323245 100644 --- a/llms/mlx_lm/models/llama.py +++ b/llms/mlx_lm/models/llama.py @@ -4,7 +4,7 @@ from typing import Dict, Optional, Tuple, Union import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, KVCache, create_additive_causal_mask +from .base import BaseModelArgs, KVCache, create_attention_mask @dataclass @@ -271,12 +271,7 @@ class LlamaModel(nn.Module): ): h = self.embed_tokens(inputs) - mask = None - if h.shape[1] > 1: - mask = create_additive_causal_mask( - h.shape[1], cache[0].offset if cache is not None else 0 - ) - mask = mask.astype(h.dtype) + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) diff --git a/llms/mlx_lm/models/minicpm.py b/llms/mlx_lm/models/minicpm.py index dbfe4186..a3d01cbb 100644 --- a/llms/mlx_lm/models/minicpm.py +++ b/llms/mlx_lm/models/minicpm.py @@ -5,7 +5,7 @@ import mlx.core as mx import mlx.nn as nn import numpy as np -from .base import BaseModelArgs +from .base import BaseModelArgs, create_attention_mask @dataclass @@ -160,10 +160,7 @@ class MiniCPMModel(nn.Module): ): h = self.embed_tokens(inputs) * self.args.scale_emb - mask = None - if h.shape[1] > 1: - mask = nn.MultiHeadAttention.create_additive_causal_mask(h.shape[1]) - mask = mask.astype(h.dtype) + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) diff --git a/llms/mlx_lm/models/mixtral.py b/llms/mlx_lm/models/mixtral.py index 7d1b10ac..c7d8c5c5 100644 --- a/llms/mlx_lm/models/mixtral.py +++ b/llms/mlx_lm/models/mixtral.py @@ -5,7 +5,7 @@ from typing import Dict, Optional, Tuple, Union import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs +from .base import BaseModelArgs, create_attention_mask from .switch_layers import SwitchGLU @@ -164,11 +164,7 @@ class MixtralModel(nn.Module): ): h = self.embed_tokens(inputs) - mask = None - T = h.shape[1] - if T > 1: - mask = nn.MultiHeadAttention.create_additive_causal_mask(T) - mask = mask.astype(h.dtype) + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) diff --git a/llms/mlx_lm/models/olmo.py b/llms/mlx_lm/models/olmo.py index 120ea9b9..8a28ad74 100644 --- a/llms/mlx_lm/models/olmo.py +++ b/llms/mlx_lm/models/olmo.py @@ -5,7 +5,7 @@ from typing import Optional, Tuple import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs +from .base import BaseModelArgs, create_attention_mask try: import hf_olmo @@ -126,10 +126,7 @@ class Transformer(nn.Module): ): h = self.wte(inputs) - mask = None - if h.shape[1] > 1: - mask = nn.MultiHeadAttention.create_additive_causal_mask(h.shape[1]) - mask = mask.astype(h.dtype) + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.blocks) diff --git a/llms/mlx_lm/models/openelm.py b/llms/mlx_lm/models/openelm.py index 3fbdc58c..3f0d2605 100644 --- a/llms/mlx_lm/models/openelm.py +++ b/llms/mlx_lm/models/openelm.py @@ -4,7 +4,7 @@ from typing import Dict, List, Optional, Tuple, Union import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs +from .base import BaseModelArgs, create_attention_mask @dataclass @@ -180,10 +180,7 @@ class OpenELMModel(nn.Module): ): h = self.token_embeddings(inputs) - mask = None - if h.shape[1] > 1: - mask = nn.MultiHeadAttention.create_additive_causal_mask(h.shape[1]) - mask = mask.astype(h.dtype) + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) diff --git a/llms/mlx_lm/models/phi.py b/llms/mlx_lm/models/phi.py index 8feaa23a..520ac1ad 100644 --- a/llms/mlx_lm/models/phi.py +++ b/llms/mlx_lm/models/phi.py @@ -5,7 +5,7 @@ from typing import Tuple import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs +from .base import BaseModelArgs, create_attention_mask @dataclass @@ -138,14 +138,12 @@ class PhiModel(nn.Module): def __call__(self, x, cache): x = self.embed_tokens(x) + + mask = create_attention_mask(x, cache) + if cache is None: cache = [None] * len(self.layers) - mask = None - if x.shape[1] > 1: - mask = nn.MultiHeadAttention.create_additive_causal_mask(x.shape[1]) - mask = mask.astype(x.dtype) - for layer, c in zip(self.layers, cache): x = layer(x, mask, c) return self.final_layernorm(x) diff --git a/llms/mlx_lm/models/phi3.py b/llms/mlx_lm/models/phi3.py index dd2d6d82..2536aacb 100644 --- a/llms/mlx_lm/models/phi3.py +++ b/llms/mlx_lm/models/phi3.py @@ -4,7 +4,7 @@ from typing import Dict, List, Optional, Tuple, Union import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, KVCache +from .base import BaseModelArgs, KVCache, create_attention_mask from .su_rope import SuScaledRotaryEmbedding @@ -172,10 +172,7 @@ class Phi3Model(nn.Module): ): h = self.embed_tokens(inputs) - mask = None - if h.shape[1] > 1: - mask = nn.MultiHeadAttention.create_additive_causal_mask(h.shape[1]) - mask = mask.astype(h.dtype) + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) diff --git a/llms/mlx_lm/models/phi3small.py b/llms/mlx_lm/models/phi3small.py index e0f2d856..de075652 100644 --- a/llms/mlx_lm/models/phi3small.py +++ b/llms/mlx_lm/models/phi3small.py @@ -6,7 +6,7 @@ from typing import Dict, Optional, Tuple, Union import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, KVCache +from .base import BaseModelArgs, KVCache, create_attention_mask @dataclass @@ -263,10 +263,7 @@ class Phi3Model(nn.Module): if self.mup_embedding_multiplier: h = self.mup_embedding_multiplier * h - mask = None - if h.shape[1] > 1: - mask = nn.MultiHeadAttention.create_additive_causal_mask(h.shape[1]) - mask = mask.astype(h.dtype) + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) diff --git a/llms/mlx_lm/models/phixtral.py b/llms/mlx_lm/models/phixtral.py index 40a3bc4b..f0aef0c9 100644 --- a/llms/mlx_lm/models/phixtral.py +++ b/llms/mlx_lm/models/phixtral.py @@ -6,6 +6,7 @@ from typing import Tuple import mlx.core as mx import mlx.nn as nn +from .base import create_attention_mask from .switch_layers import SwitchMLP @@ -167,10 +168,7 @@ class Model(nn.Module): mask: mx.array = None, cache: mx.array = None, ) -> Tuple[mx.array, mx.array]: - mask = None - if x.shape[1] > 1: - mask = nn.MultiHeadAttention.create_additive_causal_mask(x.shape[1]) - mask = mask.astype(x.dtype) + mask = create_attention_mask(x, cache) y = self.transformer(x, mask, cache) return self.lm_head(y) diff --git a/llms/mlx_lm/models/plamo.py b/llms/mlx_lm/models/plamo.py index 2d0ddaed..47a9ea4f 100644 --- a/llms/mlx_lm/models/plamo.py +++ b/llms/mlx_lm/models/plamo.py @@ -5,7 +5,7 @@ import mlx.core as mx import mlx.nn as nn import numpy as np -from .base import BaseModelArgs +from .base import BaseModelArgs, create_attention_mask @dataclass @@ -171,10 +171,7 @@ class PlamoModel(nn.Module): ) -> Tuple[mx.array, Optional[List[Union[Tuple[mx.array, mx.array], None]]]]: h = self.embed_tokens(inputs) - mask = None - if h.shape[1] > 1: - mask = nn.MultiHeadAttention.create_additive_causal_mask(h.shape[1]) - mask = mask.astype(self.embed_tokens.weight.dtype) + mask = create_attention_mask(h, cache) if cache is None: cache = [None for _ in range(len(self.layers.layers))] diff --git a/llms/mlx_lm/models/qwen.py b/llms/mlx_lm/models/qwen.py index 44b6dfd3..67816599 100644 --- a/llms/mlx_lm/models/qwen.py +++ b/llms/mlx_lm/models/qwen.py @@ -4,7 +4,7 @@ from typing import Tuple import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs +from .base import BaseModelArgs, create_attention_mask @dataclass @@ -122,11 +122,7 @@ class QwenModel(nn.Module): def __call__(self, inputs, mask=None, cache=None): x = self.wte(inputs) - mask = None - T = x.shape[1] - if T > 1: - mask = nn.MultiHeadAttention.create_additive_causal_mask(T) - mask = mask.astype(x.dtype) + mask = create_attention_mask(x, cache) if cache is None: cache = [None] * len(self.h) diff --git a/llms/mlx_lm/models/qwen2.py b/llms/mlx_lm/models/qwen2.py index fab09003..cb8268aa 100644 --- a/llms/mlx_lm/models/qwen2.py +++ b/llms/mlx_lm/models/qwen2.py @@ -4,7 +4,7 @@ from typing import Dict, Optional, Tuple, Union import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, KVCache +from .base import BaseModelArgs, KVCache, create_attention_mask @dataclass @@ -151,10 +151,7 @@ class Qwen2Model(nn.Module): ): h = self.embed_tokens(inputs) - mask = None - if h.shape[1] > 1: - mask = nn.MultiHeadAttention.create_additive_causal_mask(h.shape[1]) - mask = mask.astype(h.dtype) + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) diff --git a/llms/mlx_lm/models/qwen2_moe.py b/llms/mlx_lm/models/qwen2_moe.py index 57f154a0..121ab813 100644 --- a/llms/mlx_lm/models/qwen2_moe.py +++ b/llms/mlx_lm/models/qwen2_moe.py @@ -5,7 +5,7 @@ from typing import Dict, Optional, Tuple, Union import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, KVCache +from .base import BaseModelArgs, KVCache, create_attention_mask from .switch_layers import SwitchGLU @@ -189,10 +189,7 @@ class Qwen2MoeModel(nn.Module): ): h = self.embed_tokens(inputs) - mask = None - if h.shape[1] > 1: - mask = nn.MultiHeadAttention.create_additive_causal_mask(h.shape[1]) - mask = mask.astype(h.dtype) + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) diff --git a/llms/mlx_lm/models/stablelm.py b/llms/mlx_lm/models/stablelm.py index 30e3a332..9b4d043c 100644 --- a/llms/mlx_lm/models/stablelm.py +++ b/llms/mlx_lm/models/stablelm.py @@ -5,7 +5,7 @@ from typing import Tuple import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs +from .base import BaseModelArgs, create_attention_mask @dataclass @@ -198,11 +198,7 @@ class Model(nn.Module): mask: mx.array = None, cache: mx.array = None, ) -> Tuple[mx.array, mx.array]: - mask = None - if x.shape[1] > 1: - mask = nn.MultiHeadAttention.create_additive_causal_mask(x.shape[1]) - mask = mask.astype(x.dtype) - + mask = create_attention_mask(x, cache) y = self.model(x, mask, cache) return self.lm_head(y) diff --git a/llms/mlx_lm/models/starcoder2.py b/llms/mlx_lm/models/starcoder2.py index 7b058d8f..a6eb5377 100644 --- a/llms/mlx_lm/models/starcoder2.py +++ b/llms/mlx_lm/models/starcoder2.py @@ -4,7 +4,7 @@ from typing import Optional, Tuple import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, KVCache +from .base import BaseModelArgs, KVCache, create_attention_mask @dataclass @@ -127,10 +127,7 @@ class Starcoder2Model(nn.Module): ): h = self.embed_tokens(inputs) - mask = None - if h.shape[1] > 1: - mask = nn.MultiHeadAttention.create_additive_causal_mask(h.shape[1]) - mask = mask.astype(h.dtype) + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) From 85dc76f6e0f2cf3ee3d84c211868a6856e163f3f Mon Sep 17 00:00:00 2001 From: madroid Date: Fri, 26 Jul 2024 23:58:52 +0800 Subject: [PATCH 003/188] Server: support stream_options (#913) * Server: support stream_options see https://x.com/OpenAIDevs/status/1787573348496773423 * Server: support stream_options * Server: check None type --- llms/mlx_lm/server.py | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/llms/mlx_lm/server.py b/llms/mlx_lm/server.py index 23d327e5..c13878f3 100644 --- a/llms/mlx_lm/server.py +++ b/llms/mlx_lm/server.py @@ -6,16 +6,12 @@ import logging import time import uuid import warnings -from functools import lru_cache from http.server import BaseHTTPRequestHandler, HTTPServer from pathlib import Path -from typing import Dict, List, Literal, NamedTuple, Optional, Tuple, Union +from typing import Dict, List, Literal, NamedTuple, Optional, Union import mlx.core as mx -import mlx.nn as nn -from transformers import PreTrainedTokenizer -from .tokenizer_utils import TokenizerWrapper from .utils import generate_step, load @@ -195,6 +191,7 @@ class APIHandler(BaseHTTPRequestHandler): # Extract request parameters from the body self.stream = self.body.get("stream", False) + self.stream_options = self.body.get("stream_options", None) self.requested_model = self.body.get("model", "default_model") self.max_tokens = self.body.get("max_tokens", 100) self.temperature = self.body.get("temperature", 1.0) @@ -525,9 +522,33 @@ class APIHandler(BaseHTTPRequestHandler): self.wfile.write(f"data: {json.dumps(response)}\n\n".encode()) self.wfile.flush() + if self.stream_options is not None and self.stream_options["include_usage"]: + response = self.completion_usage_response(len(prompt), len(tokens)) + self.wfile.write(f"data: {json.dumps(response)}\n\n".encode()) + self.wfile.write("data: [DONE]\n\n".encode()) self.wfile.flush() + def completion_usage_response( + self, + prompt_token_count: Optional[int] = None, + completion_token_count: Optional[int] = None, + ): + response = { + "id": self.request_id, + "system_fingerprint": f"fp_{uuid.uuid4()}", + "object": "chat.completion", + "model": self.requested_model, + "created": self.created, + "choices": [], + "usage": { + "prompt_tokens": prompt_token_count, + "completion_tokens": completion_token_count, + "total_tokens": prompt_token_count + completion_token_count, + }, + } + return response + def handle_chat_completions(self) -> mx.array: """ Handle a chat completion request. From 8fa12b0058a2647dff5d776f84deef43e1cb7720 Mon Sep 17 00:00:00 2001 From: Khush Gupta <78624519+khushgx@users.noreply.github.com> Date: Thu, 1 Aug 2024 16:18:18 -0700 Subject: [PATCH 004/188] Adapters loading (#902) * Added functionality to load in adapters through post-requests so you do not need to restart the server * ran pre-commit * nits * fix test --------- Co-authored-by: Awni Hannun --- llms/mlx_lm/SERVER.md | 7 +++++++ llms/mlx_lm/server.py | 22 ++++++++++++++++------ llms/tests/test_server.py | 2 +- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/llms/mlx_lm/SERVER.md b/llms/mlx_lm/SERVER.md index 48364bee..9c42d410 100644 --- a/llms/mlx_lm/SERVER.md +++ b/llms/mlx_lm/SERVER.md @@ -78,3 +78,10 @@ curl localhost:8080/v1/chat/completions \ - `logprobs`: (Optional) An integer specifying the number of top tokens and corresponding log probabilities to return for each output in the generated sequence. If set, this can be any value between 1 and 10, inclusive. + +- `model`: (Optional) A string path to a local model or Hugging Face repo id. + If the path is local is must be relative to the directory the server was + started in. + +- `adapters`: (Optional) A string path to low-rank adapters. The path must be + rlative to the directory the server was started in. diff --git a/llms/mlx_lm/server.py b/llms/mlx_lm/server.py index c13878f3..7456399c 100644 --- a/llms/mlx_lm/server.py +++ b/llms/mlx_lm/server.py @@ -97,8 +97,9 @@ class ModelProvider: "Local models must be relative to the current working dir." ) - def load(self, model_path): - if self.model_key == model_path: + # Added in adapter_path to load dynamically + def load(self, model_path, adapter_path=None): + if self.model_key == (model_path, adapter_path): return self.model, self.tokenizer # Remove the old model if it exists. @@ -116,18 +117,22 @@ class ModelProvider: if model_path == "default_model" and self.cli_args.model is not None: model, tokenizer = load( self.cli_args.model, - adapter_path=self.cli_args.adapter_path, + adapter_path=( + adapter_path if adapter_path else self.cli_args.adapter_path + ), # if the user doesn't change the model but adds an adapter path tokenizer_config=tokenizer_config, ) else: self._validate_model_path(model_path) - model, tokenizer = load(model_path, tokenizer_config=tokenizer_config) + model, tokenizer = load( + model_path, adapter_path=adapter_path, tokenizer_config=tokenizer_config + ) if self.cli_args.use_default_chat_template: if tokenizer.chat_template is None: tokenizer.chat_template = tokenizer.default_chat_template - self.model_key = model_path + self.model_key = (model_path, adapter_path) self.model = model self.tokenizer = tokenizer @@ -193,6 +198,7 @@ class APIHandler(BaseHTTPRequestHandler): self.stream = self.body.get("stream", False) self.stream_options = self.body.get("stream_options", None) self.requested_model = self.body.get("model", "default_model") + self.adapter = self.body.get("adapters", None) self.max_tokens = self.body.get("max_tokens", 100) self.temperature = self.body.get("temperature", 1.0) self.top_p = self.body.get("top_p", 1.0) @@ -204,7 +210,9 @@ class APIHandler(BaseHTTPRequestHandler): # Load the model if needed try: - self.model, self.tokenizer = self.model_provider.load(self.requested_model) + self.model, self.tokenizer = self.model_provider.load( + self.requested_model, self.adapter + ) except: self._set_completion_headers(404) self.end_headers() @@ -278,6 +286,8 @@ class APIHandler(BaseHTTPRequestHandler): if not isinstance(self.requested_model, str): raise ValueError("model must be a string") + if self.adapter is not None and not isinstance(self.adapter, str): + raise ValueError("adapter must be a string") def generate_response( self, diff --git a/llms/tests/test_server.py b/llms/tests/test_server.py index 4d71a5a3..b8047eaa 100644 --- a/llms/tests/test_server.py +++ b/llms/tests/test_server.py @@ -12,7 +12,7 @@ class DummyModelProvider: HF_MODEL_PATH = "mlx-community/Qwen1.5-0.5B-Chat-4bit" self.model, self.tokenizer = load(HF_MODEL_PATH) - def load(self, model): + def load(self, model, adapter=None): assert model in ["default_model", "chat_model"] return self.model, self.tokenizer From df744c98e67f94fba8a893cb5aba4fdb735f5f4a Mon Sep 17 00:00:00 2001 From: tidely <43219534+tidely@users.noreply.github.com> Date: Wed, 7 Aug 2024 01:24:15 +0300 Subject: [PATCH 005/188] Predict stop sequence matches during streaming (#541) * Predict stop sequence matches during streaming Check for overlap of stop sequences and the tokens array for potential sequence matches after more tokens get generated. Generate tokens until we can confirm that the stop sequence is not met. * fix typo * Change sequence_overlap logic * range isn't inclusive, add 1 to max_overlap * Add test_server.py Added a test for the sequence_overlap method * nits * eos sequence * finalize --------- Co-authored-by: Y4hL <43219534+Y4hL@users.noreply.github.com> Co-authored-by: Awni Hannun --- llms/mlx_lm/server.py | 56 ++++++++++++++++++++++++--------------- llms/tests/test_server.py | 13 +++++++++ 2 files changed, 47 insertions(+), 22 deletions(-) diff --git a/llms/mlx_lm/server.py b/llms/mlx_lm/server.py index 7456399c..79ac1836 100644 --- a/llms/mlx_lm/server.py +++ b/llms/mlx_lm/server.py @@ -8,7 +8,7 @@ import uuid import warnings from http.server import BaseHTTPRequestHandler, HTTPServer from pathlib import Path -from typing import Dict, List, Literal, NamedTuple, Optional, Union +from typing import Dict, List, Literal, NamedTuple, Optional, Sequence, Union import mlx.core as mx @@ -54,6 +54,21 @@ def stopping_criteria( return StopCondition(stop_met=False, trim_length=0) +def sequence_overlap(s1: Sequence, s2: Sequence) -> bool: + """ + Checks if a suffix of s1 has overlap with a prefix of s2 + + Args: + s1 (Sequence): The first sequence + s2 (Sequence): The second sequence + + Returns: + bool: If the two sequences have overlap + """ + max_overlap = min(len(s1), len(s2)) + return any(s1[-i:] == s2[:i] for i in range(1, max_overlap + 1)) + + def convert_chat(messages: List[dict], role_mapping: Optional[dict] = None): default_role_mapping = { "system_prompt": ( @@ -462,12 +477,13 @@ class APIHandler(BaseHTTPRequestHandler): stop_id_sequences: List[List[int]], ): """ - Generate response to prompt and foward it to the client using a Server Sent Events (SSE) stream. + Generate response to prompt and foward it to the client using a Server + Sent Events (SSE) stream. Args: prompt (mx.array): The prompt, in token form inside of a mlx array - stop_id_sequences (List[List[int]]): - A list of stop words passed to the stopping_criteria function + stop_id_sequences (List[List[int]]): A list of stop words passed to + the stopping_criteria function """ # No additional headers are needed, call end_headers self.end_headers() @@ -476,12 +492,9 @@ class APIHandler(BaseHTTPRequestHandler): detokenizer.reset() tokens = [] - max_stop_id_sequence_len = len(max(stop_id_sequences, default=[])) - # Buffer to store the last `max_stop_id_sequence_len` tokens - # to check for stop conditions before writing to the stream. - stop_sequence_buffer = [] stop_sequence_suffix = None logging.debug(f"Starting stream:") + for (token, _), _ in zip( generate_step( prompt=prompt, @@ -496,11 +509,6 @@ class APIHandler(BaseHTTPRequestHandler): detokenizer.add_token(token) logging.debug(detokenizer.text) tokens.append(token) - stop_sequence_buffer.append(token) - - # Continue generating tokens until buffer is as large as the longest stop_id_sequence - if len(stop_sequence_buffer) < max_stop_id_sequence_len: - continue stop_condition = stopping_criteria( tokens, @@ -514,21 +522,25 @@ class APIHandler(BaseHTTPRequestHandler): ) break + # If the end of tokens overlaps with a stop sequence, generate new + # tokens until we know if the stop sequence is hit or not + if any( + (sequence_overlap(tokens, sequence) for sequence in stop_id_sequences) + ): + continue + new_text = detokenizer.last_segment response = self.generate_response(new_text, None) self.wfile.write(f"data: {json.dumps(response)}\n\n".encode()) self.wfile.flush() - stop_sequence_buffer = [] # check is there any remaining text to send - if stop_sequence_buffer: - next_chunk = ( - detokenizer.last_segment - if stop_sequence_suffix is None - else detokenizer.last_segment[: -len(stop_sequence_suffix)] - ) - response = self.generate_response(next_chunk, "length") - + detokenizer.finalize() + last_segment = detokenizer.last_segment + if last_segment: + if stop_sequence_suffix is not None: + last_segment = last_segment[: -len(stop_sequence_suffix)] + response = self.generate_response(last_segment, "length") self.wfile.write(f"data: {json.dumps(response)}\n\n".encode()) self.wfile.flush() diff --git a/llms/tests/test_server.py b/llms/tests/test_server.py index b8047eaa..baea664a 100644 --- a/llms/tests/test_server.py +++ b/llms/tests/test_server.py @@ -1,3 +1,4 @@ +# Copyright © 2024 Apple Inc. import http import threading import unittest @@ -76,6 +77,18 @@ class TestServer(unittest.TestCase): self.assertIn("id", response_body) self.assertIn("choices", response_body) + def test_sequence_overlap(self): + from mlx_lm.server import sequence_overlap + + self.assertTrue(sequence_overlap([1], [1])) + self.assertTrue(sequence_overlap([1, 2], [1, 2])) + self.assertTrue(sequence_overlap([1, 3], [3, 4])) + self.assertTrue(sequence_overlap([1, 2, 3], [2, 3])) + + self.assertFalse(sequence_overlap([1], [2])) + self.assertFalse(sequence_overlap([1, 2], [3, 4])) + self.assertFalse(sequence_overlap([1, 2, 3], [4, 1, 2, 3])) + if __name__ == "__main__": unittest.main() From 33905447f92dc7ce38680f7f6bd8d3335dd204e5 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Fri, 9 Aug 2024 11:11:58 -0700 Subject: [PATCH 006/188] Whisper updates to allow HF models (#923) * simplify conversion and update convert for HF models * use npz for compat * fixes * fixes * fix gguf * allow user supplied path --- README.md | 1 + llms/mlx_lm/gguf.py | 6 +- whisper/convert.py | 176 ++++++++++++++++++++------------- whisper/mlx_whisper/version.py | 2 +- whisper/test.py | 6 +- 5 files changed, 116 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index 2ca11d4b..00e57803 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ Some more useful examples are listed below. - Joint text and image embeddings with [CLIP](clip). - Text generation from image and text inputs with [LLaVA](llava). +- Image segmentation with [Segment Anything (SAM)](segment_anything). ### Other Models diff --git a/llms/mlx_lm/gguf.py b/llms/mlx_lm/gguf.py index 1f858d70..5d524580 100644 --- a/llms/mlx_lm/gguf.py +++ b/llms/mlx_lm/gguf.py @@ -59,7 +59,7 @@ class HfVocab: for token_id in range(self.vocab_size_base): if token_id in self.added_tokens_ids: continue - token_text = reverse_vocab[token_id].encode("utf-8") + token_text = reverse_vocab[token_id] yield token_text, self.get_token_score(token_id), self.get_token_type( token_id, token_text, self.special_ids ) @@ -67,7 +67,7 @@ class HfVocab: def get_token_type( self, token_id: int, token_text: bytes, special_ids: Set[int] ) -> TokenType: - if re.fullmatch(rb"<0x[0-9A-Fa-f]{2}>", token_text): + if re.fullmatch(rb"<0x[0-9A-Fa-f]{2}>", token_text.encode("utf-8")): return TokenType.BYTE return TokenType.CONTROL if token_id in special_ids else TokenType.NORMAL @@ -84,7 +84,7 @@ class HfVocab: else: toktype = TokenType.USER_DEFINED score = -1000.0 - yield text.encode("utf-8"), score, toktype + yield text, score, toktype def has_newline_token(self): return "<0x0A>" in self.tokenizer.vocab or "\n" in self.tokenizer.vocab diff --git a/whisper/convert.py b/whisper/convert.py index 37825d6c..85ce5fba 100644 --- a/whisper/convert.py +++ b/whisper/convert.py @@ -105,6 +105,88 @@ def available_models() -> List[str]: return list(_MODELS.keys()) +def hf_to_pt(weights, config): + config = { + "n_mels": config["num_mel_bins"], + "n_audio_ctx": config["max_source_positions"], + "n_audio_state": config["d_model"], + "n_audio_head": config["encoder_attention_heads"], + "n_audio_layer": config["encoder_layers"], + "n_vocab": config["vocab_size"], + "n_text_ctx": config["max_target_positions"], + "n_text_state": config["d_model"], + "n_text_head": config["decoder_attention_heads"], + "n_text_layer": config["decoder_layers"], + } + + def remap(k): + k = k.replace("model.", "") + k = k.replace(".layers", ".blocks") + k = k.replace(".self_attn", ".attn") + k = k.replace(".attn_layer_norm", ".attn_ln") + k = k.replace(".encoder_attn.", ".cross_attn.") + k = k.replace(".encoder_attn_layer_norm", ".cross_attn_ln") + k = k.replace(".final_layer_norm", ".mlp_ln") + k = k.replace(".q_proj", ".query") + k = k.replace(".k_proj", ".key") + k = k.replace(".v_proj", ".value") + k = k.replace(".out_proj", ".out") + k = k.replace(".fc1", ".mlp1") + k = k.replace(".fc2", ".mlp2") + k = k.replace("embed_positions.weight", "positional_embedding") + k = k.replace("decoder.embed_tokens", "decoder.token_embedding") + k = k.replace("encoder.layer_norm", "encoder.ln_post") + k = k.replace("decoder.layer_norm", "decoder.ln") + return k + + # token embeddings are shared with output projection + weights.pop("proj_out.weight", None) + weights = {remap(k): v for k, v in weights.items()} + return weights, config + + +def load_torch_weights_and_config( + name_or_path: str, + download_root: str = None, +): + if download_root is None: + download_root = os.path.join(os.path.expanduser("~"), ".cache/whisper") + + # todo: accept alignment_heads of local Pytorch checkpoint + alignment_heads = None + if name_or_path in _MODELS: + alignment_heads = _ALIGNMENT_HEADS[name_or_path] + name_or_path = _download(_MODELS[name_or_path], download_root) + elif not Path(name_or_path).exists(): + # Try downloading from HF + from huggingface_hub import snapshot_download + + name_or_path = snapshot_download( + repo_id=name_or_path, + allow_patterns=["*.json", "pytorch_model.bin", "*.txt"], + ) + else: + raise RuntimeError( + f"Model {name_or_path} is not found in {available_models()}," + "on Hugging Face or as a local path." + ) + + if name_or_path.endswith(".pt"): + checkpoint = torch.load(name_or_path, map_location="cpu") + weights, config = checkpoint["model_state_dict"], checkpoint["dims"] + else: + name_or_path = Path(name_or_path) + weights = torch.load( + name_or_path / "pytorch_model.bin", + map_location="cpu", + ) + with open(name_or_path / "config.json", "r") as fp: + config = json.load(fp) + weights, config = hf_to_pt(weights, config) + + return weights, config, alignment_heads + + def load_torch_model( name_or_path: str, download_root: str = None, @@ -115,7 +197,8 @@ def load_torch_model( Parameters ---------- name_or_path : str - one of the official model names listed by `whisper.available_models()` or a local Pytorch checkpoint which is in the original OpenAI format + one of the official model names listed by `whisper.available_models()` or + a local Pytorch checkpoint which is in the original OpenAI format download_root: str path to download the model files; by default, it uses "~/.cache/whisper" @@ -128,22 +211,12 @@ def load_torch_model( if download_root is None: download_root = os.path.join(os.path.expanduser("~"), ".cache/whisper") - # todo: accept alignment_heads of local Pytorch checkpoint - alignment_heads = None - if name_or_path in _MODELS: - alignment_heads = _ALIGNMENT_HEADS[name_or_path] - name_or_path = _download(_MODELS[name_or_path], download_root) - elif not Path(name_or_path).is_file(): - raise RuntimeError( - f"Model {name_or_path} is neither found in {available_models()} nor as a local path" - ) - - with open(name_or_path, "rb") as fp: - checkpoint = torch.load(fp) - - dims = torch_whisper.ModelDimensions(**checkpoint["dims"]) + weights, config, alignment_heads = load_torch_weights_and_config( + name_or_path, download_root + ) + dims = torch_whisper.ModelDimensions(**config) model = torch_whisper.Whisper(dims) - model.load_state_dict(checkpoint["model_state_dict"]) + model.load_state_dict(weights) if alignment_heads is not None: model.set_alignment_heads(alignment_heads) @@ -151,59 +224,26 @@ def load_torch_model( return model -def convert(model, rules=None): - params = {} - if rules is not None and type(model) in rules: - out = rules[type(model)](model, rules) - return out - if isinstance(model, torch.Tensor): - return mx.array(model.detach().numpy()) - if isinstance(model, torch.nn.ModuleList): - return [convert(n, rules) for n in model.children()] - if isinstance(model, torch.nn.Conv1d): - return { - "weight": convert(model.weight).transpose(0, 2, 1), - "bias": convert(model.bias), - } - for k, n in model.named_children(): - if k in rules: - params.update(rules[k](n, rules)) - else: - params[k] = convert(n, rules) - for k, p in model.named_parameters(recurse=False): - params[k] = convert(p) - return params +def convert(name_or_path: str, dtype: mx.Dtype = mx.float16): + def remap(key, value): + key = key.replace("mlp.0", "mlp1") + key = key.replace("mlp.2", "mlp2") + if "conv" in key and value.ndim == 3: + value = value.swapaxes(1, 2) + return key, mx.array(value.detach()).astype(dtype) + weights, config, alignment_heads = load_torch_weights_and_config(name_or_path) + weights.pop("encoder.positional_embedding", None) + weights = dict(remap(k, v) for k, v in weights.items()) -def torch_to_mlx( - torch_model: torch_whisper.Whisper, - dtype: mx.Dtype = mx.float16, -) -> Whisper: - def convert_rblock(model, rules): - children = dict(model.named_children()) - mlp = list(children.pop("mlp").children()) - params = { - "mlp1": convert(mlp[0], rules), - "mlp2": convert(mlp[-1], rules), - } - for k, n in children.items(): - params[k] = convert(n, rules) - return params + model_dims = ModelDimensions(**config) + model = Whisper(model_dims, dtype) + model.load_weights(list(weights.items()), strict=False) - rules = { - torch_whisper.ResidualAttentionBlock: convert_rblock, - } + if alignment_heads is not None: + model.set_alignment_heads(alignment_heads) - params = convert(torch_model, rules) - - mlx_model = Whisper(torch_model.dims, dtype) - params = tree_map(lambda p: p.astype(dtype), params) - mlx_model.update(params) - - if (alignment_heads := getattr(torch_model, "alignment_heads", None)) is not None: - mlx_model.set_alignment_heads(alignment_heads.indices().T.numpy()) - - return mlx_model + return model def upload_to_hub(path: str, name: str, torch_name_or_path: str): @@ -292,13 +332,13 @@ if __name__ == "__main__": action="store_true", ) parser.add_argument( - "--q_group_size", + "--q-group-size", help="Group size for quantization.", type=int, default=64, ) parser.add_argument( - "--q_bits", + "--q-bits", help="Bits per weight for quantization.", type=int, default=4, @@ -318,7 +358,7 @@ if __name__ == "__main__": dtype = getattr(mx, args.dtype) print("[INFO] Loading") - model = torch_to_mlx(load_torch_model(args.torch_name_or_path), dtype) + model = convert(args.torch_name_or_path, dtype) config = asdict(model.dims) weights = dict(tree_flatten(model.parameters())) diff --git a/whisper/mlx_whisper/version.py b/whisper/mlx_whisper/version.py index 87ee07a7..ae3cfb71 100644 --- a/whisper/mlx_whisper/version.py +++ b/whisper/mlx_whisper/version.py @@ -1,3 +1,3 @@ # Copyright © 2023-2024 Apple Inc. -__version__ = "0.1.0" +__version__ = "0.2.0" diff --git a/whisper/test.py b/whisper/test.py index ce559251..f0acb3cd 100644 --- a/whisper/test.py +++ b/whisper/test.py @@ -13,7 +13,7 @@ import mlx_whisper.decoding as decoding import mlx_whisper.load_models as load_models import numpy as np import torch -from convert import load_torch_model, quantize, torch_to_mlx +from convert import convert, load_torch_model, quantize from mlx.utils import tree_flatten MODEL_NAME = "tiny" @@ -41,12 +41,12 @@ def _save_model(save_dir, weights, config): def load_torch_and_mlx(): torch_model = load_torch_model(MODEL_NAME) - fp32_model = torch_to_mlx(torch_model, dtype=mx.float32) + fp32_model = convert(MODEL_NAME, dtype=mx.float32) config = asdict(fp32_model.dims) weights = dict(tree_flatten(fp32_model.parameters())) _save_model(MLX_FP32_MODEL_PATH, weights, config) - fp16_model = torch_to_mlx(torch_model, dtype=mx.float16) + fp16_model = convert(MODEL_NAME, dtype=mx.float16) config = asdict(fp16_model.dims) weights = dict(tree_flatten(fp16_model.parameters())) _save_model(MLX_FP16_MODEL_PATH, weights, config) From 95840f32e28ca2af4ec49096fc4ddace51e0fb30 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Wed, 14 Aug 2024 10:22:04 -0700 Subject: [PATCH 007/188] Fix whipser conversion for safetensors models (#935) * fix whipser conversion for safetensor only. error in mlx lm for existing paths * fix tests --- llms/mlx_lm/utils.py | 13 ++++++++++--- llms/tests/test_utils.py | 1 + whisper/convert.py | 34 +++++++++++++++++++++++----------- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index cffa2a89..0e7f7a39 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -660,6 +660,16 @@ def convert( revision: Optional[str] = None, dequantize: bool = False, ): + # Check the save path is empty + if isinstance(mlx_path, str): + mlx_path = Path(mlx_path) + + if mlx_path.exists(): + raise ValueError( + f"Cannot save to the path {mlx_path} as it already exists." + " Please delete the file/directory or specify a new path to save to." + ) + print("[INFO] Loading") model_path = get_model_path(hf_path, revision=revision) model, config, tokenizer = fetch_from_hub(model_path, lazy=True) @@ -681,9 +691,6 @@ def convert( model = dequantize_model(model) weights = dict(tree_flatten(model.parameters())) - if isinstance(mlx_path, str): - mlx_path = Path(mlx_path) - del model save_weights(mlx_path, weights, donate_weights=True) diff --git a/llms/tests/test_utils.py b/llms/tests/test_utils.py index 576c2820..18cfa8c7 100644 --- a/llms/tests/test_utils.py +++ b/llms/tests/test_utils.py @@ -82,6 +82,7 @@ class TestUtils(unittest.TestCase): self.assertTrue(isinstance(model.layers[-1].mlp.up_proj, nn.QuantizedLinear)) # Check model weights have right type + mlx_path = os.path.join(self.test_dir, "mlx_model_bf16") utils.convert(HF_MODEL_PATH, mlx_path=mlx_path, dtype="bfloat16") model, _ = utils.load(mlx_path) diff --git a/whisper/convert.py b/whisper/convert.py index 85ce5fba..da7195e0 100644 --- a/whisper/convert.py +++ b/whisper/convert.py @@ -163,7 +163,12 @@ def load_torch_weights_and_config( name_or_path = snapshot_download( repo_id=name_or_path, - allow_patterns=["*.json", "pytorch_model.bin", "*.txt"], + allow_patterns=[ + "*.json", + "pytorch_model.bin", + "model.safetensors", + "*.txt", + ], ) else: raise RuntimeError( @@ -176,10 +181,11 @@ def load_torch_weights_and_config( weights, config = checkpoint["model_state_dict"], checkpoint["dims"] else: name_or_path = Path(name_or_path) - weights = torch.load( - name_or_path / "pytorch_model.bin", - map_location="cpu", - ) + pt_path = name_or_path / "pytorch_model.bin" + if pt_path.is_file(): + weights = torch.load(pt_path, map_location="cpu") + else: + weights = mx.load(str(name_or_path / "model.safetensors")) with open(name_or_path / "config.json", "r") as fp: config = json.load(fp) weights, config = hf_to_pt(weights, config) @@ -230,7 +236,9 @@ def convert(name_or_path: str, dtype: mx.Dtype = mx.float16): key = key.replace("mlp.2", "mlp2") if "conv" in key and value.ndim == 3: value = value.swapaxes(1, 2) - return key, mx.array(value.detach()).astype(dtype) + if isinstance(value, torch.Tensor): + value = mx.array(value.detach()) + return key, value.astype(dtype) weights, config, alignment_heads = load_torch_weights_and_config(name_or_path) weights.pop("encoder.positional_embedding", None) @@ -262,12 +270,16 @@ This model was converted to MLX format from [`{torch_name_or_path}`](). ## Use with mlx ```bash -git clone https://github.com/ml-explore/mlx-examples.git -cd mlx-examples/whisper/ -pip install -r requirements.txt +pip install mlx-whisper +``` ->> import whisper ->> whisper.transcribe("FILE_NAME") +```python +import mlx_whisper + +result = mlx_whisper.transcribe( + "FILE_NAME", + path_or_hf_repo={repo_id}, +) ``` """ card = ModelCard(text) From 9b83004631bf0d2c225d4bc31c0ed613b1b15b8f Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Thu, 15 Aug 2024 11:29:09 -0700 Subject: [PATCH 008/188] Faster sampling with `mx.compile` (#937) * faster sampling with compile * fix test --- llms/mlx_lm/sample_utils.py | 14 +++++++++-- llms/mlx_lm/utils.py | 4 ++-- llms/tests/test_sample_utils.py | 42 ++++++++++++++------------------- 3 files changed, 32 insertions(+), 28 deletions(-) diff --git a/llms/mlx_lm/sample_utils.py b/llms/mlx_lm/sample_utils.py index f22ce2d7..2e9c172e 100644 --- a/llms/mlx_lm/sample_utils.py +++ b/llms/mlx_lm/sample_utils.py @@ -1,6 +1,11 @@ +# Copyright © 2023-2024 Apple Inc. + +from functools import partial + import mlx.core as mx +@partial(mx.compile, inputs=mx.random.state, outputs=mx.random.state) def top_p_sampling(logits: mx.array, top_p: float, temperature: float) -> mx.array: """ Apply top-p (nucleus) sampling to logits. @@ -13,7 +18,7 @@ def top_p_sampling(logits: mx.array, top_p: float, temperature: float) -> mx.arr token selected based on the top-p criterion. """ # referenced implementation from https://github.com/huggingface/transformers/blob/main/src/transformers/generation/logits_process.py#L449-L460 - probs = mx.softmax(logits / temperature, axis=-1) + probs = mx.softmax(logits * (1 / temperature), axis=-1) # sort probs in ascending order sorted_indices = mx.argsort(probs, axis=-1) @@ -25,10 +30,15 @@ def top_p_sampling(logits: mx.array, top_p: float, temperature: float) -> mx.arr top_probs = mx.where( cumulative_probs > 1 - top_p, sorted_probs, - mx.zeros_like(sorted_probs), + 0, ) sorted_token = mx.random.categorical(mx.log(top_probs)) token = sorted_indices.squeeze(0)[sorted_token] return token + + +@partial(mx.compile, inputs=mx.random.state, outputs=mx.random.state) +def categorical_sampling(logits, temp): + return mx.random.categorical(logits * (1 / temp)) diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index 0e7f7a39..a34cc6ad 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -20,7 +20,7 @@ from transformers import PreTrainedTokenizer # Local imports from .models.base import KVCache -from .sample_utils import top_p_sampling +from .sample_utils import categorical_sampling, top_p_sampling from .tokenizer_utils import TokenizerWrapper, load_tokenizer from .tuner.utils import apply_lora_layers from .tuner.utils import dequantize as dequantize_model @@ -169,7 +169,7 @@ def generate_step( if top_p > 0 and top_p < 1.0: token = top_p_sampling(logits, top_p, temp) else: - token = mx.random.categorical(logits * (1 / temp)) + token = categorical_sampling(logits, temp) return token, logprobs diff --git a/llms/tests/test_sample_utils.py b/llms/tests/test_sample_utils.py index 0bccdd07..ec0e2cb7 100644 --- a/llms/tests/test_sample_utils.py +++ b/llms/tests/test_sample_utils.py @@ -1,38 +1,32 @@ import unittest -from unittest.mock import patch import mlx.core as mx from mlx_lm.sample_utils import top_p_sampling class TestSamplingUtils(unittest.TestCase): - @patch("mlx.core.random.categorical") - def test_top_p_sampling(self, mock_categorical): - logits = mx.array([[1.0, 2.0, 3.0, 4.0]]) - top_p = 0.3 + def test_top_p_sampling(self): + probs = mx.array([0.9, 0.0, 0.0, 0.1])[None] + logits = mx.log(probs) temperature = 1.0 - expected_token = mx.array([3]) - mock_categorical.return_value = expected_token - token = top_p_sampling(logits, top_p, temperature) - expected_top_probs = mx.array([[0.0, 0.0, 0.0, 0.643914]]) - self.assertTrue(mx.allclose(token, expected_token)) - args, _ = mock_categorical.call_args - self.assertTrue(args[0].shape == expected_top_probs.shape) - self.assertTrue(mx.allclose(args[0], mx.log(expected_top_probs))) + token = top_p_sampling(logits, 0.3, temperature).item() + self.assertEqual(token, 0) - logits = mx.array([[1.0, 2.0, 3.0, 4.0]]) - top_p = 0.9 - temperature = 1.0 - expected_token = mx.array([3]) - mock_categorical.return_value = expected_token + token = top_p_sampling(logits, 0.95, temperature).item() + self.assertTrue(token in (0, 3)) - token = top_p_sampling(logits, top_p, temperature) - expected_top_probs = mx.array([[0.0, 0.0871443, 0.236883, 0.643914]]) - self.assertTrue(mx.allclose(token, expected_token)) - args, _ = mock_categorical.call_args - self.assertTrue(args[0].shape == expected_top_probs.shape) - self.assertTrue(mx.allclose(args[0], mx.log(expected_top_probs))) + probs = mx.array([0.0, 0.5, 0.4, 0.1])[None] + logits = mx.log(probs) + + token = top_p_sampling(logits, 0.4, temperature).item() + self.assertEqual(token, 1) + + token = top_p_sampling(logits, 0.6, temperature).item() + self.assertTrue(token in (1, 2)) + + token = top_p_sampling(logits, 0.95, temperature).item() + self.assertTrue(token in (1, 2, 3)) if __name__ == "__main__": From c50971e8608e05c42d8078b7009dc5b9c33d25c0 Mon Sep 17 00:00:00 2001 From: Chime Ogbuji Date: Thu, 15 Aug 2024 18:45:02 -0400 Subject: [PATCH 009/188] Min P implementation (#926) * Min P implementation * Change default to 0 (no min_p) * nits * nits --------- Co-authored-by: Awni Hannun --- llms/mlx_lm/sample_utils.py | 58 +++++++++++++++++++++++++++++++++++++ llms/mlx_lm/utils.py | 10 ++++++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/llms/mlx_lm/sample_utils.py b/llms/mlx_lm/sample_utils.py index 2e9c172e..20b008fa 100644 --- a/llms/mlx_lm/sample_utils.py +++ b/llms/mlx_lm/sample_utils.py @@ -5,6 +5,64 @@ from functools import partial import mlx.core as mx +@partial(mx.compile, inputs=mx.random.state, outputs=mx.random.state) +def min_p_sampling( + logits: mx.array, + min_p: float, + min_tokens_to_keep: int = 1, + temperature=1.0, +) -> mx.array: + """ + Apply min-p sampling to the logits. + + Min-p keeps all tokens that are above a minimum probability, scaled by the + probability of the most likely token. As a result, the filter is more + aggressive given a very high-probability token. + + Args: + logits: The logits from the model's output. + min_p (float): Minimum token probability. Typical values are in the + 0.01-0.2 range, comparably selective as setting `top_p` in the + 0.99-0.8 range. + min_tokens_to_keep (int, optional): Minimum number of tokens that cannot + be filtered. Default: ``1``. + + """ + if not (0 <= min_p <= 1.0): + raise ValueError( + f"`min_p` has to be a float in the [0, 1] interval, but is {min_p}" + ) + if not isinstance(min_tokens_to_keep, int) or (min_tokens_to_keep < 1): + raise ValueError( + f"`min_tokens_to_keep` has to be a positive integer, but is {min_tokens_to_keep}" + ) + # reference implementation: https://github.com/huggingface/transformers/blob/main/src/transformers/generation/logits_process.py#L531-L605 + + # Softmax probabilities + probs = mx.softmax(logits * (1 / temperature), axis=-1) + + # Indices sorted in decreasing order + sorted_indices = mx.argsort(-logits).squeeze(0) + sorted_probs = probs[..., sorted_indices] + + # Top probability + top_probs = probs[..., sorted_indices[0]] + + # Calculate the min_p threshold + scaled_min_p = min_p * top_probs + + # Mask tokens that have a probability less than the scaled min_p + tokens_to_remove = sorted_probs < scaled_min_p + tokens_to_remove[..., :min_tokens_to_keep] = False + + # Create pool of tokens with probability less than scaled min_p + selected_probs = mx.where(tokens_to_remove, 0, sorted_probs) + + # Return sampled token + sorted_token = mx.random.categorical(mx.log(selected_probs)) + return sorted_indices[sorted_token] + + @partial(mx.compile, inputs=mx.random.state, outputs=mx.random.state) def top_p_sampling(logits: mx.array, top_p: float, temperature: float) -> mx.array: """ diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index a34cc6ad..e7a9dba8 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -20,7 +20,7 @@ from transformers import PreTrainedTokenizer # Local imports from .models.base import KVCache -from .sample_utils import categorical_sampling, top_p_sampling +from .sample_utils import categorical_sampling, min_p_sampling, top_p_sampling from .tokenizer_utils import TokenizerWrapper, load_tokenizer from .tuner.utils import apply_lora_layers from .tuner.utils import dequantize as dequantize_model @@ -133,6 +133,8 @@ def generate_step( repetition_penalty: Optional[float] = None, repetition_context_size: Optional[int] = 20, top_p: float = 1.0, + min_p: float = 0.0, + min_tokens_to_keep: int = 1, logit_bias: Optional[Dict[int, float]] = None, ) -> Generator[Tuple[mx.array, mx.array], None, None]: """ @@ -149,6 +151,10 @@ def generate_step( consider for repetition penalty. Default: ``20``. top_p (float, optional): Nulceus sampling, higher means model considers more less likely words. + min_p (float, optional): The minimum value (scaled by the top token's + probability) that a token probability must have to be considered. + min_tokens_to_keep (int, optional): Minimum number of tokens that cannot + be filtered by min_p sampling. logit_bias (dictionary, optional): Additive logit bias. Yields: @@ -168,6 +174,8 @@ def generate_step( else: if top_p > 0 and top_p < 1.0: token = top_p_sampling(logits, top_p, temp) + elif min_p != 0.0: + token = min_p_sampling(logits, min_p, min_tokens_to_keep, temp) else: token = categorical_sampling(logits, temp) From 4e017008169d31fe943b8ca6023f058a724e555f Mon Sep 17 00:00:00 2001 From: Zai Thottakath Date: Fri, 16 Aug 2024 09:38:36 -0500 Subject: [PATCH 010/188] Allow the entire model to be targed for LoRA and DoRA fine tuning: LoRA and DoRA embeddings with small DoRALinear bug fix (#914) * feature: LoRA adapter for Embeddings * feature: wire in LoRAEmbedding into the tuner. Allow the embedding and non model.layers Linear layers to be targeted for fine tuning * feature: DoRA adapter for Embeddings * feature: wire in DoRAEmbedding * bugfix: ensure self.m is recalculated when the linear layer is changed in DoRALinear.from_linear * refactor: prefer from_base over from_linear or from_embedding. prefer fuse over to_linear or to_embedding * cleanup: remove unused imports in test_dora.py * refactor: remove unnecessary non_layer_modules * cleanup: remove wrong comments for lora embedding dropout. remove uncessary parens in dora embedding dropout * nits --------- Co-authored-by: Awni Hannun --- llms/mlx_lm/fuse.py | 10 +- llms/mlx_lm/tuner/dora.py | 110 +++++++++++++++++- llms/mlx_lm/tuner/lora.py | 102 +++++++++++++++- llms/mlx_lm/tuner/utils.py | 15 ++- llms/tests/{test_lora.py => test_finetune.py} | 90 +++++++++++++- 5 files changed, 306 insertions(+), 21 deletions(-) rename llms/tests/{test_lora.py => test_finetune.py} (65%) diff --git a/llms/mlx_lm/fuse.py b/llms/mlx_lm/fuse.py index fa06eb54..16457036 100644 --- a/llms/mlx_lm/fuse.py +++ b/llms/mlx_lm/fuse.py @@ -6,8 +6,8 @@ from pathlib import Path from mlx.utils import tree_flatten, tree_unflatten from .gguf import convert_to_gguf -from .tuner.dora import DoRALinear -from .tuner.lora import LoRALinear, LoRASwitchLinear +from .tuner.dora import DoRAEmbedding, DoRALinear +from .tuner.lora import LoRAEmbedding, LoRALinear, LoRASwitchLinear from .tuner.utils import apply_lora_layers, dequantize from .utils import ( fetch_from_hub, @@ -80,9 +80,11 @@ def main() -> None: model = apply_lora_layers(model, args.adapter_path) fused_linears = [ - (n, m.to_linear()) + (n, m.fuse()) for n, m in model.named_modules() - if isinstance(m, (LoRASwitchLinear, LoRALinear, DoRALinear)) + if isinstance( + m, (LoRASwitchLinear, LoRALinear, LoRAEmbedding, DoRALinear, DoRAEmbedding) + ) ] model.update_modules(tree_unflatten(fused_linears)) diff --git a/llms/mlx_lm/tuner/dora.py b/llms/mlx_lm/tuner/dora.py index de10556b..bd2dfb01 100644 --- a/llms/mlx_lm/tuner/dora.py +++ b/llms/mlx_lm/tuner/dora.py @@ -8,7 +8,7 @@ import mlx.nn as nn class DoRALinear(nn.Module): @staticmethod - def from_linear( + def from_base( linear: nn.Linear, r: int = 8, dropout: float = 0.0, @@ -25,10 +25,10 @@ class DoRALinear(nn.Module): dropout=dropout, scale=scale, ) - dora_lin.linear = linear + dora_lin.set_linear(linear) return dora_lin - def to_linear(self, de_quantize: bool = False): + def fuse(self, de_quantize: bool = False): linear = self.linear bias = "bias" in linear weight = linear.weight @@ -61,7 +61,7 @@ class DoRALinear(nn.Module): super().__init__() # Regular linear layer weights - self.linear = nn.Linear(input_dims, output_dims, bias=bias) + self.set_linear(nn.Linear(input_dims, output_dims, bias=bias)) self.dropout = nn.Dropout(p=dropout) # Scale for low-rank update @@ -75,6 +75,9 @@ class DoRALinear(nn.Module): shape=(input_dims, r), ) self.lora_b = mx.zeros(shape=(r, output_dims)) + + def set_linear(self, linear: nn.Linear): + self.linear = linear self.m = mx.linalg.norm(self.linear.weight, axis=1) def __call__(self, x): @@ -93,3 +96,102 @@ class DoRALinear(nn.Module): if "bias" in self.linear: out = out + self.linear.bias return out + + +class DoRAEmbedding(nn.Module): + def from_base( + embedding: nn.Embedding, + r: int = 8, + dropout: float = 0.0, + scale: float = 20.0, + ): + num_embeddings, dims = embedding.weight.shape + + # TODO support quantized weights in DoRALinear + if isinstance(embedding, nn.QuantizedLinear): + raise ValueError("DoRAEmbedding does not yet support quantization.") + dora_embedding = DoRAEmbedding( + num_embeddings=num_embeddings, + dims=dims, + r=r, + dropout=dropout, + scale=scale, + ) + dora_embedding.set_embedding(embedding) + return dora_embedding + + def fuse(self, de_quantize: bool = False): + embedding = self.embedding + weight = embedding.weight + + # Use the same type as the linear weight if not quantized + dtype = weight.dtype + + num_embeddings, dims = weight.shape + fused_embedding = nn.Embedding(num_embeddings, dims) + + lora_a = (self.scale * self.lora_a).astype(dtype) + lora_b = self.lora_b.astype(dtype) + weight = weight + lora_a @ lora_b + norm_scale = self.m / mx.linalg.norm(weight, axis=1) + fused_embedding.weight = norm_scale[:, None] * weight + + return fused_embedding + + def __init__( + self, + num_embeddings: int, + dims: int, + r: int = 8, + dropout: float = 0.0, + scale: float = 20.0, + ): + super().__init__() + + # Regular embedding layer weights + self.set_embedding(nn.Embedding(num_embeddings, dims)) + self.dropout = nn.Dropout(p=dropout) + + # Scale for low-rank update + self.scale = scale + + # Low rank lora weights + scale = 1 / math.sqrt(num_embeddings) + self.lora_a = mx.random.uniform( + low=-scale, + high=scale, + shape=(num_embeddings, r), + ) + self.lora_b = mx.zeros(shape=(r, dims)) + + def set_embedding(self, embedding: nn.Module): + self.embedding = embedding + self.m = mx.linalg.norm(embedding.weight, axis=1) + + def __call__(self, x): + y = self.embedding(x) + z = self.scale * self.lora_a[x] @ self.lora_b + out = y + self.dropout(z).astype(y.dtype) + + # Compute the norm of the adapted weights for the individual embeddings + adapted = y + z + denom = mx.stop_gradient(mx.linalg.norm(adapted, axis=-1)) + + # Remove the norm and scale by the learned magnitude + out = (self.m[x] / denom)[..., None] * out + + return out + + def as_linear(self, x): + y = self.embedding.as_linear(x) + z = (self.dropout(x) @ self.lora_b.T) @ self.lora_a.T + out = y + (self.scale * z).astype(x.dtype) + + # Compute the norm of the adapted weights + adapted = self.embedding.weight + (self.scale * self.lora_a) @ self.lora_b + denom = mx.stop_gradient(mx.linalg.norm(adapted, axis=1)) + + # Remove the norm and scale by the learned magnitude + out = (self.m / denom) * out + + return out diff --git a/llms/mlx_lm/tuner/lora.py b/llms/mlx_lm/tuner/lora.py index 19babb0e..c788cb73 100644 --- a/llms/mlx_lm/tuner/lora.py +++ b/llms/mlx_lm/tuner/lora.py @@ -10,7 +10,7 @@ from ..models.switch_layers import QuantizedSwitchLinear, SwitchLinear class LoRALinear(nn.Module): @staticmethod - def from_linear( + def from_base( linear: nn.Linear, r: int = 8, dropout: float = 0.0, @@ -31,7 +31,7 @@ class LoRALinear(nn.Module): lora_lin.linear = linear return lora_lin - def to_linear(self, de_quantize: bool = False): + def fuse(self, de_quantize: bool = False): linear = self.linear bias = "bias" in linear weight = linear.weight @@ -41,7 +41,7 @@ class LoRALinear(nn.Module): dtype = weight.dtype if is_quantized: - dtype = mx.float16 + dtype = linear.scales.dtype weight = mx.dequantize( weight, linear.scales, @@ -103,7 +103,7 @@ class LoRALinear(nn.Module): class LoRASwitchLinear(nn.Module): @staticmethod - def from_linear( + def from_base( linear: nn.Module, r: int = 8, dropout: float = 0.0, @@ -120,7 +120,7 @@ class LoRASwitchLinear(nn.Module): lora_lin.linear = linear return lora_lin - def to_linear(self, de_quantize: bool = False): + def fuse(self, de_quantize: bool = False): linear = self.linear bias = "bias" in linear weight = linear.weight @@ -191,3 +191,95 @@ class LoRASwitchLinear(nn.Module): z = z[..., None, :] @ self.lora_b[indices].swapaxes(-2, -1) return y + (self.scale * z).astype(x.dtype) + + +class LoRAEmbedding(nn.Module): + @staticmethod + def from_base( + embedding: nn.Embedding, + r: int = 8, + dropout: float = 0.0, + scale: float = 20.0, + ): + num_embeddings, dims = embedding.weight.shape + if isinstance(embedding, nn.QuantizedEmbedding): + dims *= 32 // embedding.bits + lora_embedding = LoRAEmbedding( + num_embeddings=num_embeddings, + dims=dims, + r=r, + dropout=dropout, + scale=scale, + ) + lora_embedding.embedding = embedding + return lora_embedding + + def fuse(self, de_quantize: bool = False): + embedding = self.embedding + weight = embedding.weight + is_quantized = isinstance(embedding, nn.QuantizedEmbedding) + + # Use the same type as the linear weight if not quantized + dtype = weight.dtype + + if is_quantized: + dtype = embedding.scales.dtype + weight = mx.dequantize( + weight, + embedding.scales, + embedding.biases, + embedding.group_size, + embedding.bits, + ) + num_embeddings, dims = weight.shape + fused_embedding = nn.Embedding(num_embeddings, dims) + + lora_a = (self.scale * self.lora_a).astype(dtype) + lora_b = self.lora_b.astype(dtype) + fused_embedding.weight = weight + lora_a @ lora_b + + if is_quantized and not de_quantize: + fused_embedding = nn.QuantizedEmbedding.from_embedding( + fused_embedding, + embedding.group_size, + embedding.bits, + ) + + return fused_embedding + + def __init__( + self, + num_embeddings: int, + dims: int, + r: int = 8, + dropout: float = 0.0, + scale: float = 20.0, + ): + super().__init__() + + # Regular embedding layer + self.embedding = nn.Embedding(num_embeddings, dims) + self.dropout = nn.Dropout(p=dropout) + + # Scale for low-rank update + self.scale = scale + + # Low rank lora weights + scale = 1 / math.sqrt(num_embeddings) + self.lora_a = mx.random.uniform( + low=-scale, + high=scale, + shape=(num_embeddings, r), + ) + self.lora_b = mx.zeros(shape=(r, dims)) + + def __call__(self, x): + y = self.embedding(x) + z = self.dropout(self.lora_a[x] @ self.lora_b) + out = y + (self.scale * z).astype(y.dtype) + return out + + def as_linear(self, x): + y = self.embedding.as_linear(x) + z = (self.dropout(x) @ self.lora_b.T) @ self.lora_a.T + return y + (self.scale * z).astype(x.dtype) diff --git a/llms/mlx_lm/tuner/utils.py b/llms/mlx_lm/tuner/utils.py index 2c97228d..b7f1f9de 100644 --- a/llms/mlx_lm/tuner/utils.py +++ b/llms/mlx_lm/tuner/utils.py @@ -10,8 +10,8 @@ import mlx.optimizers as opt from mlx.utils import tree_flatten, tree_unflatten from ..models.switch_layers import QuantizedSwitchLinear, SwitchLinear -from .dora import DoRALinear -from .lora import LoRALinear, LoRASwitchLinear +from .dora import DoRAEmbedding, DoRALinear +from .lora import LoRAEmbedding, LoRALinear, LoRASwitchLinear def build_schedule(schedule_config: Dict): @@ -71,12 +71,14 @@ def linear_to_lora_layers( if use_dora: raise ValueError(f"{type(layer).__name__} doesn't support DoRA yet.") LoRALayer = LoRASwitchLinear + elif isinstance(layer, (nn.Embedding, nn.QuantizedEmbedding)): + LoRALayer = DoRAEmbedding if use_dora else LoRAEmbedding else: raise ValueError( f"Can't convert layer of type {type(layer).__name__} to LoRA" ) - return LoRALayer.from_linear( + return LoRALayer.from_base( layer, r=config["rank"], scale=config["scale"], @@ -130,7 +132,12 @@ def linear_to_lora_layers( for l in model.layers[num_layers - num_lora_layers :]: lora_layers = [(k, to_lora(m)) for k, m in l.named_modules() if k in keys] - l.update_modules(tree_unflatten(lora_layers)) + if lora_layers: + l.update_modules(tree_unflatten(lora_layers)) + + lora_modules = [(k, to_lora(m)) for k, m in model.named_modules() if k in keys] + if lora_modules: + model.update_modules(tree_unflatten(lora_modules)) def apply_lora_layers(model: nn.Module, adapter_path: str) -> nn.Module: diff --git a/llms/tests/test_lora.py b/llms/tests/test_finetune.py similarity index 65% rename from llms/tests/test_lora.py rename to llms/tests/test_finetune.py index f37ae3c2..289b8cfb 100644 --- a/llms/tests/test_lora.py +++ b/llms/tests/test_finetune.py @@ -6,10 +6,13 @@ import unittest from io import StringIO from unittest.mock import MagicMock +import mlx.core as mx +import mlx.nn as nn import mlx.optimizers as opt from mlx.utils import tree_flatten from mlx_lm import lora, tuner -from mlx_lm.tuner.lora import LoRALinear +from mlx_lm.tuner.dora import DoRAEmbedding +from mlx_lm.tuner.lora import LoRAEmbedding, LoRALinear from mlx_lm.tuner.trainer import evaluate from mlx_lm.tuner.utils import build_schedule @@ -33,11 +36,12 @@ class TestLora(unittest.TestCase): num_attention_heads=4, rms_norm_eps=1e-5, vocab_size=10_000, + tie_word_embeddings=False, ) lora_layers = 4 - def check_config(params): + def check_config(params, expected_trainable_parameters=None): n_keys = 2 if "keys" in params: n_keys = len(params["keys"]) @@ -47,9 +51,11 @@ class TestLora(unittest.TestCase): trainable_params = sum( v.size for _, v in tree_flatten(model.trainable_parameters()) ) - self.assertEqual( - trainable_params, lora_layers * params["rank"] * 1024 * 2 * n_keys + + expected_trainable_parameters = expected_trainable_parameters or ( + lora_layers * params["rank"] * args.hidden_size * 2 * n_keys ) + self.assertEqual(trainable_params, expected_trainable_parameters) params = {"rank": 8, "alpha": 16, "dropout": 0.0, "scale": 10.0} check_config(params) @@ -60,6 +66,22 @@ class TestLora(unittest.TestCase): params["keys"] = ["self_attn.k_proj"] check_config(params) + params["keys"] = ["lm_head"] + check_config( + params, + expected_trainable_parameters=( + params["rank"] * (args.hidden_size + args.vocab_size) + ), + ) + + params["keys"] = ["model.embed_tokens"] + check_config( + params, + expected_trainable_parameters=( + params["rank"] * (args.hidden_size + args.vocab_size) + ), + ) + def test_gpt_neox(self): from mlx_lm.models import gpt_neox @@ -82,6 +104,66 @@ class TestLora(unittest.TestCase): model.freeze() tuner.utils.linear_to_lora_layers(model, num_lora_layers, params) + def test_lora_embedding(self): + num_embeddings = 256 + dims = 512 + tokens = mx.array([1, 2, 3]) + + embedding = nn.QuantizedEmbedding(num_embeddings, dims) + dequantized_weight = mx.dequantize( + embedding.weight, + embedding.scales, + embedding.biases, + embedding.group_size, + embedding.bits, + ) + lora_emb = LoRAEmbedding.from_base(embedding, r=8, dropout=0, scale=10) + new_embedding = lora_emb.fuse(de_quantize=True) + self.assertTrue(mx.array_equal(dequantized_weight, new_embedding.weight)) + self.assertTrue(mx.array_equal(embedding(tokens), lora_emb(tokens))) + + # as_linear + attn_output = mx.random.uniform(shape=(dims,)) + embedding_lin_out = lora_emb.as_linear(attn_output) + self.assertEqual(embedding_lin_out.shape, (num_embeddings,)) + self.assertTrue( + mx.array_equal(embedding_lin_out, embedding.as_linear(attn_output)) + ) + + # change the value of lora_b and the embeddings will no longer be equal + lora_emb.lora_b = mx.random.uniform(shape=lora_emb.lora_b.shape) + new_embedding = lora_emb.fuse(de_quantize=True) + self.assertFalse(mx.array_equal(dequantized_weight, new_embedding.weight)) + self.assertFalse(mx.array_equal(embedding(tokens), lora_emb(tokens))) + + +class TestDora(unittest.TestCase): + def test_dora_embedding(self): + num_embeddings = 256 + dims = 512 + tokens = mx.array([1, 2, 3]) + + embedding = nn.Embedding(num_embeddings, dims) + + dora_emb = DoRAEmbedding.from_base(embedding, r=8, dropout=0, scale=10) + new_embedding = dora_emb.fuse() + self.assertTrue(mx.array_equal(embedding.weight, new_embedding.weight)) + self.assertTrue(mx.array_equal(embedding(tokens), dora_emb(tokens))) + + # as_linear + attn_output = mx.random.uniform(shape=(dims,)) + embedding_lin_out = dora_emb.as_linear(attn_output) + self.assertEqual(embedding_lin_out.shape, (num_embeddings,)) + self.assertTrue( + mx.array_equal(embedding_lin_out, embedding.as_linear(attn_output)) + ) + + # change the value of lora_b and the embeddings will no longer be equal + dora_emb.lora_b = mx.random.uniform(shape=dora_emb.lora_b.shape) + new_embedding = dora_emb.fuse() + self.assertFalse(mx.array_equal(embedding.weight, new_embedding.weight)) + self.assertFalse(mx.array_equal(embedding(tokens), dora_emb(tokens))) + class TestScheduleConfig(unittest.TestCase): def test_join(self): From e196fa3208f032af1fa23374eab57169b4171c89 Mon Sep 17 00:00:00 2001 From: madroid Date: Sat, 17 Aug 2024 01:35:44 +0800 Subject: [PATCH 011/188] Whisper: Support command line (#746) * Whisper: Add CLI command * Whisper: Prevent precision loss when converting to words dictionary * Whisper: disable json ensure_ascii * Whisper: add cli setup config * Whisper: pre-commit * Whisper: Adjust the _ in the command line arguments to - * nits * version + readme * nit --------- Co-authored-by: Awni Hannun --- whisper/README.md | 16 ++ whisper/mlx_whisper/cli.py | 236 ++++++++++++++++++++++++++++ whisper/mlx_whisper/timing.py | 2 +- whisper/mlx_whisper/version.py | 2 +- whisper/mlx_whisper/writers.py | 272 +++++++++++++++++++++++++++++++++ whisper/setup.py | 5 + 6 files changed, 531 insertions(+), 2 deletions(-) create mode 100644 whisper/mlx_whisper/cli.py create mode 100644 whisper/mlx_whisper/writers.py diff --git a/whisper/README.md b/whisper/README.md index 2805e899..ac6e95f6 100644 --- a/whisper/README.md +++ b/whisper/README.md @@ -21,6 +21,22 @@ pip install mlx-whisper ### Run +#### CLI + +At its simplest: + +``` +mlx_whisper audio_file.mp3 +``` + +This will make a text file `audio_file.txt` with the results. + +Use `-f` to specify the output format and `--model` to specify the model. There +are many other supported command line options. To see them all, run +`mlx_whisper -h`. + +#### API + Transcribe audio with: ```python diff --git a/whisper/mlx_whisper/cli.py b/whisper/mlx_whisper/cli.py new file mode 100644 index 00000000..c2813338 --- /dev/null +++ b/whisper/mlx_whisper/cli.py @@ -0,0 +1,236 @@ +# Copyright © 2024 Apple Inc. + +import argparse +import os +import traceback +import warnings + +from .tokenizer import LANGUAGES, TO_LANGUAGE_CODE +from .transcribe import transcribe +from .writers import get_writer + + +def build_parser(): + def optional_int(string): + return None if string == "None" else int(string) + + def optional_float(string): + return None if string == "None" else float(string) + + def str2bool(string): + str2val = {"True": True, "False": False} + if string in str2val: + return str2val[string] + else: + raise ValueError(f"Expected one of {set(str2val.keys())}, got {string}") + + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) + parser.add_argument( + "audio", nargs="+", type=str, help="Audio file(s) to transcribe" + ) + parser.add_argument( + "--model", + default="mlx-community/whisper-tiny", + type=str, + help="The model directory or hugging face repo", + ) + parser.add_argument( + "--output-dir", + "-o", + type=str, + default=".", + help="Directory to save the outputs", + ) + parser.add_argument( + "--output-format", + "-f", + type=str, + default="txt", + choices=["txt", "vtt", "srt", "tsv", "json", "all"], + help="Format of the output file", + ) + parser.add_argument( + "--verbose", + type=str2bool, + default=True, + help="Whether to print out progress and debug messages", + ) + parser.add_argument( + "--task", + type=str, + default="transcribe", + choices=["transcribe", "translate"], + help="Perform speech recognition ('transcribe') or speech translation ('translate')", + ) + parser.add_argument( + "--language", + type=str, + default=None, + choices=sorted(LANGUAGES.keys()) + + sorted([k.title() for k in TO_LANGUAGE_CODE.keys()]), + help="Language spoken in the audio, specify None to auto-detect", + ) + parser.add_argument( + "--temperature", type=float, default=0, help="Temperature for sampling" + ) + parser.add_argument( + "--best-of", + type=optional_int, + default=5, + help="Number of candidates when sampling with non-zero temperature", + ) + parser.add_argument( + "--patience", + type=float, + default=None, + help="Optional patience value to use in beam decoding, as in https://arxiv.org/abs/2204.05424, the default (1.0) is equivalent to conventional beam search", + ) + parser.add_argument( + "--length-penalty", + type=float, + default=None, + help="Optional token length penalty coefficient (alpha) as in https://arxiv.org/abs/1609.08144, uses simple length normalization by default.", + ) + parser.add_argument( + "--suppress-tokens", + type=str, + default="-1", + help="Comma-separated list of token ids to suppress during sampling; '-1' will suppress most special characters except common punctuations", + ) + parser.add_argument( + "--initial-prompt", + type=str, + default=None, + help="Optional text to provide as a prompt for the first window.", + ) + parser.add_argument( + "--condition-on-previous-text", + type=str2bool, + default=True, + help="If True, provide the previous output of the model as a prompt for the next window; disabling may make the text inconsistent across windows, but the model becomes less prone to getting stuck in a failure loop", + ) + parser.add_argument( + "--fp16", + type=str2bool, + default=True, + help="Whether to perform inference in fp16", + ) + parser.add_argument( + "--compression-ratio-threshold", + type=optional_float, + default=2.4, + help="if the gzip compression ratio is higher than this value, treat the decoding as failed", + ) + parser.add_argument( + "--logprob-threshold", + type=optional_float, + default=-1.0, + help="If the average log probability is lower than this value, treat the decoding as failed", + ) + parser.add_argument( + "--no-speech-threshold", + type=optional_float, + default=0.6, + help="If the probability of the token is higher than this value the decoding has failed due to `logprob_threshold`, consider the segment as silence", + ) + parser.add_argument( + "--word-timestamps", + type=str2bool, + default=False, + help="Extract word-level timestamps and refine the results based on them", + ) + parser.add_argument( + "--prepend-punctuations", + type=str, + default="\"'“¿([{-", + help="If word-timestamps is True, merge these punctuation symbols with the next word", + ) + parser.add_argument( + "--append-punctuations", + type=str, + default="\"'.。,,!!??::”)]}、", + help="If word_timestamps is True, merge these punctuation symbols with the previous word", + ) + parser.add_argument( + "--highlight-words", + type=str2bool, + default=False, + help="(requires --word_timestamps True) underline each word as it is spoken in srt and vtt", + ) + parser.add_argument( + "--max-line-width", + type=int, + default=None, + help="(requires --word_timestamps True) the maximum number of characters in a line before breaking the line", + ) + parser.add_argument( + "--max-line-count", + type=int, + default=None, + help="(requires --word_timestamps True) the maximum number of lines in a segment", + ) + parser.add_argument( + "--max-words-per-line", + type=int, + default=None, + help="(requires --word_timestamps True, no effect with --max_line_width) the maximum number of words in a segment", + ) + parser.add_argument( + "--hallucination-silence-threshold", + type=optional_float, + help="(requires --word_timestamps True) skip silent periods longer than this threshold (in seconds) when a possible hallucination is detected", + ) + parser.add_argument( + "--clip-timestamps", + type=str, + default="0", + help="Comma-separated list start,end,start,end,... timestamps (in seconds) of clips to process, where the last end timestamp defaults to the end of the file", + ) + return parser + + +def main(): + parser = build_parser() + args = vars(parser.parse_args()) + if args["verbose"] is True: + print(f"Args: {args}") + + path_or_hf_repo: str = args.pop("model") + output_dir: str = args.pop("output_dir") + output_format: str = args.pop("output_format") + os.makedirs(output_dir, exist_ok=True) + + writer = get_writer(output_format, output_dir) + word_options = [ + "highlight_words", + "max_line_count", + "max_line_width", + "max_words_per_line", + ] + writer_args = {arg: args.pop(arg) for arg in word_options} + if not args["word_timestamps"]: + for k, v in writer_args.items(): + if v: + argop = k.replace("_", "-") + parser.error(f"--{argop} requires --word-timestamps True") + if writer_args["max_line_count"] and not writer_args["max_line_width"]: + warnings.warn("--max-line-count has no effect without --max-line-width") + if writer_args["max_words_per_line"] and writer_args["max_line_width"]: + warnings.warn("--max-words-per-line has no effect with --max-line-width") + for audio_path in args.pop("audio"): + try: + result = transcribe( + audio_path, + path_or_hf_repo=path_or_hf_repo, + **args, + ) + writer(result, audio_path, **writer_args) + except Exception as e: + traceback.print_exc() + print(f"Skipping {audio_path} due to {type(e).__name__}: {str(e)}") + + +if __name__ == "__main__": + main() diff --git a/whisper/mlx_whisper/timing.py b/whisper/mlx_whisper/timing.py index 13c36315..04915deb 100644 --- a/whisper/mlx_whisper/timing.py +++ b/whisper/mlx_whisper/timing.py @@ -276,7 +276,7 @@ def add_word_timestamps( word=timing.word, start=round(time_offset + timing.start, 2), end=round(time_offset + timing.end, 2), - probability=timing.probability, + probability=float(timing.probability), ) ) diff --git a/whisper/mlx_whisper/version.py b/whisper/mlx_whisper/version.py index ae3cfb71..67c7397c 100644 --- a/whisper/mlx_whisper/version.py +++ b/whisper/mlx_whisper/version.py @@ -1,3 +1,3 @@ # Copyright © 2023-2024 Apple Inc. -__version__ = "0.2.0" +__version__ = "0.3.0" diff --git a/whisper/mlx_whisper/writers.py b/whisper/mlx_whisper/writers.py new file mode 100644 index 00000000..464ead18 --- /dev/null +++ b/whisper/mlx_whisper/writers.py @@ -0,0 +1,272 @@ +# Copyright © 2024 Apple Inc. + +import json +import os +import re +import sys +import zlib +from typing import Callable, List, Optional, TextIO + + +def format_timestamp( + seconds: float, always_include_hours: bool = False, decimal_marker: str = "." +): + assert seconds >= 0, "non-negative timestamp expected" + milliseconds = round(seconds * 1000.0) + + hours = milliseconds // 3_600_000 + milliseconds -= hours * 3_600_000 + + minutes = milliseconds // 60_000 + milliseconds -= minutes * 60_000 + + seconds = milliseconds // 1_000 + milliseconds -= seconds * 1_000 + + hours_marker = f"{hours:02d}:" if always_include_hours or hours > 0 else "" + return ( + f"{hours_marker}{minutes:02d}:{seconds:02d}{decimal_marker}{milliseconds:03d}" + ) + + +def get_start(segments: List[dict]) -> Optional[float]: + return next( + (w["start"] for s in segments for w in s["words"]), + segments[0]["start"] if segments else None, + ) + + +class ResultWriter: + extension: str + + def __init__(self, output_dir: str): + self.output_dir = output_dir + + def __call__( + self, result: dict, audio_path: str, options: Optional[dict] = None, **kwargs + ): + audio_basename = os.path.basename(audio_path) + audio_basename = os.path.splitext(audio_basename)[0] + output_path = os.path.join( + self.output_dir, audio_basename + "." + self.extension + ) + + with open(output_path, "w", encoding="utf-8") as f: + self.write_result(result, file=f, options=options, **kwargs) + + def write_result( + self, result: dict, file: TextIO, options: Optional[dict] = None, **kwargs + ): + raise NotImplementedError + + +class WriteTXT(ResultWriter): + extension: str = "txt" + + def write_result( + self, result: dict, file: TextIO, options: Optional[dict] = None, **kwargs + ): + for segment in result["segments"]: + print(segment["text"].strip(), file=file, flush=True) + + +class SubtitlesWriter(ResultWriter): + always_include_hours: bool + decimal_marker: str + + def iterate_result( + self, + result: dict, + options: Optional[dict] = None, + *, + max_line_width: Optional[int] = None, + max_line_count: Optional[int] = None, + highlight_words: bool = False, + max_words_per_line: Optional[int] = None, + ): + options = options or {} + max_line_width = max_line_width or options.get("max_line_width") + max_line_count = max_line_count or options.get("max_line_count") + highlight_words = highlight_words or options.get("highlight_words", False) + max_words_per_line = max_words_per_line or options.get("max_words_per_line") + preserve_segments = max_line_count is None or max_line_width is None + max_line_width = max_line_width or 1000 + max_words_per_line = max_words_per_line or 1000 + + def iterate_subtitles(): + line_len = 0 + line_count = 1 + # the next subtitle to yield (a list of word timings with whitespace) + subtitle: List[dict] = [] + last: float = get_start(result["segments"]) or 0.0 + for segment in result["segments"]: + chunk_index = 0 + words_count = max_words_per_line + while chunk_index < len(segment["words"]): + remaining_words = len(segment["words"]) - chunk_index + if max_words_per_line > len(segment["words"]) - chunk_index: + words_count = remaining_words + for i, original_timing in enumerate( + segment["words"][chunk_index : chunk_index + words_count] + ): + timing = original_timing.copy() + long_pause = ( + not preserve_segments and timing["start"] - last > 3.0 + ) + has_room = line_len + len(timing["word"]) <= max_line_width + seg_break = i == 0 and len(subtitle) > 0 and preserve_segments + if ( + line_len > 0 + and has_room + and not long_pause + and not seg_break + ): + # line continuation + line_len += len(timing["word"]) + else: + # new line + timing["word"] = timing["word"].strip() + if ( + len(subtitle) > 0 + and max_line_count is not None + and (long_pause or line_count >= max_line_count) + or seg_break + ): + # subtitle break + yield subtitle + subtitle = [] + line_count = 1 + elif line_len > 0: + # line break + line_count += 1 + timing["word"] = "\n" + timing["word"] + line_len = len(timing["word"].strip()) + subtitle.append(timing) + last = timing["start"] + chunk_index += max_words_per_line + if len(subtitle) > 0: + yield subtitle + + if len(result["segments"]) > 0 and "words" in result["segments"][0]: + for subtitle in iterate_subtitles(): + subtitle_start = self.format_timestamp(subtitle[0]["start"]) + subtitle_end = self.format_timestamp(subtitle[-1]["end"]) + subtitle_text = "".join([word["word"] for word in subtitle]) + if highlight_words: + last = subtitle_start + all_words = [timing["word"] for timing in subtitle] + for i, this_word in enumerate(subtitle): + start = self.format_timestamp(this_word["start"]) + end = self.format_timestamp(this_word["end"]) + if last != start: + yield last, start, subtitle_text + + yield start, end, "".join( + [ + ( + re.sub(r"^(\s*)(.*)$", r"\1\2", word) + if j == i + else word + ) + for j, word in enumerate(all_words) + ] + ) + last = end + else: + yield subtitle_start, subtitle_end, subtitle_text + else: + for segment in result["segments"]: + segment_start = self.format_timestamp(segment["start"]) + segment_end = self.format_timestamp(segment["end"]) + segment_text = segment["text"].strip().replace("-->", "->") + yield segment_start, segment_end, segment_text + + def format_timestamp(self, seconds: float): + return format_timestamp( + seconds=seconds, + always_include_hours=self.always_include_hours, + decimal_marker=self.decimal_marker, + ) + + +class WriteVTT(SubtitlesWriter): + extension: str = "vtt" + always_include_hours: bool = False + decimal_marker: str = "." + + def write_result( + self, result: dict, file: TextIO, options: Optional[dict] = None, **kwargs + ): + print("WEBVTT\n", file=file) + for start, end, text in self.iterate_result(result, options, **kwargs): + print(f"{start} --> {end}\n{text}\n", file=file, flush=True) + + +class WriteSRT(SubtitlesWriter): + extension: str = "srt" + always_include_hours: bool = True + decimal_marker: str = "," + + def write_result( + self, result: dict, file: TextIO, options: Optional[dict] = None, **kwargs + ): + for i, (start, end, text) in enumerate( + self.iterate_result(result, options, **kwargs), start=1 + ): + print(f"{i}\n{start} --> {end}\n{text}\n", file=file, flush=True) + + +class WriteTSV(ResultWriter): + """ + Write a transcript to a file in TSV (tab-separated values) format containing lines like: + \t\t + + Using integer milliseconds as start and end times means there's no chance of interference from + an environment setting a language encoding that causes the decimal in a floating point number + to appear as a comma; also is faster and more efficient to parse & store, e.g., in C++. + """ + + extension: str = "tsv" + + def write_result( + self, result: dict, file: TextIO, options: Optional[dict] = None, **kwargs + ): + print("start", "end", "text", sep="\t", file=file) + for segment in result["segments"]: + print(round(1000 * segment["start"]), file=file, end="\t") + print(round(1000 * segment["end"]), file=file, end="\t") + print(segment["text"].strip().replace("\t", " "), file=file, flush=True) + + +class WriteJSON(ResultWriter): + extension: str = "json" + + def write_result( + self, result: dict, file: TextIO, options: Optional[dict] = None, **kwargs + ): + json.dump(result, file, ensure_ascii=False) + + +def get_writer( + output_format: str, output_dir: str +) -> Callable[[dict, TextIO, dict], None]: + writers = { + "txt": WriteTXT, + "vtt": WriteVTT, + "srt": WriteSRT, + "tsv": WriteTSV, + "json": WriteJSON, + } + + if output_format == "all": + all_writers = [writer(output_dir) for writer in writers.values()] + + def write_all( + result: dict, file: TextIO, options: Optional[dict] = None, **kwargs + ): + for writer in all_writers: + writer(result, file, options, **kwargs) + + return write_all + + return writers[output_format](output_dir) diff --git a/whisper/setup.py b/whisper/setup.py index c400a547..086f6471 100644 --- a/whisper/setup.py +++ b/whisper/setup.py @@ -29,4 +29,9 @@ setup( packages=find_namespace_packages(), include_package_data=True, python_requires=">=3.8", + entry_points={ + "console_scripts": [ + "mlx_whisper = mlx_whisper.cli:main", + ] + }, ) From 7be292c0c9bca9651fce1c93f4b20ee7fb7b1e82 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Fri, 16 Aug 2024 15:28:39 -0700 Subject: [PATCH 012/188] Handle longer prompt/generation (#931) * rebase * nits * nit * fix rotating cache with step prefill * update version --- llms/mlx_lm/generate.py | 8 +- llms/mlx_lm/models/base.py | 112 ++++++++++++++++++++++++-- llms/mlx_lm/models/cohere.py | 2 + llms/mlx_lm/models/dbrx.py | 2 + llms/mlx_lm/models/deepseek_v2.py | 2 + llms/mlx_lm/models/gemma.py | 2 + llms/mlx_lm/models/gemma2.py | 2 + llms/mlx_lm/models/gpt2.py | 2 + llms/mlx_lm/models/gpt_bigcode.py | 2 + llms/mlx_lm/models/gpt_neox.py | 2 + llms/mlx_lm/models/internlm2.py | 2 + llms/mlx_lm/models/llama.py | 2 + llms/mlx_lm/models/minicpm.py | 2 + llms/mlx_lm/models/mixtral.py | 2 + llms/mlx_lm/models/olmo.py | 2 + llms/mlx_lm/models/openelm.py | 2 + llms/mlx_lm/models/phi.py | 2 + llms/mlx_lm/models/phi3.py | 2 + llms/mlx_lm/models/phi3small.py | 2 + llms/mlx_lm/models/phixtral.py | 2 + llms/mlx_lm/models/plamo.py | 2 + llms/mlx_lm/models/qwen.py | 2 + llms/mlx_lm/models/qwen2.py | 2 + llms/mlx_lm/models/qwen2_moe.py | 2 + llms/mlx_lm/models/recurrent_gemma.py | 8 ++ llms/mlx_lm/models/stablelm.py | 2 + llms/mlx_lm/models/starcoder2.py | 2 + llms/mlx_lm/models/su_rope.py | 2 + llms/mlx_lm/models/switch_layers.py | 2 + llms/mlx_lm/utils.py | 26 +++++- llms/mlx_lm/version.py | 2 +- llms/tests/test_models.py | 60 +++++++++++++- 32 files changed, 255 insertions(+), 13 deletions(-) diff --git a/llms/mlx_lm/generate.py b/llms/mlx_lm/generate.py index c003940b..6707d25c 100644 --- a/llms/mlx_lm/generate.py +++ b/llms/mlx_lm/generate.py @@ -76,7 +76,12 @@ def setup_arg_parser(): type=int, default=None, help="Set the MLX cache limit in GB", - required=False, + ) + parser.add_argument( + "--max-kv-size", + type=int, + default=1024, + help="Set the maximum key-value cache size", ) return parser @@ -154,6 +159,7 @@ def main(): formatter=formatter, temp=args.temp, top_p=args.top_p, + max_kv_size=args.max_kv_size, ) diff --git a/llms/mlx_lm/models/base.py b/llms/mlx_lm/models/base.py index 3fe276d2..3e84554c 100644 --- a/llms/mlx_lm/models/base.py +++ b/llms/mlx_lm/models/base.py @@ -1,6 +1,8 @@ +# Copyright © 2023-2024 Apple Inc. + import inspect from dataclasses import dataclass -from typing import List, Optional +from typing import Any, List, Optional import mlx.core as mx import mlx.nn as nn @@ -44,6 +46,100 @@ class KVCache: self.values[..., prev : self.offset, :] = values return self.keys[..., : self.offset, :], self.values[..., : self.offset, :] + def state(self): + return self.keys, self.values + + +class RotatingKVCache: + + def __init__(self, head_dim, n_kv_heads, max_size, keep=0, step=256): + self.n_kv_heads = n_kv_heads + if isinstance(head_dim, int): + self.k_head_dim = self.v_head_dim = head_dim + elif isinstance(head_dim, tuple) and len(head_dim) == 2: + self.k_head_dim, self.v_head_dim = head_dim + else: + raise ValueError("head_dim must be an int or a tuple of two ints") + self.keep = keep + self.keys = None + self.values = None + self.offset = 0 + self.max_size = max_size + self.step = step + self._idx = 0 + + def _trim(self, trim_size, v, append=None): + to_cat = [] + if trim_size > 0: + to_cat = [v[..., : self.keep, :], v[..., trim_size + self.keep :, :]] + else: + to_cat = [v] + if append is not None: + to_cat.append(append) + return mx.concatenate(to_cat, axis=2) + + def update_and_fetch(self, keys, values): + prev = self.offset + B, _, S = keys.shape[:3] + + # Prefill mode + if S > 1: + if self.keys is None: + self.keys = keys + self.values = values + else: + # The largest size is self.max_size + S - 1 to ensure + # every token gets at least self.max_size context + trim_size = self.keys.shape[2] - self.max_size + 1 + self.keys = self._trim(trim_size, self.keys, keys) + self.values = self._trim(trim_size, self.values, values) + self.offset += S + self._idx = self.keys.shape[2] + return self.keys, self.values + + # Generation mode + # May not have hit the max size yet, so potentially + # keep growing the cache + if self.keys is None or ( + prev >= self.keys.shape[2] and self.keys.shape[2] < self.max_size + ): + new_size = min(self.step, self.max_size - prev) + k_shape = (B, self.n_kv_heads, new_size, self.k_head_dim) + v_shape = (B, self.n_kv_heads, new_size, self.v_head_dim) + new_k = mx.zeros(k_shape, keys.dtype) + new_v = mx.zeros(v_shape, values.dtype) + if self.keys is not None: + self.keys = mx.concatenate([self.keys, new_k], axis=2) + self.values = mx.concatenate([self.values, new_v], axis=2) + else: + self.keys, self.values = new_k, new_v + self._idx = prev + + # Trim if needed + trim_size = self.keys.shape[2] - self.max_size + if trim_size > 0: + self.keys = self._trim(trim_size, self.keys) + self.values = self._trim(trim_size, self.values) + self._idx = self.max_size + + # Rotate + if self._idx == self.max_size: + self._idx = self.keep + + # Assign + self.keys[..., self._idx : self._idx + 1, :] = keys + self.values[..., self._idx : self._idx + 1, :] = values + self.offset += 1 + self._idx += 1 + + # If the buffer is not full, slice off the end + if self.offset < self.max_size: + return self.keys[..., : self.offset, :], self.values[..., : self.offset, :] + return self.keys, self.values + + def state(self): + return self.keys, self.values + @dataclass class BaseModelArgs: @@ -65,13 +161,17 @@ def create_additive_causal_mask(N: int, offset: int = 0): return mask * -1e9 -def create_attention_mask(h: mx.array, cache: Optional[List[KVCache]] = None): +def create_attention_mask(h: mx.array, cache: Optional[Any] = None): T = h.shape[1] if T > 1: - # Input consists of multiple tokens, create a causal mask so that prior - # tokens do not give attention to later tokens. If a cache is in place - # (because e.g. prompt reuse), offset the mask accordingly. - offset = cache[0].offset if cache is not None and cache[0] is not None else 0 + if cache is not None and cache[0] is not None: + c = cache[0] + if isinstance(c, RotatingKVCache): + offset = min(c.max_size - 1, c.offset) + else: + offset = c.offset + else: + offset = 0 mask = create_additive_causal_mask(T, offset) mask = mask.astype(h.dtype) else: diff --git a/llms/mlx_lm/models/cohere.py b/llms/mlx_lm/models/cohere.py index 7dc2b9bf..cfcf2945 100644 --- a/llms/mlx_lm/models/cohere.py +++ b/llms/mlx_lm/models/cohere.py @@ -1,3 +1,5 @@ +# Copyright © 2023-2024 Apple Inc. + from dataclasses import dataclass from typing import Optional, Tuple diff --git a/llms/mlx_lm/models/dbrx.py b/llms/mlx_lm/models/dbrx.py index 7a2a7a7d..f0214549 100644 --- a/llms/mlx_lm/models/dbrx.py +++ b/llms/mlx_lm/models/dbrx.py @@ -1,3 +1,5 @@ +# Copyright © 2023-2024 Apple Inc. + from dataclasses import dataclass from typing import Optional, Tuple diff --git a/llms/mlx_lm/models/deepseek_v2.py b/llms/mlx_lm/models/deepseek_v2.py index bd743e53..f320b564 100644 --- a/llms/mlx_lm/models/deepseek_v2.py +++ b/llms/mlx_lm/models/deepseek_v2.py @@ -1,3 +1,5 @@ +# Copyright © 2023-2024 Apple Inc. + import math from dataclasses import dataclass from typing import Dict, Optional, Tuple diff --git a/llms/mlx_lm/models/gemma.py b/llms/mlx_lm/models/gemma.py index 323ebaa6..c6150284 100644 --- a/llms/mlx_lm/models/gemma.py +++ b/llms/mlx_lm/models/gemma.py @@ -1,3 +1,5 @@ +# Copyright © 2023-2024 Apple Inc. + from dataclasses import dataclass from typing import Optional, Tuple diff --git a/llms/mlx_lm/models/gemma2.py b/llms/mlx_lm/models/gemma2.py index d4bd8a5d..1d410a15 100644 --- a/llms/mlx_lm/models/gemma2.py +++ b/llms/mlx_lm/models/gemma2.py @@ -1,3 +1,5 @@ +# Copyright © 2023-2024 Apple Inc. + from dataclasses import dataclass from typing import Optional, Tuple diff --git a/llms/mlx_lm/models/gpt2.py b/llms/mlx_lm/models/gpt2.py index 81f71cac..8a770936 100644 --- a/llms/mlx_lm/models/gpt2.py +++ b/llms/mlx_lm/models/gpt2.py @@ -1,3 +1,5 @@ +# Copyright © 2023-2024 Apple Inc. + from dataclasses import dataclass from typing import Dict, Optional, Tuple, Union diff --git a/llms/mlx_lm/models/gpt_bigcode.py b/llms/mlx_lm/models/gpt_bigcode.py index a5336203..652eb9e4 100644 --- a/llms/mlx_lm/models/gpt_bigcode.py +++ b/llms/mlx_lm/models/gpt_bigcode.py @@ -1,3 +1,5 @@ +# Copyright © 2023-2024 Apple Inc. + from dataclasses import dataclass from typing import Dict, Optional, Tuple, Union diff --git a/llms/mlx_lm/models/gpt_neox.py b/llms/mlx_lm/models/gpt_neox.py index 1d2f74b7..c2aaa9ea 100644 --- a/llms/mlx_lm/models/gpt_neox.py +++ b/llms/mlx_lm/models/gpt_neox.py @@ -1,3 +1,5 @@ +# Copyright © 2023-2024 Apple Inc. + from dataclasses import dataclass from typing import Dict, Optional, Tuple, Union diff --git a/llms/mlx_lm/models/internlm2.py b/llms/mlx_lm/models/internlm2.py index 2ee2af2d..bcc0cf0c 100644 --- a/llms/mlx_lm/models/internlm2.py +++ b/llms/mlx_lm/models/internlm2.py @@ -1,3 +1,5 @@ +# Copyright © 2023-2024 Apple Inc. + from dataclasses import dataclass from typing import Dict, Optional, Tuple, Union diff --git a/llms/mlx_lm/models/llama.py b/llms/mlx_lm/models/llama.py index 2f323245..192e591f 100644 --- a/llms/mlx_lm/models/llama.py +++ b/llms/mlx_lm/models/llama.py @@ -1,3 +1,5 @@ +# Copyright © 2023-2024 Apple Inc. + from dataclasses import dataclass from typing import Dict, Optional, Tuple, Union diff --git a/llms/mlx_lm/models/minicpm.py b/llms/mlx_lm/models/minicpm.py index a3d01cbb..df0670be 100644 --- a/llms/mlx_lm/models/minicpm.py +++ b/llms/mlx_lm/models/minicpm.py @@ -1,3 +1,5 @@ +# Copyright © 2023-2024 Apple Inc. + from dataclasses import dataclass from typing import Dict, Optional, Tuple, Union diff --git a/llms/mlx_lm/models/mixtral.py b/llms/mlx_lm/models/mixtral.py index c7d8c5c5..2db57752 100644 --- a/llms/mlx_lm/models/mixtral.py +++ b/llms/mlx_lm/models/mixtral.py @@ -1,3 +1,5 @@ +# Copyright © 2023-2024 Apple Inc. + import math from dataclasses import dataclass from typing import Dict, Optional, Tuple, Union diff --git a/llms/mlx_lm/models/olmo.py b/llms/mlx_lm/models/olmo.py index 8a28ad74..59849c96 100644 --- a/llms/mlx_lm/models/olmo.py +++ b/llms/mlx_lm/models/olmo.py @@ -1,3 +1,5 @@ +# Copyright © 2023-2024 Apple Inc. + from dataclasses import dataclass from sys import exit from typing import Optional, Tuple diff --git a/llms/mlx_lm/models/openelm.py b/llms/mlx_lm/models/openelm.py index 3f0d2605..19d3c027 100644 --- a/llms/mlx_lm/models/openelm.py +++ b/llms/mlx_lm/models/openelm.py @@ -1,3 +1,5 @@ +# Copyright © 2023-2024 Apple Inc. + from dataclasses import dataclass from typing import Dict, List, Optional, Tuple, Union diff --git a/llms/mlx_lm/models/phi.py b/llms/mlx_lm/models/phi.py index 520ac1ad..fd3fd709 100644 --- a/llms/mlx_lm/models/phi.py +++ b/llms/mlx_lm/models/phi.py @@ -1,3 +1,5 @@ +# Copyright © 2023-2024 Apple Inc. + import math from dataclasses import dataclass from typing import Tuple diff --git a/llms/mlx_lm/models/phi3.py b/llms/mlx_lm/models/phi3.py index 2536aacb..f8facdb1 100644 --- a/llms/mlx_lm/models/phi3.py +++ b/llms/mlx_lm/models/phi3.py @@ -1,3 +1,5 @@ +# Copyright © 2023-2024 Apple Inc. + from dataclasses import dataclass from typing import Dict, List, Optional, Tuple, Union diff --git a/llms/mlx_lm/models/phi3small.py b/llms/mlx_lm/models/phi3small.py index de075652..665dbc73 100644 --- a/llms/mlx_lm/models/phi3small.py +++ b/llms/mlx_lm/models/phi3small.py @@ -1,3 +1,5 @@ +# Copyright © 2023-2024 Apple Inc. + import math from dataclasses import dataclass from functools import partial diff --git a/llms/mlx_lm/models/phixtral.py b/llms/mlx_lm/models/phixtral.py index f0aef0c9..bb67615d 100644 --- a/llms/mlx_lm/models/phixtral.py +++ b/llms/mlx_lm/models/phixtral.py @@ -1,3 +1,5 @@ +# Copyright © 2023-2024 Apple Inc. + import inspect import math from dataclasses import dataclass diff --git a/llms/mlx_lm/models/plamo.py b/llms/mlx_lm/models/plamo.py index 47a9ea4f..5d2b7586 100644 --- a/llms/mlx_lm/models/plamo.py +++ b/llms/mlx_lm/models/plamo.py @@ -1,3 +1,5 @@ +# Copyright © 2023-2024 Apple Inc. + from dataclasses import dataclass from typing import Any, List, Optional, Tuple, Union diff --git a/llms/mlx_lm/models/qwen.py b/llms/mlx_lm/models/qwen.py index 67816599..6d2c7bbf 100644 --- a/llms/mlx_lm/models/qwen.py +++ b/llms/mlx_lm/models/qwen.py @@ -1,3 +1,5 @@ +# Copyright © 2023-2024 Apple Inc. + from dataclasses import dataclass from typing import Tuple diff --git a/llms/mlx_lm/models/qwen2.py b/llms/mlx_lm/models/qwen2.py index cb8268aa..b3ce02a3 100644 --- a/llms/mlx_lm/models/qwen2.py +++ b/llms/mlx_lm/models/qwen2.py @@ -1,3 +1,5 @@ +# Copyright © 2023-2024 Apple Inc. + from dataclasses import dataclass from typing import Dict, Optional, Tuple, Union diff --git a/llms/mlx_lm/models/qwen2_moe.py b/llms/mlx_lm/models/qwen2_moe.py index 121ab813..ff7831f3 100644 --- a/llms/mlx_lm/models/qwen2_moe.py +++ b/llms/mlx_lm/models/qwen2_moe.py @@ -1,3 +1,5 @@ +# Copyright © 2023-2024 Apple Inc. + import math from dataclasses import dataclass from typing import Dict, Optional, Tuple, Union diff --git a/llms/mlx_lm/models/recurrent_gemma.py b/llms/mlx_lm/models/recurrent_gemma.py index 428431e3..34750ace 100644 --- a/llms/mlx_lm/models/recurrent_gemma.py +++ b/llms/mlx_lm/models/recurrent_gemma.py @@ -1,3 +1,5 @@ +# Copyright © 2023-2024 Apple Inc. + import math from dataclasses import dataclass from typing import List, Literal, Optional @@ -53,6 +55,9 @@ class RecurrentCache: def update(self, conv_state, recurrent_state): self._cache = (conv_state, recurrent_state) + def state(self): + return self._cache + class WindowKVCache: @@ -80,6 +85,9 @@ class WindowKVCache: self.values = _update(self.values, values) return self.keys, self.values + def state(self): + return self.keys, self.values + class RMSNorm(nn.Module): def __init__(self, dims: int, eps: float = 1e-5): diff --git a/llms/mlx_lm/models/stablelm.py b/llms/mlx_lm/models/stablelm.py index 9b4d043c..b340de28 100644 --- a/llms/mlx_lm/models/stablelm.py +++ b/llms/mlx_lm/models/stablelm.py @@ -1,3 +1,5 @@ +# Copyright © 2023-2024 Apple Inc. + import math from dataclasses import dataclass from typing import Tuple diff --git a/llms/mlx_lm/models/starcoder2.py b/llms/mlx_lm/models/starcoder2.py index a6eb5377..9cec0e39 100644 --- a/llms/mlx_lm/models/starcoder2.py +++ b/llms/mlx_lm/models/starcoder2.py @@ -1,3 +1,5 @@ +# Copyright © 2023-2024 Apple Inc. + from dataclasses import dataclass from typing import Optional, Tuple diff --git a/llms/mlx_lm/models/su_rope.py b/llms/mlx_lm/models/su_rope.py index cdf6ceaf..2ee20a63 100644 --- a/llms/mlx_lm/models/su_rope.py +++ b/llms/mlx_lm/models/su_rope.py @@ -1,3 +1,5 @@ +# Copyright © 2023-2024 Apple Inc. + import math from typing import List, Union diff --git a/llms/mlx_lm/models/switch_layers.py b/llms/mlx_lm/models/switch_layers.py index 00aa65d8..4a157473 100644 --- a/llms/mlx_lm/models/switch_layers.py +++ b/llms/mlx_lm/models/switch_layers.py @@ -1,3 +1,5 @@ +# Copyright © 2023-2024 Apple Inc. + import math import mlx.core as mx diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index e7a9dba8..44196766 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -19,7 +19,7 @@ from mlx.utils import tree_flatten from transformers import PreTrainedTokenizer # Local imports -from .models.base import KVCache +from .models.base import KVCache, RotatingKVCache from .sample_utils import categorical_sampling, min_p_sampling, top_p_sampling from .tokenizer_utils import TokenizerWrapper, load_tokenizer from .tuner.utils import apply_lora_layers @@ -136,6 +136,8 @@ def generate_step( min_p: float = 0.0, min_tokens_to_keep: int = 1, logit_bias: Optional[Dict[int, float]] = None, + prefill_step_size: int = 512, + max_kv_size: Optional[int] = None, ) -> Generator[Tuple[mx.array, mx.array], None, None]: """ A generator producing token ids based on the given prompt from the model. @@ -156,6 +158,9 @@ def generate_step( min_tokens_to_keep (int, optional): Minimum number of tokens that cannot be filtered by min_p sampling. logit_bias (dictionary, optional): Additive logit bias. + prefill_step_size (int): Step size for processing the prompt. + max_kv_size (int, optional): Maximum size of the key-value cache. Old + entries (except the first 4 tokens) will be overwritten. Yields: Generator[Tuple[mx.array, mx.array], None, None]: A generator producing @@ -197,7 +202,13 @@ def generate_step( if isinstance(model.n_kv_heads, int) else model.n_kv_heads ) - cache = [KVCache(model.head_dim, n) for n in kv_heads] + if max_kv_size is not None: + cache = [ + RotatingKVCache(model.head_dim, n, max_size=max_kv_size, keep=4) + for n in kv_heads + ] + else: + cache = [KVCache(model.head_dim, n) for n in kv_heads] repetition_context = prompt.tolist() @@ -223,6 +234,11 @@ def generate_step( repetition_context = repetition_context[-repetition_context_size:] return y, logprobs.squeeze(0) + while y.size > prefill_step_size: + model(y[:prefill_step_size][None], cache=cache) + mx.eval([c.state for c in cache]) + y = y[prefill_step_size:] + y, logprobs = _step(y) mx.async_eval(y) @@ -343,8 +359,10 @@ def generate( return prompt_tps = prompt_tokens.size / prompt_time gen_tps = (token_count - 1) / gen_time - print(f"Prompt: {prompt_tps:.3f} tokens-per-sec") - print(f"Generation: {gen_tps:.3f} tokens-per-sec") + print(f"Prompt: {prompt_tokens.size} tokens, {prompt_tps:.3f} tokens-per-sec") + print(f"Generation: {token_count} tokens, {gen_tps:.3f} tokens-per-sec") + peak_mem = mx.metal.get_peak_memory() / 2**30 + print(f"Peak memory: {peak_mem:.3f} GB") return detokenizer.text diff --git a/llms/mlx_lm/version.py b/llms/mlx_lm/version.py index 40b73ede..f73aaa0a 100644 --- a/llms/mlx_lm/version.py +++ b/llms/mlx_lm/version.py @@ -1,3 +1,3 @@ # Copyright © 2023-2024 Apple Inc. -__version__ = "0.16.0" +__version__ = "0.17.0" diff --git a/llms/tests/test_models.py b/llms/tests/test_models.py index 19341981..fcf1dc33 100644 --- a/llms/tests/test_models.py +++ b/llms/tests/test_models.py @@ -4,7 +4,7 @@ import unittest import mlx.core as mx from mlx.utils import tree_map -from mlx_lm.models.base import KVCache +from mlx_lm.models.base import KVCache, RotatingKVCache class TestModels(unittest.TestCase): @@ -29,6 +29,64 @@ class TestModels(unittest.TestCase): self.assertTrue(mx.array_equal(v_up, expected)) self.assertEqual(cache.offset, cache.step + 1) + def test_rotating_kv_cache(self): + b, h, d = 1, 2, 32 + cache = RotatingKVCache(d, h, max_size=8, step=4) + + k = mx.random.uniform(shape=(b, h, 2, d)) + v = mx.random.uniform(shape=(b, h, 2, d)) + + k_up, v_up = cache.update_and_fetch(k, v) + self.assertTrue(mx.array_equal(k_up, k)) + self.assertTrue(mx.array_equal(v_up, v)) + self.assertEqual(cache.offset, 2) + + k = mx.random.uniform(shape=(b, h, 5, d)) + v = mx.random.uniform(shape=(b, h, 5, d)) + k_up, v_up = cache.update_and_fetch(k, v) + self.assertTrue(mx.array_equal(k_up[..., 2:, :], k)) + self.assertTrue(mx.array_equal(v_up[..., 2:, :], v)) + + k = mx.random.uniform(shape=(b, h, 4, d)) + v = mx.random.uniform(shape=(b, h, 4, d)) + k_up, v_up = cache.update_and_fetch(k, v) + self.assertTrue(mx.array_equal(k_up[..., -4:, :], k)) + self.assertTrue(mx.array_equal(v_up[..., -4:, :], v)) + + idx = 0 + for _ in range(10): + k = mx.random.uniform(shape=(b, h, 1, d)) + v = mx.random.uniform(shape=(b, h, 1, d)) + k_up, v_up = cache.update_and_fetch(k, v) + self.assertTrue(mx.array_equal(k_up[..., idx : idx + 1, :], k)) + self.assertTrue(mx.array_equal(v_up[..., idx : idx + 1, :], v)) + idx += 1 + idx %= 8 + + # Try with nonzero keep + cache = RotatingKVCache(d, h, max_size=8, step=4, keep=2) + + # Check a large update + k = mx.random.uniform(shape=(b, h, 20, d)) + v = mx.random.uniform(shape=(b, h, 20, d)) + k_up, v_up = cache.update_and_fetch(k, v) + self.assertTrue(mx.array_equal(k_up, k)) + self.assertTrue(mx.array_equal(v_up, v)) + + # A bunch of small updates + self.assertEqual(cache.offset, 20) + idx = 2 + for i in range(10): + k = mx.random.uniform(shape=(b, h, 1, d)) + v = mx.random.uniform(shape=(b, h, 1, d)) + k_up, v_up = cache.update_and_fetch(k, v) + self.assertTrue(mx.array_equal(k_up[..., idx : idx + 1, :], k)) + self.assertTrue(mx.array_equal(v_up[..., idx : idx + 1, :], v)) + self.assertEqual(cache.offset, 21 + i) + idx += 1 + if idx >= 8: + idx = 2 + def model_test_runner(self, model, model_type, vocab_size, num_layers): self.assertEqual(len(model.layers), num_layers) From 0164d2058bf43197d4fe04bf26afa22cda78b7f0 Mon Sep 17 00:00:00 2001 From: L Date: Sat, 17 Aug 2024 23:18:09 +0900 Subject: [PATCH 013/188] feat: DeepSeek MoE v1 (#942) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: deepseek v1 DeepSeek is still releasing models on the DeepSeek V1 architecture. ```sh mlx_lm.convert --hf-path deepseek-ai/DeepSeek-Prover-V1.5-RL --mlx-path DeepSeek-Prover-V1.5-RL-8bit --q-bits 8 -q mlx_lm.generate --model DeepSeek-Prover-V1.5-RL-8bit --ignore-chat-template --max-tokens 512 --prompt 'import Mathlib import Aesop set_option maxHeartbeats 0 open BigOperators Real Nat Topology Rat /-- The second and fourth terms of a geometric sequence are $2$ and $6$. Which of the following is a possible first term? Show that it is $\frac{2\sqrt{3}}{3}$.-/ theorem amc12b_2003_p6 (a r : ℝ) (u : ℕ → ℝ) (h₀ : ∀ k, u k = a * r ^ k) (h₁ : u 1 = 2) (h₂ : u 3 = 6) : u 0 = 2 / Real.sqrt 3 ∨ u 0 = -(2 / Real.sqrt 3) := by' ``` * nits * nits * nits --------- Co-authored-by: Awni Hannun --- llms/mlx_lm/models/deepseek.py | 266 +++++++++++++++++++++++++++++++++ llms/mlx_lm/tuner/utils.py | 1 + 2 files changed, 267 insertions(+) create mode 100644 llms/mlx_lm/models/deepseek.py diff --git a/llms/mlx_lm/models/deepseek.py b/llms/mlx_lm/models/deepseek.py new file mode 100644 index 00000000..dcfa331c --- /dev/null +++ b/llms/mlx_lm/models/deepseek.py @@ -0,0 +1,266 @@ +from dataclasses import dataclass +from typing import Dict, Optional + +import mlx.core as mx +import mlx.nn as nn + +from .base import BaseModelArgs, KVCache, create_attention_mask +from .switch_layers import SwitchGLU + + +@dataclass +class ModelArgs(BaseModelArgs): + model_type: str = "deepseek" + vocab_size: int = 102400 + hidden_size: int = 4096 + intermediate_size: int = 11008 + moe_intermediate_size: int = 1407 + num_hidden_layers: int = 30 + num_attention_heads: int = 32 + num_key_value_heads: int = 32 + n_shared_experts: Optional[int] = None + n_routed_experts: Optional[int] = None + num_experts_per_tok: Optional[int] = None + moe_layer_freq: int = 1 + first_k_dense_replace: int = 0 + max_position_embeddings: int = 2048 + rms_norm_eps: float = 1e-6 + rope_theta: float = 10000.0 + rope_scaling: Optional[Dict] = None + attention_bias: bool = False + + +class DeepseekAttention(nn.Module): + def __init__(self, config: ModelArgs): + super().__init__() + self.config = config + self.hidden_size = config.hidden_size + self.num_attention_heads = config.num_attention_heads + self.num_kv_heads = config.num_key_value_heads + self.head_dim = config.hidden_size // config.num_attention_heads + self.scale = self.head_dim**-0.5 + + attention_bias = getattr(config, "attention_bias", False) + + self.q_proj = nn.Linear( + self.hidden_size, + config.num_attention_heads * self.head_dim, + bias=attention_bias, + ) + self.k_proj = nn.Linear( + self.hidden_size, + config.num_key_value_heads * self.head_dim, + bias=attention_bias, + ) + self.v_proj = nn.Linear( + self.hidden_size, + config.num_key_value_heads * self.head_dim, + bias=attention_bias, + ) + self.o_proj = nn.Linear( + self.hidden_size, + config.num_attention_heads * self.head_dim, + bias=attention_bias, + ) + + rope_scale = 1.0 + if config.rope_scaling and config.rope_scaling["type"] == "linear": + assert isinstance(config.rope_scaling["factor"], float) + rope_scale = 1 / config.rope_scaling["factor"] + self.rope = nn.RoPE( + self.head_dim, + base=config.rope_theta, + scale=rope_scale, + ) + + def __call__( + self, + x: mx.array, + mask: Optional[mx.array] = None, + cache: Optional[KVCache] = None, + ) -> mx.array: + B, L, _ = x.shape + + queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x) + + queries = queries.reshape(B, L, self.num_attention_heads, -1).transpose( + 0, 2, 1, 3 + ) + keys = keys.reshape(B, L, self.num_kv_heads, -1).transpose(0, 2, 1, 3) + values = values.reshape(B, L, self.num_kv_heads, -1).transpose(0, 2, 1, 3) + + if cache is not None: + queries = self.rope(queries, offset=cache.offset) + keys = self.rope(keys, offset=cache.offset) + keys, values = cache.update_and_fetch(keys, values) + else: + queries = self.rope(queries) + keys = self.rope(keys) + + output = mx.fast.scaled_dot_product_attention( + queries, keys, values, scale=self.scale, mask=mask + ) + output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) + return self.o_proj(output) + + +class DeepseekMLP(nn.Module): + def __init__( + self, + config: ModelArgs, + hidden_size: int | None = None, + intermediate_size: int | None = None, + ): + super().__init__() + self.config = config + self.hidden_size = hidden_size or config.hidden_size + self.intermediate_size = intermediate_size or config.intermediate_size + self.gate_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False) + self.up_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False) + self.down_proj = nn.Linear(self.intermediate_size, self.hidden_size, bias=False) + self.act_fn = nn.silu + + def __call__(self, x: mx.array) -> mx.array: + return self.down_proj(self.act_fn(self.gate_proj(x)) * self.up_proj(x)) + + +class MoEGate(nn.Module): + def __init__(self, config: ModelArgs): + super().__init__() + self.config = config + self.top_k = config.num_experts_per_tok + self.n_routed_experts = config.n_routed_experts + self.weight = mx.zeros((self.n_routed_experts, config.hidden_size)) + + def __call__(self, x): + gates = x @ self.weight.T + scores = mx.softmax(gates, axis=-1, precise=True) + k = self.top_k + inds = mx.stop_gradient(mx.argpartition(-scores, kth=k - 1, axis=-1)[..., :k]) + scores = mx.take_along_axis(scores, inds, axis=-1) + return inds, scores + + +class DeepseekMoE(nn.Module): + def __init__(self, config: ModelArgs): + super().__init__() + self.config = config + self.switch_mlp = SwitchGLU( + config.hidden_size, config.moe_intermediate_size, config.n_routed_experts + ) + + self.gate = MoEGate(config) + if config.n_shared_experts is not None: + intermediate_size = config.moe_intermediate_size * config.n_shared_experts + self.shared_experts = DeepseekMLP( + config=config, intermediate_size=intermediate_size + ) + + def __call__(self, x): + inds, scores = self.gate(x) + y = self.switch_mlp(x, inds) + y = (y * scores[..., None]).sum(axis=-2) + if self.config.n_shared_experts is not None: + y = y + self.shared_experts(x) + + return y + + +class DeepseekDecoderLayer(nn.Module): + def __init__(self, config: ModelArgs, layer_idx: int): + super().__init__() + self.self_attn = DeepseekAttention(config) + self.mlp = ( + DeepseekMoE(config) + if ( + config.n_routed_experts is not None + and layer_idx >= config.first_k_dense_replace + and layer_idx % config.moe_layer_freq == 0 + ) + else DeepseekMLP(config) + ) + self.input_layernorm = nn.RMSNorm(config.hidden_size, eps=config.rms_norm_eps) + self.post_attention_layernorm = nn.RMSNorm( + config.hidden_size, eps=config.rms_norm_eps + ) + + def __call__( + self, + x: mx.array, + mask: Optional[mx.array] = None, + cache: Optional[KVCache] = None, + ) -> mx.array: + r = self.self_attn(self.input_layernorm(x), mask, cache) + h = x + r + r = self.mlp(self.post_attention_layernorm(h)) + out = h + r + return out + + +class DeepseekModel(nn.Module): + def __init__(self, config: ModelArgs): + super().__init__() + self.config = config + self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size) + self.layers = [ + DeepseekDecoderLayer(config, idx) for idx in range(config.num_hidden_layers) + ] + self.norm = nn.RMSNorm(config.hidden_size, eps=config.rms_norm_eps) + + def __call__( + self, + x: mx.array, + cache: Optional[KVCache] = None, + ) -> mx.array: + h = self.embed_tokens(x) + mask = create_attention_mask(h, cache) + + if cache is None: + cache = [None] * len(self.layers) + + for layer, c in zip(self.layers, cache): + h = layer(h, mask, c) + + return self.norm(h) + + +class Model(nn.Module): + def __init__(self, config: ModelArgs): + super().__init__() + self.args = config + self.model_type = config.model_type + self.model = DeepseekModel(config) + self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False) + + def __call__( + self, + inputs: mx.array, + cache: Optional[KVCache] = None, + ): + out = self.model(inputs, cache) + return self.lm_head(out) + + def sanitize(self, weights): + for l in range(self.args.num_hidden_layers): + prefix = f"model.layers.{l}" + for m in ["gate_proj", "down_proj", "up_proj"]: + for k in ["weight", "scales", "biases"]: + if f"{prefix}.mlp.experts.0.{m}.{k}" in weights: + to_join = [ + weights.pop(f"{prefix}.mlp.experts.{e}.{m}.{k}") + for e in range(self.args.n_routed_experts) + ] + weights[f"{prefix}.mlp.switch_mlp.{m}.{k}"] = mx.stack(to_join) + return weights + + @property + def layers(self): + return self.model.layers + + @property + def head_dim(self): + return self.args.hidden_size // self.args.num_attention_heads + + @property + def n_kv_heads(self): + return self.args.num_key_value_heads diff --git a/llms/mlx_lm/tuner/utils.py b/llms/mlx_lm/tuner/utils.py index b7f1f9de..c6af9730 100644 --- a/llms/mlx_lm/tuner/utils.py +++ b/llms/mlx_lm/tuner/utils.py @@ -101,6 +101,7 @@ def linear_to_lora_layers( "starcoder2", "cohere", "minicpm", + "deepseek", ]: keys = set(["self_attn.q_proj", "self_attn.v_proj"]) if model.model_type == "mixtral": From 58591a1b4175265e7b316de5b4d0365be658c6b6 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Thu, 22 Aug 2024 10:41:21 -0700 Subject: [PATCH 014/188] fine tune deepseek (#932) --- llms/mlx_lm/tuner/utils.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/llms/mlx_lm/tuner/utils.py b/llms/mlx_lm/tuner/utils.py index c6af9730..9f18c2c0 100644 --- a/llms/mlx_lm/tuner/utils.py +++ b/llms/mlx_lm/tuner/utils.py @@ -128,6 +128,16 @@ def linear_to_lora_layers( keys = set(["norm_attn_norm.attn.Wqkv", "ffn.router.layer"]) elif model.model_type == "internlm2": keys = set(["attention.wqkv", "attention.wo"]) + elif model.model_type == "deepseek_v2": + keys = set( + [ + "self_attn.q_proj", + "self_attn.q_a_proj", + "self_attn.q_b_proj", + "self_attn.kv_a_proj_with_mqa", + "self_attn.kv_b_proj", + ] + ) else: raise ValueError(f"Lora does not support {model.model_type}") From 6731254e761f69d3c0925fd682a4d988fbf3fe7a Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Fri, 23 Aug 2024 13:18:51 -0700 Subject: [PATCH 015/188] Use fast rope (#945) * use fast rope * fix llama * use fast rope for llama3.1 * requires unreleased mlx * fix su * fix deepseek v2 * only one of base or freqs * nit * fix * hard code freqs --- llms/mlx_lm/models/deepseek_v2.py | 91 +++++++---------------- llms/mlx_lm/models/llama.py | 43 ++++------- llms/mlx_lm/models/phi3.py | 4 +- llms/mlx_lm/models/su_rope.py | 51 ++++--------- llms/mlx_lm/requirements.txt | 2 +- llms/mlx_lm/version.py | 2 +- stable_diffusion/stable_diffusion/unet.py | 9 +-- 7 files changed, 65 insertions(+), 137 deletions(-) diff --git a/llms/mlx_lm/models/deepseek_v2.py b/llms/mlx_lm/models/deepseek_v2.py index f320b564..602a9710 100644 --- a/llms/mlx_lm/models/deepseek_v2.py +++ b/llms/mlx_lm/models/deepseek_v2.py @@ -68,13 +68,12 @@ def yarn_get_mscale(scale=1, mscale=1): return 0.1 * mscale * math.log(scale) + 1.0 -def yarn_linear_ramp_mask(min, max, dim): - if min == max: - max += 0.001 # Prevent singularity +def yarn_linear_ramp_mask(min_val, max_val, dim): + if min_val == max_val: + max_val += 0.001 # Prevent singularity - linear_func = (mx.arange(dim, dtype=mx.float32) - min) / (max - min) - ramp_func = mx.clip(linear_func, 0, 1) - return ramp_func + linear_func = (mx.arange(dim, dtype=mx.float32) - min_val) / (max_val - min_val) + return mx.clip(linear_func, 0, 1) class DeepseekV2YarnRotaryEmbedding(nn.Module): @@ -91,72 +90,36 @@ class DeepseekV2YarnRotaryEmbedding(nn.Module): mscale_all_dim=0, ): super().__init__() - self.dim = dim - self.max_position_embeddings = max_position_embeddings - self.base = base - self.scaling_factor = scaling_factor - self.original_max_position_embeddings = original_max_position_embeddings - self.beta_fast = beta_fast - self.beta_slow = beta_slow - self.mscale = mscale - self.mscale_all_dim = mscale_all_dim - - self.max_seq_len_cached = None - self._cos_cached = None - self._sin_cached = None - self._inv_freq = None - self.set_cos_sin_cache(max_position_embeddings) - - def set_cos_sin_cache(self, seq_len): - self.max_seq_len_cached = seq_len - dim = self.dim - freq_extra = 1.0 / (self.base ** (mx.arange(0, dim, 2, dtype=mx.float32) / dim)) - freq_inter = 1.0 / ( - self.scaling_factor - * self.base ** (mx.arange(0, dim, 2, dtype=mx.float32) / dim) + self.mscale = yarn_get_mscale(scaling_factor, mscale) / yarn_get_mscale( + scaling_factor, mscale_all_dim + ) + freq_extra = base ** (mx.arange(0, dim, 2, dtype=mx.float32) / dim) + freq_inter = scaling_factor * base ** ( + mx.arange(0, dim, 2, dtype=mx.float32) / dim ) - low, high = yarn_find_correction_range( - self.beta_fast, - self.beta_slow, + beta_fast, + beta_slow, dim, - self.base, - self.original_max_position_embeddings, + base, + original_max_position_embeddings, ) - inv_freq_mask = 1.0 - yarn_linear_ramp_mask(low, high, dim // 2) - inv_freq = freq_inter * (1 - inv_freq_mask) + freq_extra * inv_freq_mask - self._inv_freq = inv_freq - - t = mx.arange(seq_len, dtype=mx.float32) - freqs = mx.outer(t, inv_freq) - - mscale = yarn_get_mscale(self.scaling_factor, self.mscale) / yarn_get_mscale( - self.scaling_factor, self.mscale_all_dim + freq_mask = 1.0 - yarn_linear_ramp_mask(low, high, dim // 2) + self._freqs = (freq_inter * freq_extra) / ( + freq_inter * freq_mask + freq_extra * (1 - freq_mask) ) - self._cos_cached = mx.cos(freqs) * mscale - self._sin_cached = mx.sin(freqs) * mscale - - def apply_rotary_pos_emb(self, x, cos, sin): - x1 = x[..., ::2] - x2 = x[..., 1::2] - rx1 = x1 * cos - x2 * sin - rx2 = x1 * sin + x2 * cos - return mx.concatenate([rx1, rx2], axis=-1) - def __call__(self, x, offset=0): - seq_len = offset + x.shape[2] - if self.max_seq_len_cached is None or seq_len > self.max_seq_len_cached: - self.set_cos_sin_cache(seq_len=seq_len) - - if self._cos_cached.dtype != x.dtype: - self._cos_cached = self._cos_cached.astype(x.dtype) - self._sin_cached = self._sin_cached.astype(x.dtype) - - return self.apply_rotary_pos_emb( + if self.mscale != 1.0: + x = self.mscale * x + return mx.fast.rope( x, - self._cos_cached[offset:seq_len], - self._sin_cached[offset:seq_len], + x.shape[-1], + traditional=True, + base=None, + scale=1.0, + offset=offset, + freqs=self._freqs, ) diff --git a/llms/mlx_lm/models/llama.py b/llms/mlx_lm/models/llama.py index 192e591f..c4a947a5 100644 --- a/llms/mlx_lm/models/llama.py +++ b/llms/mlx_lm/models/llama.py @@ -65,19 +65,16 @@ class DynamicNTKScalingRoPE(nn.Module): self.dims = dims self.max_position_embeddings = max_position_embeddings self.traditional = traditional - self.original_base = base self.scale = scale self.rope_type = rope_type self.rope_scaling = rope_scaling - self.base = self.compute_base_freq() + self.base = base + self.compute_freqs() - def compute_base_freq(self): - if self.rope_type == "llama3": - return self.compute_llama3_base_freq() - return self.original_base - - # source: https://github.com/huggingface/transformers/blob/d5a99dfcee6e94065cb7c83cc8ab6fc5daa0cc4e/src/transformers/modeling_rope_utils.py#L318 - def compute_llama3_base_freq(self): + def compute_freqs(self): + if self.rope_type != "llama3": + self._freqs = None + return factor = self.rope_scaling["factor"] low_freq_factor = self.rope_scaling.get("low_freq_factor", 1.0) high_freq_factor = self.rope_scaling.get("high_freq_factor", 4.0) @@ -89,19 +86,17 @@ class DynamicNTKScalingRoPE(nn.Module): low_freq_wavelen = old_context_len / low_freq_factor high_freq_wavelen = old_context_len / high_freq_factor - freqs = self.original_base ** (mx.arange(0, self.dims, 2) / self.dims) + freqs = self.base ** (mx.arange(0, self.dims, 2) / self.dims) wavelens = 2 * mx.pi * freqs - new_base_freqs = [] - smooths = (wavelens - high_freq_wavelen) / ( - low_freq_wavelen - high_freq_wavelen + freqs = mx.where(wavelens > low_freq_wavelen, freqs * factor, freqs) + is_medium_freq = (wavelens > high_freq_wavelen) & (wavelens < low_freq_wavelen) + smooth_factors = (old_context_len / wavelens - low_freq_factor) / ( + high_freq_factor - low_freq_factor ) - new_base_freqs = freqs * (1 - smooths) * factor + smooths - new_base_freqs = mx.where(wavelens < high_freq_wavelen, freqs, new_base_freqs) - new_base_freqs = mx.where( - wavelens > low_freq_wavelen, freqs * factor, new_base_freqs - ) - return new_base_freqs.mean().item() + smooth_freqs = freqs / ((1 - smooth_factors) / factor + smooth_factors) + self._freqs = mx.where(is_medium_freq, smooth_freqs, freqs) + self.base = None def extra_repr(self): return ( @@ -111,20 +106,14 @@ class DynamicNTKScalingRoPE(nn.Module): ) def __call__(self, x, offset: int = 0): - seq_len = x.shape[1] + offset - base = self.base - if self.max_position_embeddings and seq_len > self.max_position_embeddings: - base *= ( - (self.scale * seq_len / self.max_position_embeddings) - (self.scale - 1) - ) ** (self.dims / (self.dims - 2)) - return mx.fast.rope( x, self.dims, traditional=self.traditional, - base=base, + base=self.base, scale=self.scale, offset=offset, + freqs=self._freqs, ) diff --git a/llms/mlx_lm/models/phi3.py b/llms/mlx_lm/models/phi3.py index f8facdb1..112ade7d 100644 --- a/llms/mlx_lm/models/phi3.py +++ b/llms/mlx_lm/models/phi3.py @@ -59,19 +59,17 @@ class Attention(nn.Module): self.qkv_proj = nn.Linear(dim, op_size, bias=False) self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=False) - rope_scale = 1.0 if args.rope_scaling and args.rope_scaling["type"] in ["longrope", "su"]: self.rope = SuScaledRotaryEmbedding( head_dim, - traditional=False, base=args.rope_theta, - scale=rope_scale, max_position_embeddings=args.max_position_embeddings, original_max_position_embeddings=args.original_max_position_embeddings, short_factor=args.rope_scaling["short_factor"], long_factor=args.rope_scaling["long_factor"], ) else: + rope_scale = 1.0 if args.rope_scaling and args.rope_scaling["type"] == "linear": assert isinstance(args.rope_scaling["factor"], float) rope_scale = 1 / args.rope_scaling["factor"] diff --git a/llms/mlx_lm/models/su_rope.py b/llms/mlx_lm/models/su_rope.py index 2ee20a63..f96b9957 100644 --- a/llms/mlx_lm/models/su_rope.py +++ b/llms/mlx_lm/models/su_rope.py @@ -4,15 +4,14 @@ import math from typing import List, Union import mlx.core as mx +import mlx.nn as nn -class SuScaledRotaryEmbedding: +class SuScaledRotaryEmbedding(nn.Module): def __init__( self, dims: int, - traditional: bool = False, base: float = 10000.0, - scale: float = 1.0, max_position_embeddings: int = 131072, original_max_position_embeddings: int = 4096, short_factor: Union[List[float], float] = 1.0, @@ -23,10 +22,7 @@ class SuScaledRotaryEmbedding: Args: dims (int): The feature dimensions to be rotated. - traditional (bool, optional): Unused. Default: ``False``. base (int, optional): Base for the exponential scaling. - scale (float, optional): The scale used to scale the positions. - Default: ``1.0``. max_position_embeddings (int, optional): The maximum sequence length that this model was trained with. This is used to determine the size of the original RoPE embeddings when using long scaling. @@ -42,40 +38,23 @@ class SuScaledRotaryEmbedding: factors for sequences of length greater than ``original_max_position_embeddings``. Default: ``1.0``. """ - self.inv_freq_short = 1.0 / ( - mx.array(short_factor, dtype=mx.float32) - * base ** (mx.arange(0, dims, 2, dtype=mx.float32) / dims) - ) - self.inv_freq_long = 1.0 / ( - scale - * mx.array(long_factor, dtype=mx.float32) - * base ** (mx.arange(0, dims, 2, dtype=mx.float32) / dims) - ) + super().__init__() + freqs = base ** (mx.arange(0, dims, 2, dtype=mx.float32) / dims) + self._freqs = mx.array(long_factor, dtype=mx.float32) * freqs self.original_max_position_embeddings = original_max_position_embeddings - self.scaling_factor = math.sqrt( + self.scale = math.sqrt( 1 + math.log(max_position_embeddings / original_max_position_embeddings) / math.log(original_max_position_embeddings) ) - def _get_cos_sin(self, offset, L): - position_ids = mx.arange(offset, offset + L, dtype=mx.float32) - inv_freq = ( - self.inv_freq_long - if (offset + L) > self.original_max_position_embeddings - else self.inv_freq_short - ) - freqs = position_ids[:, None] * inv_freq[None, :] - emb = mx.concatenate([freqs, freqs], axis=-1) - cos = mx.cos(emb) * self.scaling_factor - sin = mx.sin(emb) * self.scaling_factor - return cos, sin - def __call__(self, x, offset: int = 0): - def _rotate_half(_x): - midpoint = _x.shape[-1] // 2 - x1, x2 = _x[..., :midpoint], _x[..., midpoint:] - return mx.concatenate([-x2, x1], axis=-1) - - cos, sin = self._get_cos_sin(offset, x.shape[2]) - return (x * cos) + (_rotate_half(x) * sin) + return mx.fast.rope( + self.scale * x, + x.shape[-1], + traditional=False, + base=None, + scale=1.0, + offset=offset, + freqs=self._freqs, + ) diff --git a/llms/mlx_lm/requirements.txt b/llms/mlx_lm/requirements.txt index 4875f931..814c03cc 100644 --- a/llms/mlx_lm/requirements.txt +++ b/llms/mlx_lm/requirements.txt @@ -1,4 +1,4 @@ -mlx>=0.14.1 +mlx>=0.17.0 numpy transformers[sentencepiece]>=4.39.3 protobuf diff --git a/llms/mlx_lm/version.py b/llms/mlx_lm/version.py index f73aaa0a..41237905 100644 --- a/llms/mlx_lm/version.py +++ b/llms/mlx_lm/version.py @@ -1,3 +1,3 @@ # Copyright © 2023-2024 Apple Inc. -__version__ = "0.17.0" +__version__ = "0.17.1" diff --git a/stable_diffusion/stable_diffusion/unet.py b/stable_diffusion/stable_diffusion/unet.py index ec2915e5..cfad7fcc 100644 --- a/stable_diffusion/stable_diffusion/unet.py +++ b/stable_diffusion/stable_diffusion/unet.py @@ -110,7 +110,7 @@ class Transformer2D(nn.Module): # Perform the input norm and projection B, H, W, C = x.shape - x = self.norm(x.astype(mx.float32)).astype(dtype).reshape(B, -1, C) + x = self.norm(x).reshape(B, -1, C) x = self.proj_in(x) # Apply the transformer @@ -156,12 +156,12 @@ class ResnetBlock2D(nn.Module): if temb is not None: temb = self.time_emb_proj(nn.silu(temb)) - y = self.norm1(x.astype(mx.float32)).astype(dtype) + y = self.norm1(x) y = nn.silu(y) y = self.conv1(y) if temb is not None: y = y + temb[:, None, None, :] - y = self.norm2(y.astype(mx.float32)).astype(dtype) + y = self.norm2(y) y = nn.silu(y) y = self.conv2(y) @@ -453,8 +453,7 @@ class UNetModel(nn.Module): ) # Postprocess the output - dtype = x.dtype - x = self.conv_norm_out(x.astype(mx.float32)).astype(dtype) + x = self.conv_norm_out(x) x = nn.silu(x) x = self.conv_out(x) From b5e18ef1e399f81baa6e92b1e25c2d8fe0394f42 Mon Sep 17 00:00:00 2001 From: Prince Canuma Date: Sat, 24 Aug 2024 15:52:33 +0200 Subject: [PATCH 016/188] Add Phi-3.5-MoE (#946) * add phimoe * add phimoe to tunner * add switch_mlp * fix SuScaled args * nits --------- Co-authored-by: Awni Hannun --- llms/mlx_lm/models/phimoe.py | 218 ++++++++++++++++++++++++++++++++++ llms/mlx_lm/models/su_rope.py | 6 +- llms/mlx_lm/tuner/utils.py | 3 +- 3 files changed, 225 insertions(+), 2 deletions(-) create mode 100644 llms/mlx_lm/models/phimoe.py diff --git a/llms/mlx_lm/models/phimoe.py b/llms/mlx_lm/models/phimoe.py new file mode 100644 index 00000000..db6bd4b5 --- /dev/null +++ b/llms/mlx_lm/models/phimoe.py @@ -0,0 +1,218 @@ +# Copyright © 2024 Apple Inc. +import math +from dataclasses import dataclass +from typing import Dict, List, Optional, Union + +import mlx.core as mx +import mlx.nn as nn + +from .base import BaseModelArgs, create_attention_mask +from .su_rope import SuScaledRotaryEmbedding +from .switch_layers import SwitchGLU + + +@dataclass +class ModelArgs(BaseModelArgs): + model_type: str = "phimoe" + vocab_size: int = 32064 + hidden_size: int = 4096 + intermediate_size: int = 6400 + num_hidden_layers: int = 32 + num_attention_heads: int = 32 + num_key_value_heads: int = 8 + max_position_embeddings: int = 131072 + original_max_position_embeddings: int = 4096 + rms_norm_eps: float = 1e-6 + rope_scaling: Dict[str, Union[float, List[float]]] = None + num_local_experts: int = 16 + num_experts_per_tok: int = 2 + rope_theta: float = 10000.0 + + +class Attention(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + + dim = args.hidden_size + self.n_heads = n_heads = args.num_attention_heads + self.n_kv_heads = n_kv_heads = args.num_key_value_heads + + head_dim = args.hidden_size // n_heads + self.scale = head_dim**-0.5 + + self.q_proj = nn.Linear(dim, n_heads * head_dim, bias=True) + self.k_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=True) + self.v_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=True) + self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=True) + + self.rope = SuScaledRotaryEmbedding( + head_dim, + base=args.rope_theta, + max_position_embeddings=args.max_position_embeddings, + original_max_position_embeddings=args.original_max_position_embeddings, + short_factor=args.rope_scaling["short_factor"], + long_factor=args.rope_scaling["long_factor"], + short_mscale=args.rope_scaling["short_mscale"], + long_mscale=args.rope_scaling["long_mscale"], + ) + + def __call__( + self, + x: mx.array, + mask: Optional[mx.array] = None, + cache=None, + ) -> mx.array: + B, L, D = x.shape + + queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x) + + # Prepare the queries, keys and values for the attention computation + queries = queries.reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) + keys = keys.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) + values = values.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) + + if cache is not None: + queries = self.rope(queries, offset=cache.offset) + keys = self.rope(keys, offset=cache.offset) + keys, values = cache.update_and_fetch(keys, values) + else: + queries = self.rope(queries) + keys = self.rope(keys) + + output = mx.fast.scaled_dot_product_attention( + queries, keys, values, scale=self.scale, mask=mask + ) + output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) + return self.o_proj(output) + + +class PhiMoESparseMoeBlock(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.hidden_dim = args.hidden_size + self.ffn_dim = args.intermediate_size + self.num_experts = args.num_local_experts + self.top_k = args.num_experts_per_tok + + self.gate = nn.Linear(self.hidden_dim, self.num_experts, bias=False) + self.switch_mlp = SwitchGLU(self.hidden_dim, self.ffn_dim, self.num_experts) + + def __call__(self, x: mx.array) -> mx.array: + gates = self.gate(x) + + k = self.top_k + inds = mx.stop_gradient(mx.argpartition(-gates, kth=k - 1, axis=-1)[..., :k]) + scores = mx.take_along_axis(gates, inds, axis=-1) + scores = mx.softmax(scores, axis=-1, precise=True) + + y = self.switch_mlp(x, inds) + y = (y * scores[..., None]).sum(axis=-2) + + return y + + +class PhiMoEDecoderLayer(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.hidden_size = args.hidden_size + + self.self_attn = Attention(args) + self.block_sparse_moe = PhiMoESparseMoeBlock(args) + self.input_layernorm = nn.LayerNorm(args.hidden_size, eps=args.rms_norm_eps) + self.post_attention_layernorm = nn.LayerNorm( + args.hidden_size, eps=args.rms_norm_eps + ) + + def __call__( + self, + x: mx.array, + mask: Optional[mx.array] = None, + cache=None, + ) -> mx.array: + residual = x + hidden_states = self.input_layernorm(x) + hidden_states = self.self_attn(hidden_states, mask=mask, cache=cache) + hidden_states = residual + hidden_states + + residual = hidden_states + hidden_states = self.post_attention_layernorm(hidden_states) + hidden_states = self.block_sparse_moe(hidden_states) + hidden_states = residual + hidden_states + + return hidden_states + + +class PhiMoEModel(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.args = args + self.vocab_size = args.vocab_size + + self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size) + self.layers = [PhiMoEDecoderLayer(args) for _ in range(args.num_hidden_layers)] + self.norm = nn.LayerNorm(args.hidden_size, eps=args.rms_norm_eps) + + def __call__( + self, + inputs: mx.array, + cache=None, + ) -> mx.array: + h = self.embed_tokens(inputs) + + mask = create_attention_mask(h, cache) + + if cache is None: + cache = [None] * len(self.layers) + + for layer, c in zip(self.layers, cache): + h = layer(h, mask, c) + + return self.norm(h) + + +class Model(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.args = args + self.model = PhiMoEModel(args) + self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=True) + + def __call__( + self, + inputs: mx.array, + cache=None, + ): + out = self.model(inputs, cache) + return self.lm_head(out) + + def sanitize(self, weights): + if "model.layers.0.block_sparse_moe.experts.0.w1.weight" not in weights: + return weights + for l in range(self.args.num_hidden_layers): + prefix = f"model.layers.{l}" + for n, m in [("w1", "gate_proj"), ("w2", "down_proj"), ("w3", "up_proj")]: + for k in ["weight", "scales", "biases"]: + if f"{prefix}.block_sparse_moe.experts.0.{n}.{k}" in weights: + to_join = [ + weights.pop( + f"{prefix}.block_sparse_moe.experts.{e}.{n}.{k}" + ) + for e in range(self.args.num_local_experts) + ] + weights[f"{prefix}.block_sparse_moe.switch_mlp.{m}.{k}"] = ( + mx.stack(to_join) + ) + + return weights + + @property + def layers(self): + return self.model.layers + + @property + def head_dim(self): + return self.args.hidden_size // self.args.num_attention_heads + + @property + def n_kv_heads(self): + return self.args.num_key_value_heads diff --git a/llms/mlx_lm/models/su_rope.py b/llms/mlx_lm/models/su_rope.py index f96b9957..9c414afd 100644 --- a/llms/mlx_lm/models/su_rope.py +++ b/llms/mlx_lm/models/su_rope.py @@ -16,6 +16,8 @@ class SuScaledRotaryEmbedding(nn.Module): original_max_position_embeddings: int = 4096, short_factor: Union[List[float], float] = 1.0, long_factor: Union[List[float], float] = 1.0, + short_mscale: float = None, + long_mscale: float = None, ): """ Phi3Su Scaled Rotary Embedding layer for Phi-3 models. @@ -37,12 +39,14 @@ class SuScaledRotaryEmbedding(nn.Module): long_factor (float or list[float], optional): List of scaling factors for sequences of length greater than ``original_max_position_embeddings``. Default: ``1.0``. + short_mscale (float, optional): Scale the input prior to embedding. + long_mscale (float, optional): Scale the input prior to embedding. """ super().__init__() freqs = base ** (mx.arange(0, dims, 2, dtype=mx.float32) / dims) self._freqs = mx.array(long_factor, dtype=mx.float32) * freqs self.original_max_position_embeddings = original_max_position_embeddings - self.scale = math.sqrt( + self.scale = long_mscale or math.sqrt( 1 + math.log(max_position_embeddings / original_max_position_embeddings) / math.log(original_max_position_embeddings) diff --git a/llms/mlx_lm/tuner/utils.py b/llms/mlx_lm/tuner/utils.py index 9f18c2c0..1a54a925 100644 --- a/llms/mlx_lm/tuner/utils.py +++ b/llms/mlx_lm/tuner/utils.py @@ -96,6 +96,7 @@ def linear_to_lora_layers( "stablelm", "qwen2", "qwen2_moe", + "phimoe", "gemma", "gemma2", "starcoder2", @@ -104,7 +105,7 @@ def linear_to_lora_layers( "deepseek", ]: keys = set(["self_attn.q_proj", "self_attn.v_proj"]) - if model.model_type == "mixtral": + if model.model_type in ["mixtral", "phimoe"]: keys.add("block_sparse_moe.gate") if model.model_type == "qwen2_moe": keys.add("mlp.gate") From bf21789b17377bed99dbe846030f56ac810e0339 Mon Sep 17 00:00:00 2001 From: Nripesh Niketan <86844847+NripeshN@users.noreply.github.com> Date: Mon, 26 Aug 2024 20:24:23 +0530 Subject: [PATCH 017/188] chore: update black pre-commit hooks to latest versions (#955) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1a648a69..012a96ad 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/psf/black-pre-commit-mirror - rev: 24.3.0 + rev: 24.8.0 hooks: - id: black - repo: https://github.com/pycqa/isort From 7f8c961287442ec1b78bedbba8e34251ec6defa2 Mon Sep 17 00:00:00 2001 From: Angelos Katharopoulos Date: Wed, 28 Aug 2024 14:47:33 -0700 Subject: [PATCH 018/188] Fix setattr for the TokenizerWrapper (#961) --- llms/mlx_lm/tokenizer_utils.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/llms/mlx_lm/tokenizer_utils.py b/llms/mlx_lm/tokenizer_utils.py index 6caad629..04bbbcc5 100644 --- a/llms/mlx_lm/tokenizer_utils.py +++ b/llms/mlx_lm/tokenizer_utils.py @@ -252,9 +252,19 @@ class TokenizerWrapper: def __getattr__(self, attr): if attr == "detokenizer": return self._detokenizer + elif attr.startswith("_"): + return self.__getattribute__(attr) else: return getattr(self._tokenizer, attr) + def __setattr__(self, attr, value): + if attr == "detokenizer": + raise AttributeError("Cannot set the detokenizer.") + elif attr.startswith("_"): + super().__setattr__(attr, value) + else: + setattr(self._tokenizer, attr, value) + def _match(a, b): if type(a) != type(b): From 1003a8b2dd59a22255a6a6c9a20f9d41f9812fb5 Mon Sep 17 00:00:00 2001 From: Angelos Katharopoulos Date: Wed, 28 Aug 2024 22:11:45 -0700 Subject: [PATCH 019/188] Add the ability to load the KV cache from a file (#956) --- llms/mlx_lm/cache_prompt.py | 149 ++++++++++++++++++++++++++++++++++++ llms/mlx_lm/generate.py | 69 +++++++++++++++-- llms/mlx_lm/models/base.py | 2 + llms/mlx_lm/utils.py | 51 ++++++++---- llms/setup.py | 1 + 5 files changed, 250 insertions(+), 22 deletions(-) create mode 100644 llms/mlx_lm/cache_prompt.py diff --git a/llms/mlx_lm/cache_prompt.py b/llms/mlx_lm/cache_prompt.py new file mode 100644 index 00000000..ad045f1a --- /dev/null +++ b/llms/mlx_lm/cache_prompt.py @@ -0,0 +1,149 @@ +# Copyright © 2024 Apple Inc. + +import argparse +import json +import sys +import time + +import mlx.core as mx + +from .utils import load, make_kv_caches + + +def setup_arg_parser(): + """Set up and return the argument parser.""" + parser = argparse.ArgumentParser( + description="Cache the KV cache of a prompt to be reused with mlx_lm.generate" + ) + parser.add_argument( + "--model", + type=str, + default="mlx_model", + help="The path to the local model directory or Hugging Face repo.", + ) + parser.add_argument( + "--adapter-path", + type=str, + help="Optional path for the trained adapter weights and config.", + ) + parser.add_argument( + "--trust-remote-code", + action="store_true", + help="Enable trusting remote code for tokenizer", + ) + parser.add_argument( + "--eos-token", + type=str, + default=None, + help="End of sequence token for tokenizer", + ) + parser.add_argument( + "--ignore-chat-template", + action="store_true", + help="Use the raw prompt without the tokenizer's chat template.", + ) + parser.add_argument( + "--use-default-chat-template", + action="store_true", + help="Use the default chat template", + ) + parser.add_argument( + "--cache-limit-gb", + type=int, + default=None, + help="Set the MLX cache limit in GB", + ) + parser.add_argument( + "--max-kv-size", + type=int, + default=1024, + help="Set the maximum key-value cache size", + ) + parser.add_argument( + "--kv-cache-file", help="The file to save the KV caches in", required=True + ) + parser.add_argument( + "--prompt", + required=True, + help="Message to be processed by the model ('-' reads from stdin)", + ) + return parser + + +def main(): + parser = setup_arg_parser() + args = parser.parse_args() + + if args.cache_limit_gb is not None: + mx.metal.set_cache_limit(args.cache_limit_gb * 1024 * 1024 * 1024) + + # Building tokenizer_config + tokenizer_config = {"trust_remote_code": True if args.trust_remote_code else None} + if args.eos_token is not None: + tokenizer_config["eos_token"] = args.eos_token + + model, tokenizer = load( + args.model, + adapter_path=args.adapter_path, + tokenizer_config=tokenizer_config, + ) + + args.prompt = sys.stdin.read() if args.prompt == "-" else args.prompt + + if args.use_default_chat_template: + if tokenizer.chat_template is None: + tokenizer.chat_template = tokenizer.default_chat_template + + if not args.ignore_chat_template and ( + hasattr(tokenizer, "apply_chat_template") + and tokenizer.chat_template is not None + ): + messages = [{"role": "user", "content": args.prompt}] + prompt = tokenizer.apply_chat_template( + messages, tokenize=False, add_generation_prompt=True + ) + + # Treat the prompt as a prefix assuming that the suffix will be + # provided at generation time. + test_prompt = tokenizer.apply_chat_template( + [{"role": "user", "content": ""}], + tokenize=False, + add_generation_prompt=True, + ) + n = len(test_prompt) - test_prompt.index("") - len("") + prompt = prompt[:-n] + else: + prompt = args.prompt + + cache = make_kv_caches(model, args.max_kv_size) + y = mx.array(tokenizer.encode(prompt)) + + # Process the prompt + processed = 0 + step_size = 512 + start = time.time() + max_msg_len = 0 + while y.size > 0: + model(y[:step_size][None], cache=cache) + mx.eval([c.state for c in cache]) + processed += min(y.size, step_size) + y = y[step_size:] + current = time.time() + speed = processed / (current - start) + msg = f"\rProcessed {processed:6d} tokens ({speed:6.2f} tok/s)" + max_msg_len = max(max_msg_len, len(msg)) + print(msg + " " * (max_msg_len - len(msg)), end="", flush=True) + print() + print(f"Peak memory: {mx.metal.get_peak_memory() / 2**30:.3f} GB") + + print("Saving...") + cache_dict = {} + for i, c in enumerate(cache): + cache_dict[f"{i}_keys"] = c.state[0] + cache_dict[f"{i}_values"] = c.state[1] + metadata = {} + metadata["model"] = args.model + metadata["chat_template"] = tokenizer.chat_template + metadata["tokenizer_config"] = json.dumps(tokenizer_config) + metadata["max_kv_size"] = str(args.max_kv_size) + mx.save_safetensors(args.kv_cache_file, cache_dict, metadata) diff --git a/llms/mlx_lm/generate.py b/llms/mlx_lm/generate.py index 6707d25c..4aa4001a 100644 --- a/llms/mlx_lm/generate.py +++ b/llms/mlx_lm/generate.py @@ -1,17 +1,18 @@ # Copyright © 2023-2024 Apple Inc. import argparse +import json import mlx.core as mx from .utils import generate, load -DEFAULT_MODEL_PATH = "mlx_model" DEFAULT_PROMPT = "hello" DEFAULT_MAX_TOKENS = 100 DEFAULT_TEMP = 0.6 DEFAULT_TOP_P = 1.0 DEFAULT_SEED = 0 +DEFAULT_MAX_KV_SIZE = 1024 def setup_arg_parser(): @@ -20,7 +21,6 @@ def setup_arg_parser(): parser.add_argument( "--model", type=str, - default="mlx_model", help="The path to the local model directory or Hugging Face repo.", ) parser.add_argument( @@ -80,9 +80,14 @@ def setup_arg_parser(): parser.add_argument( "--max-kv-size", type=int, - default=1024, help="Set the maximum key-value cache size", ) + parser.add_argument( + "--kv-cache-file", + type=str, + default=None, + help="A file containing saved KV caches to avoid recomputing them", + ) return parser @@ -113,6 +118,24 @@ def colorprint_by_t0(s, t0): colorprint(color, s) +def load_kv_cache_from_file(kv_cache_file): + if kv_cache_file is None: + return None, None + + kv_cache, metadata = mx.load(kv_cache_file, return_metadata=True) + cache_per_layer = {} + for k, x in kv_cache.items(): + layer, kv_type = k.split("_") + if layer not in cache_per_layer: + cache_per_layer[layer] = {} + cache_per_layer[layer][kv_type] = x + + cache_history = [None] * len(cache_per_layer) + for layer, c in cache_per_layer.items(): + cache_history[int(layer)] = (c["keys"], c["values"]) + return cache_history, metadata + + def main(): parser = setup_arg_parser() args = parser.parse_args() @@ -122,13 +145,25 @@ def main(): if args.cache_limit_gb is not None: mx.metal.set_cache_limit(args.cache_limit_gb * 1024 * 1024 * 1024) + # Load the kv cache and metadata if a kv cache file is provided + cache_history, metadata = load_kv_cache_from_file(args.kv_cache_file) + # Building tokenizer_config - tokenizer_config = {"trust_remote_code": True if args.trust_remote_code else None} + tokenizer_config = ( + {} if cache_history is None else json.loads(metadata["tokenizer_config"]) + ) + if args.trust_remote_code: + tokenizer_config["trust_remote_code"] = True if args.eos_token is not None: tokenizer_config["eos_token"] = args.eos_token + # If no model path is provided then use the one in the kv cache history + model_path = args.model + if cache_history is not None and model_path is None: + model_path = metadata["model"] + model, tokenizer = load( - args.model, + model_path, adapter_path=args.adapter_path, tokenizer_config=tokenizer_config, ) @@ -136,6 +171,8 @@ def main(): if args.use_default_chat_template: if tokenizer.chat_template is None: tokenizer.chat_template = tokenizer.default_chat_template + elif tokenizer.chat_template is None: + tokenizer.chat_template = metadata["chat_template"] if not args.ignore_chat_template and ( hasattr(tokenizer, "apply_chat_template") @@ -145,11 +182,30 @@ def main(): prompt = tokenizer.apply_chat_template( messages, tokenize=False, add_generation_prompt=True ) + + # Treat the prompt as a suffix assuming that the prefix is in the + # stored kv cache. + if cache_history is not None: + test_prompt = tokenizer.apply_chat_template( + [{"role": "user", "content": ""}], + tokenize=False, + add_generation_prompt=True, + ) + prompt = prompt[test_prompt.index("") :] else: prompt = args.prompt formatter = colorprint_by_t0 if args.colorize else None + # Determine the max kv size from the kv cache or passed arguments + max_kv_size = args.max_kv_size + if max_kv_size is None: + max_kv_size = ( + int(metadata["max_kv_size"]) + if cache_history is not None + else DEFAULT_MAX_KV_SIZE + ) + generate( model, tokenizer, @@ -159,7 +215,8 @@ def main(): formatter=formatter, temp=args.temp, top_p=args.top_p, - max_kv_size=args.max_kv_size, + max_kv_size=max_kv_size, + cache_history=cache_history, ) diff --git a/llms/mlx_lm/models/base.py b/llms/mlx_lm/models/base.py index 3e84554c..dc19dd05 100644 --- a/llms/mlx_lm/models/base.py +++ b/llms/mlx_lm/models/base.py @@ -46,6 +46,7 @@ class KVCache: self.values[..., prev : self.offset, :] = values return self.keys[..., : self.offset, :], self.values[..., : self.offset, :] + @property def state(self): return self.keys, self.values @@ -137,6 +138,7 @@ class RotatingKVCache: return self.keys[..., : self.offset, :], self.values[..., : self.offset, :] return self.keys, self.values + @property def state(self): return self.keys, self.values diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index 44196766..71476df3 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -9,7 +9,7 @@ import shutil import time from pathlib import Path from textwrap import dedent -from typing import Any, Callable, Dict, Generator, Optional, Tuple, Type, Union +from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Type, Union import mlx.core as mx import mlx.nn as nn @@ -126,6 +126,26 @@ def apply_repetition_penalty(logits: mx.array, generated_tokens: Any, penalty: f return logits +def make_kv_caches( + model: nn.Module, max_kv_size: Optional[int] = None +) -> List[Union[KVCache, RotatingKVCache]]: + if hasattr(model, "make_cache"): + return model.make_cache() + + kv_heads = ( + [model.n_kv_heads] * len(model.layers) + if isinstance(model.n_kv_heads, int) + else model.n_kv_heads + ) + if max_kv_size is not None: + return [ + RotatingKVCache(model.head_dim, n, max_size=max_kv_size, keep=4) + for n in kv_heads + ] + else: + return [KVCache(model.head_dim, n) for n in kv_heads] + + def generate_step( prompt: mx.array, model: nn.Module, @@ -138,6 +158,7 @@ def generate_step( logit_bias: Optional[Dict[int, float]] = None, prefill_step_size: int = 512, max_kv_size: Optional[int] = None, + cache_history: Optional[List[Tuple[mx.array, mx.array]]] = None, ) -> Generator[Tuple[mx.array, mx.array], None, None]: """ A generator producing token ids based on the given prompt from the model. @@ -194,21 +215,19 @@ def generate_step( ) y = prompt - if hasattr(model, "make_cache"): - cache = model.make_cache() - else: - kv_heads = ( - [model.n_kv_heads] * len(model.layers) - if isinstance(model.n_kv_heads, int) - else model.n_kv_heads - ) - if max_kv_size is not None: - cache = [ - RotatingKVCache(model.head_dim, n, max_size=max_kv_size, keep=4) - for n in kv_heads - ] - else: - cache = [KVCache(model.head_dim, n) for n in kv_heads] + + # Create the KV cache for generation + cache = make_kv_caches(model, max_kv_size) + + if cache_history is not None: + if len(cache_history) != len(cache): + raise ValueError("Wrong number of layers in the cache history") + + # Set the history in the cache objects and evaluate them to prepare for + # generation. + for c, h in zip(cache, cache_history): + c.update_and_fetch(h[0], h[1]) + mx.eval([c.state for c in cache]) repetition_context = prompt.tolist() diff --git a/llms/setup.py b/llms/setup.py index 88deed17..ac294ae1 100644 --- a/llms/setup.py +++ b/llms/setup.py @@ -31,6 +31,7 @@ setup( }, entry_points={ "console_scripts": [ + "mlx_lm.cache_prompt = mlx_lm.cache_prompt:main", "mlx_lm.convert = mlx_lm.convert:main", "mlx_lm.fuse = mlx_lm.fuse:main", "mlx_lm.generate = mlx_lm.generate:main", From b1186e2a81c678087bcaab43202d040b126523c3 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Thu, 29 Aug 2024 15:05:17 -0700 Subject: [PATCH 020/188] Docs on prompt scaling (#963) * docs on prompt scaling * remove unused var * nits --- llms/README.md | 42 ++++++++++++++++++++++++++++++++++--- llms/mlx_lm/cache_prompt.py | 6 +++++- llms/mlx_lm/generate.py | 11 ++++------ llms/mlx_lm/version.py | 2 +- 4 files changed, 49 insertions(+), 12 deletions(-) diff --git a/llms/README.md b/llms/README.md index 497c0277..79f26d41 100644 --- a/llms/README.md +++ b/llms/README.md @@ -38,7 +38,9 @@ To see a description of all the arguments you can do: >>> help(generate) ``` -Check out the [generation example](https://github.com/ml-explore/mlx-examples/tree/main/llms/mlx_lm/examples/generate_response.py) to see how to use the API in more detail. +Check out the [generation +example](https://github.com/ml-explore/mlx-examples/tree/main/llms/mlx_lm/examples/generate_response.py) +to see how to use the API in more detail. The `mlx-lm` package also comes with functionality to quantize and optionally upload models to the Hugging Face Hub. @@ -122,10 +124,44 @@ mlx_lm.convert \ --upload-repo mlx-community/my-4bit-mistral ``` +### Long Prompts and Generations + +MLX LM has some tools to scale efficiently to long prompts and generations: + +- A rotating fixed-size key-value cache. +- Prompt caching + +To use the rotating key-value cache pass the argument `--max-kv-size n` where +`n` can be any integer. Smaller values like `512` will use very little RAM but +result in worse quality. Larger values like `4096` or higher will use more RAM +but have better quality. + +Caching prompts can substantially speedup reusing the same long context with +different queries. To cache a prompt use `mlx_lm.cache_prompt`. For example: + +```bash +cat prompt.txt | mlx_lm.cache_prompt \ + --model mistralai/Mistral-7B-Instruct-v0.3 \ + --prompt - \ + --kv-cache-file mistral_prompt.safetensors +``` + +Then use the cached prompt with `mlx_lm.generate`: + +``` +mlx_lm.generate \ + --kv-cache-file mistral_prompt.safetensors \ + --prompt "\nSummarize the above text." +``` + +The cached prompt is treated as a prefix to the supplied prompt. Also notice +when using a cached prompt, the model to use is read from the cache and need +not be supplied explicitly. + ### Supported Models -The example supports Hugging Face format Mistral, Llama, and Phi-2 style -models. If the model you want to run is not supported, file an +MLX LM supports thousands of Hugging Face format LLMs. If the model you want to +run is not supported, file an [issue](https://github.com/ml-explore/mlx-examples/issues/new) or better yet, submit a pull request. diff --git a/llms/mlx_lm/cache_prompt.py b/llms/mlx_lm/cache_prompt.py index ad045f1a..fe088118 100644 --- a/llms/mlx_lm/cache_prompt.py +++ b/llms/mlx_lm/cache_prompt.py @@ -56,7 +56,7 @@ def setup_arg_parser(): parser.add_argument( "--max-kv-size", type=int, - default=1024, + default=None, help="Set the maximum key-value cache size", ) parser.add_argument( @@ -147,3 +147,7 @@ def main(): metadata["tokenizer_config"] = json.dumps(tokenizer_config) metadata["max_kv_size"] = str(args.max_kv_size) mx.save_safetensors(args.kv_cache_file, cache_dict, metadata) + + +if __name__ == "__main__": + main() diff --git a/llms/mlx_lm/generate.py b/llms/mlx_lm/generate.py index 4aa4001a..54f6f4d2 100644 --- a/llms/mlx_lm/generate.py +++ b/llms/mlx_lm/generate.py @@ -12,7 +12,6 @@ DEFAULT_MAX_TOKENS = 100 DEFAULT_TEMP = 0.6 DEFAULT_TOP_P = 1.0 DEFAULT_SEED = 0 -DEFAULT_MAX_KV_SIZE = 1024 def setup_arg_parser(): @@ -81,6 +80,7 @@ def setup_arg_parser(): "--max-kv-size", type=int, help="Set the maximum key-value cache size", + default=None, ) parser.add_argument( "--kv-cache-file", @@ -199,12 +199,9 @@ def main(): # Determine the max kv size from the kv cache or passed arguments max_kv_size = args.max_kv_size - if max_kv_size is None: - max_kv_size = ( - int(metadata["max_kv_size"]) - if cache_history is not None - else DEFAULT_MAX_KV_SIZE - ) + if cache_history is not None: + max_kv_size = metadata["max_kv_size"] + max_kv_size = int(max_kv_size) if max_kv_size.isdigit() else None generate( model, diff --git a/llms/mlx_lm/version.py b/llms/mlx_lm/version.py index 41237905..87e86846 100644 --- a/llms/mlx_lm/version.py +++ b/llms/mlx_lm/version.py @@ -1,3 +1,3 @@ # Copyright © 2023-2024 Apple Inc. -__version__ = "0.17.1" +__version__ = "0.18.0" From fc93c557238e9441835afe2748fd170016cb068b Mon Sep 17 00:00:00 2001 From: L Date: Thu, 29 Aug 2024 21:08:57 -0700 Subject: [PATCH 021/188] feat(mlx_lm): Nemotron (#949) * feat: Nemotron https://huggingface.co/nvidia/Minitron-4B-Base This is basically Llama with partial RoPE and LayerNorm instead of BatchNorm. Also they add 1 to the LayerNorm weight for some reason. * fixup! feat: Nemotron * nits --------- Co-authored-by: Awni Hannun --- llms/mlx_lm/models/nemotron.py | 227 +++++++++++++++++++++++++++++++++ llms/mlx_lm/tuner/utils.py | 1 + 2 files changed, 228 insertions(+) create mode 100644 llms/mlx_lm/models/nemotron.py diff --git a/llms/mlx_lm/models/nemotron.py b/llms/mlx_lm/models/nemotron.py new file mode 100644 index 00000000..ef55d1d7 --- /dev/null +++ b/llms/mlx_lm/models/nemotron.py @@ -0,0 +1,227 @@ +# Copyright © 2024 Apple Inc. + +from dataclasses import dataclass +from functools import partial +from typing import Dict, Optional, Union + +import mlx.core as mx +import mlx.nn as nn + +from .base import BaseModelArgs, KVCache, create_attention_mask + + +@dataclass +class ModelArgs(BaseModelArgs): + model_type: str + hidden_size: int + hidden_act: str + num_hidden_layers: int + intermediate_size: int + num_attention_heads: int + norm_eps: float + vocab_size: int + num_key_value_heads: int + head_dim: Optional[int] = None + max_position_embeddings: Optional[int] = None + attention_bias: bool = False + mlp_bias: bool = False + partial_rotary_factor: float = 0.5 + rope_theta: float = 10000.0 + rope_traditional: bool = False + rope_scaling: Optional[Dict[str, Union[float, str]]] = None + tie_word_embeddings: bool = False + + def __post_init__(self): + if self.rope_scaling: + if not "factor" in self.rope_scaling: + raise ValueError(f"rope_scaling must contain 'factor'") + rope_type = self.rope_scaling.get("type") or self.rope_scaling.get( + "rope_type" + ) + if rope_type is None: + raise ValueError( + f"rope_scaling must contain either 'type' or 'rope_type'" + ) + if rope_type not in ["linear"]: + raise ValueError("rope_scaling 'type' currently only supports 'linear'") + + +@partial(mx.compile, shapeless=True) +def relu_squared(x): + return nn.relu(x).square() + + +class NemotronLayerNorm1P(nn.LayerNorm): + def __call__(self, x): + weight = self.weight + 1 if "weight" in self else None + bias = self.bias if "bias" in self else None + return mx.fast.layer_norm(x, weight, bias, self.eps) + + +class Attention(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + + dim = args.hidden_size + self.n_heads = n_heads = args.num_attention_heads + self.n_kv_heads = n_kv_heads = args.num_key_value_heads + + self.head_dim = head_dim = args.head_dim or args.hidden_size // n_heads + self.partial_rotary_factor = args.partial_rotary_factor + + self.scale = head_dim**-0.5 + if hasattr(args, "attention_bias"): + attention_bias = args.attention_bias + else: + attention_bias = False + + self.q_proj = nn.Linear(dim, n_heads * head_dim, bias=attention_bias) + self.k_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=attention_bias) + self.v_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=attention_bias) + self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=attention_bias) + + rope_scale = 1.0 + if args.rope_scaling and args.rope_scaling["type"] == "linear": + assert isinstance(args.rope_scaling["factor"], float) + rope_scale = 1 / args.rope_scaling["factor"] + self.rope = nn.RoPE( + int(self.partial_rotary_factor * self.head_dim), + base=args.rope_theta, + scale=rope_scale, + ) + + def __call__( + self, + x: mx.array, + mask: Optional[mx.array] = None, + cache: Optional[KVCache] = None, + ) -> mx.array: + B, L, _ = x.shape + + queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x) + + # Prepare the queries, keys and values for the attention computation + queries = queries.reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) + keys = keys.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) + values = values.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) + + if cache is not None: + queries = self.rope(queries, offset=cache.offset) + keys = self.rope(keys, offset=cache.offset) + keys, values = cache.update_and_fetch(keys, values) + else: + queries = self.rope(queries) + keys = self.rope(keys) + + output = mx.fast.scaled_dot_product_attention( + queries, keys, values, scale=self.scale, mask=mask + ) + output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) + return self.o_proj(output) + + +class MLP(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + + dim = args.hidden_size + hidden_dim = args.intermediate_size + mlp_bias = args.mlp_bias + + self.down_proj = nn.Linear(hidden_dim, dim, bias=mlp_bias) + self.up_proj = nn.Linear(dim, hidden_dim, bias=mlp_bias) + + def __call__(self, x) -> mx.array: + return self.down_proj(relu_squared(self.up_proj(x))) + + +class TransformerBlock(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.num_attention_heads = args.num_attention_heads + self.hidden_size = args.hidden_size + self.self_attn = Attention(args) + self.mlp = MLP(args) + self.input_layernorm = NemotronLayerNorm1P(args.hidden_size, eps=args.norm_eps) + self.post_attention_layernorm = NemotronLayerNorm1P( + args.hidden_size, eps=args.norm_eps + ) + + def __call__( + self, + x: mx.array, + mask: Optional[mx.array] = None, + cache: Optional[KVCache] = None, + ) -> mx.array: + r = self.self_attn(self.input_layernorm(x), mask, cache) + h = x + r + r = self.mlp(self.post_attention_layernorm(h)) + out = h + r + return out + + +class NemotronModel(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.args = args + self.vocab_size = args.vocab_size + self.num_hidden_layers = args.num_hidden_layers + assert self.vocab_size > 0 + self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size) + self.layers = [ + TransformerBlock(args=args) for _ in range(args.num_hidden_layers) + ] + self.norm = NemotronLayerNorm1P(args.hidden_size, eps=args.norm_eps) + + def __call__( + self, + inputs: mx.array, + cache=None, + ): + h = self.embed_tokens(inputs) + + mask = create_attention_mask(h, cache) + + if cache is None: + cache = [None] * len(self.layers) + + for layer, c in zip(self.layers, cache): + h = layer(h, mask, cache=c) + + return self.norm(h) + + +class Model(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.args = args + self.model_type = args.model_type + self.model = NemotronModel(args) + if not args.tie_word_embeddings: + self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=False) + + def __call__( + self, + inputs: mx.array, + cache=None, + ): + out = self.model(inputs, cache) + if self.args.tie_word_embeddings: + out = self.model.embed_tokens.as_linear(out) + else: + out = self.lm_head(out) + return out + + @property + def layers(self): + return self.model.layers + + @property + def head_dim(self): + return ( + self.args.head_dim or self.args.hidden_size // self.args.num_attention_heads + ) + + @property + def n_kv_heads(self): + return self.args.num_key_value_heads diff --git a/llms/mlx_lm/tuner/utils.py b/llms/mlx_lm/tuner/utils.py index 1a54a925..71fbfaab 100644 --- a/llms/mlx_lm/tuner/utils.py +++ b/llms/mlx_lm/tuner/utils.py @@ -93,6 +93,7 @@ def linear_to_lora_layers( "llama", "phi", "mixtral", + "nemotron", "stablelm", "qwen2", "qwen2_moe", From 3c6e8b11af9dda55ee8d38ee9f612a65118f7793 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Fri, 30 Aug 2024 05:56:27 -0700 Subject: [PATCH 022/188] fix (#965) --- llms/mlx_lm/generate.py | 2 +- llms/mlx_lm/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/llms/mlx_lm/generate.py b/llms/mlx_lm/generate.py index 54f6f4d2..f37037b6 100644 --- a/llms/mlx_lm/generate.py +++ b/llms/mlx_lm/generate.py @@ -171,7 +171,7 @@ def main(): if args.use_default_chat_template: if tokenizer.chat_template is None: tokenizer.chat_template = tokenizer.default_chat_template - elif tokenizer.chat_template is None: + elif cache_history is not None: tokenizer.chat_template = metadata["chat_template"] if not args.ignore_chat_template and ( diff --git a/llms/mlx_lm/version.py b/llms/mlx_lm/version.py index 87e86846..a2eb9a25 100644 --- a/llms/mlx_lm/version.py +++ b/llms/mlx_lm/version.py @@ -1,3 +1,3 @@ # Copyright © 2023-2024 Apple Inc. -__version__ = "0.18.0" +__version__ = "0.18.1" From bf921afcbef89463c2b8a78c4008d993500fadb4 Mon Sep 17 00:00:00 2001 From: James Zhao Date: Tue, 3 Sep 2024 23:16:21 +0300 Subject: [PATCH 023/188] Make sure to import the correct "version" module when installing mlx_whisper and mlx_lm from local source code. (#969) * Make sure to import the correct "version" module when installing the mlx_whisper package from local source code. * Make sure to import the correct "version" module when installing the mlx_lm package from local source code * fix --------- Co-authored-by: Awni Hannun --- llms/mlx_lm/__init__.py | 2 +- llms/mlx_lm/{version.py => _version.py} | 0 llms/setup.py | 2 +- whisper/mlx_whisper/__init__.py | 2 +- whisper/mlx_whisper/{version.py => _version.py} | 0 whisper/setup.py | 2 +- 6 files changed, 4 insertions(+), 4 deletions(-) rename llms/mlx_lm/{version.py => _version.py} (100%) rename whisper/mlx_whisper/{version.py => _version.py} (100%) diff --git a/llms/mlx_lm/__init__.py b/llms/mlx_lm/__init__.py index e971c467..502c78e5 100644 --- a/llms/mlx_lm/__init__.py +++ b/llms/mlx_lm/__init__.py @@ -1,4 +1,4 @@ # Copyright © 2023-2024 Apple Inc. +from ._version import __version__ from .utils import convert, generate, load, stream_generate -from .version import __version__ diff --git a/llms/mlx_lm/version.py b/llms/mlx_lm/_version.py similarity index 100% rename from llms/mlx_lm/version.py rename to llms/mlx_lm/_version.py diff --git a/llms/setup.py b/llms/setup.py index ac294ae1..e2cfe0cd 100644 --- a/llms/setup.py +++ b/llms/setup.py @@ -10,7 +10,7 @@ with open(package_dir / "requirements.txt") as fid: requirements = [l.strip() for l in fid.readlines()] sys.path.append(str(package_dir)) -from version import __version__ +from _version import __version__ setup( name="mlx-lm", diff --git a/whisper/mlx_whisper/__init__.py b/whisper/mlx_whisper/__init__.py index e6de0858..14c5197f 100644 --- a/whisper/mlx_whisper/__init__.py +++ b/whisper/mlx_whisper/__init__.py @@ -1,5 +1,5 @@ # Copyright © 2023-2024 Apple Inc. from . import audio, decoding, load_models +from ._version import __version__ from .transcribe import transcribe -from .version import __version__ diff --git a/whisper/mlx_whisper/version.py b/whisper/mlx_whisper/_version.py similarity index 100% rename from whisper/mlx_whisper/version.py rename to whisper/mlx_whisper/_version.py diff --git a/whisper/setup.py b/whisper/setup.py index 086f6471..0cabd64b 100644 --- a/whisper/setup.py +++ b/whisper/setup.py @@ -12,7 +12,7 @@ with open(package_dir / "requirements.txt") as fid: sys.path.append(str(package_dir)) -from version import __version__ +from _version import __version__ setup( name="mlx-whisper", From 83a209e200eef505a688997f64afdff2c6e0d363 Mon Sep 17 00:00:00 2001 From: Chime Ogbuji Date: Tue, 3 Sep 2024 16:29:10 -0400 Subject: [PATCH 024/188] Add prompt piping (#962) * Initial commit of --prompt-only and prompt from STDIN feature * Switch to using --verbose instead of --prompt-only * Fix capitalization typo * Fix reference to changed option name * Update exception text --- llms/mlx_lm/generate.py | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/llms/mlx_lm/generate.py b/llms/mlx_lm/generate.py index f37037b6..537bd853 100644 --- a/llms/mlx_lm/generate.py +++ b/llms/mlx_lm/generate.py @@ -2,6 +2,7 @@ import argparse import json +import sys import mlx.core as mx @@ -14,6 +15,10 @@ DEFAULT_TOP_P = 1.0 DEFAULT_SEED = 0 +def str2bool(string): + return string.lower() not in ["false", "f"] + + def setup_arg_parser(): """Set up and return the argument parser.""" parser = argparse.ArgumentParser(description="LLM inference script") @@ -39,7 +44,9 @@ def setup_arg_parser(): help="End of sequence token for tokenizer", ) parser.add_argument( - "--prompt", default=DEFAULT_PROMPT, help="Message to be processed by the model" + "--prompt", + default=DEFAULT_PROMPT, + help="Message to be processed by the model ('-' reads from stdin)", ) parser.add_argument( "--max-tokens", @@ -65,6 +72,12 @@ def setup_arg_parser(): action="store_true", help="Use the default chat template", ) + parser.add_argument( + "--verbose", + type=str2bool, + default=True, + help="Log verbose output when 'True' or 'T' or only print the response when 'False' or 'F'", + ) parser.add_argument( "--colorize", action="store_true", @@ -178,7 +191,12 @@ def main(): hasattr(tokenizer, "apply_chat_template") and tokenizer.chat_template is not None ): - messages = [{"role": "user", "content": args.prompt}] + messages = [ + { + "role": "user", + "content": sys.stdin.read() if args.prompt == "-" else args.prompt, + } + ] prompt = tokenizer.apply_chat_template( messages, tokenize=False, add_generation_prompt=True ) @@ -195,6 +213,8 @@ def main(): else: prompt = args.prompt + if args.colorize and not args.verbose: + raise ValueError("Cannot use --colorize with --verbose=False") formatter = colorprint_by_t0 if args.colorize else None # Determine the max kv size from the kv cache or passed arguments @@ -203,18 +223,20 @@ def main(): max_kv_size = metadata["max_kv_size"] max_kv_size = int(max_kv_size) if max_kv_size.isdigit() else None - generate( + response = generate( model, tokenizer, prompt, args.max_tokens, - verbose=True, + verbose=args.verbose, formatter=formatter, temp=args.temp, top_p=args.top_p, max_kv_size=max_kv_size, cache_history=cache_history, ) + if not args.verbose: + print(response) if __name__ == "__main__": From bd29aec299c8fa59c161a9c1207bfc59db31d845 Mon Sep 17 00:00:00 2001 From: madroid Date: Wed, 4 Sep 2024 21:19:32 +0800 Subject: [PATCH 025/188] Support HuggingFace model tree (#957) * Hub: Update quantization configuration fields * Hub: add base_model metadata * Hub: add quantization_config for model tree Quantized type * Hub: update quantization_config value * Hub: remove config print --- llms/mlx_lm/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index 71476df3..eee28c9c 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -560,6 +560,7 @@ def upload_to_hub(path: str, upload_repo: str, hf_path: str): card = ModelCard.load(hf_path) card.data.tags = ["mlx"] if card.data.tags is None else card.data.tags + ["mlx"] + card.data.base_model = hf_path card.text = dedent( f""" # {upload_repo} @@ -666,6 +667,8 @@ def quantize_model( quantized_config = copy.deepcopy(config) nn.quantize(model, q_group_size, q_bits) quantized_config["quantization"] = {"group_size": q_group_size, "bits": q_bits} + # support hf model tree #957 + quantized_config["quantization_config"] = quantized_config["quantization"] quantized_weights = dict(tree_flatten(model.parameters())) return quantized_weights, quantized_config From 324184d670ec11916a5e92314171d497b312eefe Mon Sep 17 00:00:00 2001 From: Angelos Katharopoulos Date: Fri, 6 Sep 2024 20:19:27 -0700 Subject: [PATCH 026/188] Fix the cache_prompt (#979) --- llms/mlx_lm/cache_prompt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/llms/mlx_lm/cache_prompt.py b/llms/mlx_lm/cache_prompt.py index fe088118..9829efb4 100644 --- a/llms/mlx_lm/cache_prompt.py +++ b/llms/mlx_lm/cache_prompt.py @@ -139,8 +139,8 @@ def main(): print("Saving...") cache_dict = {} for i, c in enumerate(cache): - cache_dict[f"{i}_keys"] = c.state[0] - cache_dict[f"{i}_values"] = c.state[1] + cache_dict[f"{i}_keys"] = c.state[0][..., : c.offset, :] + cache_dict[f"{i}_values"] = c.state[1][..., : c.offset, :] metadata = {} metadata["model"] = args.model metadata["chat_template"] = tokenizer.chat_template From c3e3411756e098a3f5f29d988e9221034f1af47c Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Sat, 7 Sep 2024 06:06:15 -0700 Subject: [PATCH 027/188] Update LLM generation docs to use chat template (#973) * fix docs * add template to model cards as well * revert * version --- llms/README.md | 14 +++++++++++++- llms/mlx_lm/_version.py | 2 +- llms/mlx_lm/utils.py | 11 ++++++++++- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/llms/README.md b/llms/README.md index 79f26d41..b8e1914d 100644 --- a/llms/README.md +++ b/llms/README.md @@ -29,7 +29,14 @@ from mlx_lm import load, generate model, tokenizer = load("mlx-community/Mistral-7B-Instruct-v0.3-4bit") -response = generate(model, tokenizer, prompt="hello", verbose=True) +prompt = "Write a story about Einstein" + +messages = [{"role": "user", "content": prompt}] +prompt = tokenizer.apply_chat_template( + messages, tokenize=False, add_generation_prompt=True +) + +response = generate(model, tokenizer, prompt=prompt, verbose=True) ``` To see a description of all the arguments you can do: @@ -79,6 +86,11 @@ model, tokenizer = load(repo) prompt = "Write a story about Einstein" +messages = [{"role": "user", "content": prompt}] +prompt = tokenizer.apply_chat_template( + messages, tokenize=False, add_generation_prompt=True +) + for t in stream_generate(model, tokenizer, prompt, max_tokens=512): print(t, end="", flush=True) print() diff --git a/llms/mlx_lm/_version.py b/llms/mlx_lm/_version.py index a2eb9a25..8110c823 100644 --- a/llms/mlx_lm/_version.py +++ b/llms/mlx_lm/_version.py @@ -1,3 +1,3 @@ # Copyright © 2023-2024 Apple Inc. -__version__ = "0.18.1" +__version__ = "0.18.2" diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index eee28c9c..ad9b3221 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -577,7 +577,16 @@ def upload_to_hub(path: str, upload_repo: str, hf_path: str): from mlx_lm import load, generate model, tokenizer = load("{upload_repo}") - response = generate(model, tokenizer, prompt="hello", verbose=True) + + prompt="hello" + + if hasattr(tokenizer, "apply_chat_template") and tokenizer.chat_template is not None: + messages = [{"role": "user", "content": prompt}] + prompt = tokenizer.apply_chat_template( + messages, tokenize=False, add_generation_prompt=True + ) + + response = generate(model, tokenizer, prompt=prompt, verbose=True) ``` """ ) From 6c2369e4b97f49fb5906ec46033497b39931b25d Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Sat, 7 Sep 2024 14:46:57 -0700 Subject: [PATCH 028/188] Fix bug in upload + docs nit (#981) * fix bug in upload + docs nit * nit --- llms/mlx_lm/LORA.md | 30 +++++++----------------------- llms/mlx_lm/utils.py | 2 +- 2 files changed, 8 insertions(+), 24 deletions(-) diff --git a/llms/mlx_lm/LORA.md b/llms/mlx_lm/LORA.md index 2e739d0f..2d9a2553 100644 --- a/llms/mlx_lm/LORA.md +++ b/llms/mlx_lm/LORA.md @@ -166,44 +166,28 @@ Currently, `*.jsonl` files support three data formats: `chat`, `chat`: ```jsonl -{ - "messages": [ - { - "role": "system", - "content": "You are a helpful assistant." - }, - { - "role": "user", - "content": "Hello." - }, - { - "role": "assistant", - "content": "How can I assistant you today." - } - ] -} +{"messages": [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "Hello."}, {"role": "assistant", "content": "How can I assistant you today."}]} ``` `completions`: ```jsonl -{ - "prompt": "What is the capital of France?", - "completion": "Paris." -} +{"prompt": "What is the capital of France?", "completion": "Paris."} ``` `text`: ```jsonl -{ - "text": "This is an example for the model." -} +{"text": "This is an example for the model."} ``` Note, the format is automatically determined by the dataset. Note also, keys in each line not expected by the loader will be ignored. +> [!NOTE] +> Each example in the datasets must be on a single line. Do not put more than +> one example per line and do not split an example accross multiple lines. + ### Hugging Face Datasets To use Hugging Face datasets, first install the `datasets` package: diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index ad9b3221..b4a2ea51 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -581,7 +581,7 @@ def upload_to_hub(path: str, upload_repo: str, hf_path: str): prompt="hello" if hasattr(tokenizer, "apply_chat_template") and tokenizer.chat_template is not None: - messages = [{"role": "user", "content": prompt}] + messages = [{{"role": "user", "content": prompt}}] prompt = tokenizer.apply_chat_template( messages, tokenize=False, add_generation_prompt=True ) From f530f56df2738a54982c4541189a8c8d7cd94c44 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Tue, 17 Sep 2024 16:22:48 -0700 Subject: [PATCH 029/188] don't use internal exception (#990) --- llms/mlx_lm/utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index b4a2ea51..5621609d 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -14,7 +14,6 @@ from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Type, import mlx.core as mx import mlx.nn as nn from huggingface_hub import snapshot_download -from huggingface_hub.utils._errors import RepositoryNotFoundError from mlx.utils import tree_flatten from transformers import PreTrainedTokenizer @@ -91,7 +90,7 @@ def get_model_path(path_or_hf_repo: str, revision: Optional[str] = None) -> Path ], ) ) - except RepositoryNotFoundError: + except: raise ModelNotFoundError( f"Model not found for path or HF repo: {path_or_hf_repo}.\n" "Please make sure you specified the local path or Hugging Face" From 796d5e40e4cce0e0d49d3b3b3c00957b31702fe0 Mon Sep 17 00:00:00 2001 From: Angelos Katharopoulos Date: Fri, 20 Sep 2024 13:33:45 -0700 Subject: [PATCH 030/188] Fix export to gguf (#993) --- llms/mlx_lm/gguf.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/llms/mlx_lm/gguf.py b/llms/mlx_lm/gguf.py index 5d524580..241ac35a 100644 --- a/llms/mlx_lm/gguf.py +++ b/llms/mlx_lm/gguf.py @@ -67,7 +67,7 @@ class HfVocab: def get_token_type( self, token_id: int, token_text: bytes, special_ids: Set[int] ) -> TokenType: - if re.fullmatch(rb"<0x[0-9A-Fa-f]{2}>", token_text.encode("utf-8")): + if re.fullmatch(r"<0x[0-9A-Fa-f]{2}>", token_text): return TokenType.BYTE return TokenType.CONTROL if token_id in special_ids else TokenType.NORMAL @@ -77,9 +77,7 @@ class HfVocab: def added_tokens(self) -> Iterable[Tuple[bytes, float, TokenType]]: for text in self.added_tokens_list: if text in self.specials: - toktype = self.get_token_type( - self.specials[text], b"", self.special_ids - ) + toktype = self.get_token_type(self.specials[text], "", self.special_ids) score = self.get_token_score(self.specials[text]) else: toktype = TokenType.USER_DEFINED @@ -243,15 +241,18 @@ def prepare_metadata(config, vocab): metadata["tokenizer.ggml.tokens"] = tokens metadata["tokenizer.ggml.scores"] = mx.array(scores, dtype=mx.float32) metadata["tokenizer.ggml.token_type"] = mx.array(toktypes, dtype=mx.uint32) - metadata["tokenizer.ggml.bos_token_id"] = mx.array( - vocab.tokenizer.bos_token_id, dtype=mx.uint32 - ) - metadata["tokenizer.ggml.eos_token_id"] = mx.array( - vocab.tokenizer.eos_token_id, dtype=mx.uint32 - ) - metadata["tokenizer.ggml.unknown_token_id"] = mx.array( - vocab.tokenizer.unk_token_id, dtype=mx.uint32 - ) + if vocab.tokenizer.bos_token_id is not None: + metadata["tokenizer.ggml.bos_token_id"] = mx.array( + vocab.tokenizer.bos_token_id, dtype=mx.uint32 + ) + if vocab.tokenizer.eos_token_id is not None: + metadata["tokenizer.ggml.eos_token_id"] = mx.array( + vocab.tokenizer.eos_token_id, dtype=mx.uint32 + ) + if vocab.tokenizer.unk_token_id is not None: + metadata["tokenizer.ggml.unknown_token_id"] = mx.array( + vocab.tokenizer.unk_token_id, dtype=mx.uint32 + ) metadata = {k: v for k, v in metadata.items() if v is not None} return metadata From 9bb2dd62f350d9f0f1a8003e354431b5172831e5 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Mon, 23 Sep 2024 11:39:25 -0700 Subject: [PATCH 031/188] Encodec (#991) * initial encodec * works * nits * use fast group norm * fix for rnn layer * fix mlx version * use custom LSTM kernel * audio encodec * fix example, support batched inference * nits --- README.md | 1 + encodec/README.md | 83 ++++ encodec/benchmarks/bench_mx.py | 30 ++ encodec/benchmarks/bench_pt.py | 34 ++ encodec/convert.py | 213 +++++++++++ encodec/encodec.py | 671 +++++++++++++++++++++++++++++++++ encodec/example.py | 37 ++ encodec/requirements.txt | 3 + encodec/test.py | 66 ++++ encodec/utils.py | 129 +++++++ 10 files changed, 1267 insertions(+) create mode 100644 encodec/README.md create mode 100644 encodec/benchmarks/bench_mx.py create mode 100644 encodec/benchmarks/bench_pt.py create mode 100644 encodec/convert.py create mode 100644 encodec/encodec.py create mode 100644 encodec/example.py create mode 100644 encodec/requirements.txt create mode 100644 encodec/test.py create mode 100644 encodec/utils.py diff --git a/README.md b/README.md index 00e57803..bd180975 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ Some more useful examples are listed below. ### Audio Models - Speech recognition with [OpenAI's Whisper](whisper). +- Audio compression and generation with [Meta's EnCodec](encodec). ### Multimodal models diff --git a/encodec/README.md b/encodec/README.md new file mode 100644 index 00000000..3ab2793c --- /dev/null +++ b/encodec/README.md @@ -0,0 +1,83 @@ +# EnCodec + +An example of Meta's EnCodec model in MLX.[^1] EnCodec is used to compress and +generate audio. + +### Setup + +Install the requirements: + +``` +pip install -r requirements.txt +``` + +Optionally install FFmpeg and SciPy for loading and saving audio files, +respectively. + +Install [FFmpeg](https://ffmpeg.org/): + +``` +# on macOS using Homebrew (https://brew.sh/) +brew install ffmpeg +``` + +Install SciPy: + +``` +pip install scipy +``` + +### Example + +An example using the model: + +```python +import mlx.core as mx +from utils import load, load_audio, save_audio + +# Load the 48 KHz model and preprocessor. +model, processor = load("mlx-community/encodec-48khz-float32") + +# Load an audio file +audio = load_audio("path/to/aduio", model.sampling_rate, model.channels) + +# Preprocess the audio (this can also be a list of arrays for batched +# processing). +feats, mask = processor(audio) + +# Encode at the given bandwidth. A lower bandwidth results in more +# compression but lower reconstruction quality. +@mx.compile +def encode(feats, mask): + return model.encode(feats, mask, bandwidth=3) + +# Decode to reconstruct the audio +@mx.compile +def decode(codes, scales, mask): + return model.decode(codes, scales, mask) + + +codes, scales = encode(feats, mask) +reconstructed = decode(codes, scales, mask) + +# Trim any padding: +reconstructed = reconstructed[0, : len(audio)] + +# Save the audio as a wave file +save_audio("reconstructed.wav", reconstructed, model.sampling_rate) +``` + +The 24 KHz, 32 KHz, and 48 KHz MLX formatted models are available in the +[Hugging Face MLX Community](https://huggingface.co/collections/mlx-community/encodec-66e62334038300b07a43b164) +in several data types. + +### Optional + +To convert models, use the `convert.py` script. To see the options, run: + +```bash +python convert.py -h +``` + +[^1]: Refer to the [arXiv paper](https://arxiv.org/abs/2210.13438) and + [code](https://github.com/facebookresearch/encodec) for more details. diff --git a/encodec/benchmarks/bench_mx.py b/encodec/benchmarks/bench_mx.py new file mode 100644 index 00000000..2acd4b75 --- /dev/null +++ b/encodec/benchmarks/bench_mx.py @@ -0,0 +1,30 @@ +# Copyright © 2024 Apple Inc. + +import time + +import mlx.core as mx +from utils import load + +model, processor = load("mlx-community/encodec-48khz-float32") + +audio = mx.random.uniform(shape=(288000, 2)) +feats, mask = processor(audio) +mx.eval(model, feats, mask) + + +@mx.compile +def fun(): + codes, scales = model.encode(feats, mask, bandwidth=3) + reconstructed = model.decode(codes, scales, mask) + return reconstructed + + +for _ in range(5): + mx.eval(fun()) + +tic = time.time() +for _ in range(10): + mx.eval(fun()) +toc = time.time() +ms = 1000 * (toc - tic) / 10 +print(f"Time per it: {ms:.3f}") diff --git a/encodec/benchmarks/bench_pt.py b/encodec/benchmarks/bench_pt.py new file mode 100644 index 00000000..5d158a32 --- /dev/null +++ b/encodec/benchmarks/bench_pt.py @@ -0,0 +1,34 @@ +# Copyright © 2024 Apple Inc. + +import time + +import numpy as np +import torch +from transformers import AutoProcessor, EncodecModel + +processor = AutoProcessor.from_pretrained("facebook/encodec_48khz") +audio = np.random.uniform(size=(2, 288000)).astype(np.float32) + +pt_model = EncodecModel.from_pretrained("facebook/encodec_48khz").to("mps") +pt_inputs = processor( + raw_audio=audio, sampling_rate=processor.sampling_rate, return_tensors="pt" +).to("mps") + + +def fun(): + pt_encoded = pt_model.encode(pt_inputs["input_values"], pt_inputs["padding_mask"]) + pt_audio = pt_model.decode( + pt_encoded.audio_codes, pt_encoded.audio_scales, pt_inputs["padding_mask"] + ) + torch.mps.synchronize() + + +for _ in range(5): + fun() + +tic = time.time() +for _ in range(10): + fun() +toc = time.time() +ms = 1000 * (toc - tic) / 10 +print(f"Time per it: {ms:.3f}") diff --git a/encodec/convert.py b/encodec/convert.py new file mode 100644 index 00000000..13bd31a6 --- /dev/null +++ b/encodec/convert.py @@ -0,0 +1,213 @@ +# Copyright © 2024 Apple Inc. + +import argparse +import json +from pathlib import Path +from textwrap import dedent +from types import SimpleNamespace +from typing import Any, Dict, Union + +import mlx.core as mx +import mlx.nn as nn +from huggingface_hub import snapshot_download +from mlx.utils import tree_flatten + +import encodec + + +def fetch_from_hub(hf_repo: str) -> Path: + model_path = Path( + snapshot_download( + repo_id=hf_repo, + allow_patterns=["*.json", "*.safetensors"], + ) + ) + return model_path + + +def upload_to_hub(path: str, upload_repo: str, hf_path: str): + """ + Uploads the model to Hugging Face hub. + + Args: + path (str): Local path to the model. + upload_repo (str): Name of the HF repo to upload to. + hf_path (str): Path to the original Hugging Face model. + """ + import os + + from huggingface_hub import HfApi, ModelCard, logging + + content = dedent( + f""" + --- + language: en + license: other + library: mlx + tags: + - mlx + --- + + The Model [{upload_repo}](https://huggingface.co/{upload_repo}) was + converted to MLX format from + [{hf_path}](https://huggingface.co/{hf_path}). + + This model is intended to be used with the [EnCodec MLX + example](https://github.com/ml-explore/mlx-examples/tree/main/encodec). + """ + ) + + card = ModelCard(content) + card.save(os.path.join(path, "README.md")) + + logging.set_verbosity_info() + + api = HfApi() + api.create_repo(repo_id=upload_repo, exist_ok=True) + api.upload_folder( + folder_path=path, + repo_id=upload_repo, + repo_type="model", + multi_commits=True, + multi_commits_verbose=True, + ) + print(f"Upload successful, go to https://huggingface.co/{upload_repo} for details.") + + +def save_weights(save_path: Union[str, Path], weights: Dict[str, Any]) -> None: + if isinstance(save_path, str): + save_path = Path(save_path) + save_path.mkdir(parents=True, exist_ok=True) + + total_size = sum(v.nbytes for v in weights.values()) + index_data = {"metadata": {"total_size": total_size}, "weight_map": {}} + mx.save_safetensors( + str(save_path / "model.safetensors"), weights, metadata={"format": "mlx"} + ) + + for weight_name in weights.keys(): + index_data["weight_map"][weight_name] = "model.safetensors" + + index_data["weight_map"] = { + k: index_data["weight_map"][k] for k in sorted(index_data["weight_map"]) + } + + with open(save_path / "model.safetensors.index.json", "w") as f: + json.dump(index_data, f, indent=4) + + +def save_config( + config: dict, + config_path: Union[str, Path], +) -> None: + """Save the model configuration to the ``config_path``. + + The final configuration will be sorted before saving for better readability. + + Args: + config (dict): The model configuration. + config_path (Union[str, Path]): Model configuration file path. + """ + # Clean unused keys + config.pop("_name_or_path", None) + + # sort the config for better readability + config = dict(sorted(config.items())) + + # write the updated config to the config_path (if provided) + with open(config_path, "w") as fid: + json.dump(config, fid, indent=4) + + +def convert( + upload: bool, + model: str, + dtype: str = None, +): + hf_repo = f"facebook/encodec_{model}" + mlx_repo = f"mlx-community/encodec-{model}-{dtype}" + path = fetch_from_hub(hf_repo) + save_path = Path("mlx_models") + + weights = mx.load(str(Path(path) / "model.safetensors")) + + with open(path / "config.json", "r") as fid: + config = SimpleNamespace(**json.load(fid)) + + model = encodec.EncodecModel(config) + + new_weights = {} + for k, v in weights.items(): + basename, pname = k.rsplit(".", 1) + if pname == "weight_v": + g = weights[basename + ".weight_g"] + v = g * (v / mx.linalg.norm(v, axis=(1, 2), keepdims=True)) + k = basename + ".weight" + elif pname in ["weight_g", "embed_avg", "cluster_size", "inited"]: + continue + elif "lstm" in basename: + w_or_b, ih_or_hh, ln = pname.split("_") + if w_or_b == "weight": + new_pname = "Wx" if ih_or_hh == "ih" else "Wh" + elif w_or_b == "bias" and ih_or_hh == "ih": + continue + else: + v = v + weights[k.replace("_hh_", "_ih_")] + new_pname = "bias" + k = basename + "." + ln[1:] + "." + new_pname + if "conv.weight" in k: + # Possibly a transposed conv which has a different order + if "decoder" in k: + ln = int(k.split(".")[2]) + if "conv" in model.decoder.layers[ln] and isinstance( + model.decoder.layers[ln].conv, nn.ConvTranspose1d + ): + v = mx.moveaxis(v, 0, 2) + else: + v = mx.moveaxis(v, 1, 2) + else: + v = mx.moveaxis(v, 1, 2) + + new_weights[k] = v + weights = new_weights + + model.load_weights(list(weights.items())) + + if dtype is not None: + t = getattr(mx, dtype) + weights = {k: v.astype(t) for k, v in weights.items()} + + if isinstance(save_path, str): + save_path = Path(save_path) + + save_weights(save_path, weights) + + save_config(vars(config), config_path=save_path / "config.json") + + if upload: + upload_to_hub(save_path, mlx_repo, hf_repo) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Convert EnCodec weights to MLX.") + parser.add_argument( + "--model", + type=str, + default="48khz", + help="", + choices=["24khz", "32khz", "48khz"], + ) + parser.add_argument( + "--upload", + action="store_true", + help="Upload the weights to Hugging Face.", + ) + parser.add_argument( + "--dtype", + type=str, + help="Data type to convert the model to.", + default="float32", + choices=["float32", "bfloat16", "float16"], + ) + args = parser.parse_args() + convert(upload=args.upload, model=args.model, dtype=args.dtype) diff --git a/encodec/encodec.py b/encodec/encodec.py new file mode 100644 index 00000000..3ef47369 --- /dev/null +++ b/encodec/encodec.py @@ -0,0 +1,671 @@ +# Copyright © 2024 Apple Inc. + +import math +from dataclasses import dataclass +from typing import List, Optional, Tuple, Union + +import mlx.core as mx +import mlx.nn as nn +import numpy as np + +_lstm_kernel = mx.fast.metal_kernel( + name="lstm", + input_names=["x", "h_in", "cell", "hidden_size", "time_step", "num_time_steps"], + output_names=["hidden_state", "cell_state"], + header=""" + template + T sigmoid(T x) { + auto y = 1 / (1 + metal::exp(-metal::abs(x))); + return (x < 0) ? 1 - y : y; + } + """, + source=""" + uint b = thread_position_in_grid.x; + uint d = hidden_size * 4; + + uint elem = b * d + thread_position_in_grid.y; + uint index = elem; + uint x_index = b * num_time_steps * d + time_step * d + index; + + auto i = sigmoid(h_in[index] + x[x_index]); + index += hidden_size; + x_index += hidden_size; + auto f = sigmoid(h_in[index] + x[x_index]); + index += hidden_size; + x_index += hidden_size; + auto g = metal::precise::tanh(h_in[index] + x[x_index]); + index += hidden_size; + x_index += hidden_size; + auto o = sigmoid(h_in[index] + x[x_index]); + + cell_state[elem] = f * cell[elem] + i * g; + hidden_state[elem] = o * metal::precise::tanh(cell_state[elem]); + """, +) + + +def lstm_custom(x, h_in, cell, time_step): + assert x.ndim == 3, "Input to LSTM must have 3 dimensions." + out_shape = cell.shape + return _lstm_kernel( + inputs=[x, h_in, cell, out_shape[-1], time_step, x.shape[-2]], + output_shapes=[out_shape, out_shape], + output_dtypes=[h_in.dtype, h_in.dtype], + grid=(x.shape[0], h_in.size // 4, 1), + threadgroup=(256, 1, 1), + ) + + +class LSTM(nn.Module): + def __init__( + self, + input_size: int, + hidden_size: int, + bias: bool = True, + ): + super().__init__() + + self.hidden_size = hidden_size + self.Wx = mx.zeros((4 * hidden_size, input_size)) + self.Wh = mx.zeros((4 * hidden_size, hidden_size)) + self.bias = mx.zeros((4 * hidden_size,)) if bias else None + + def __call__(self, x, hidden=None, cell=None): + if self.bias is not None: + x = mx.addmm(self.bias, x, self.Wx.T) + else: + x = x @ self.Wx.T + + all_hidden = [] + + B = x.shape[0] + cell = cell or mx.zeros((B, self.hidden_size), x.dtype) + for t in range(x.shape[-2]): + if hidden is None: + hidden = mx.zeros((B, self.hidden_size * 4), x.dtype) + else: + hidden = hidden @ self.Wh.T + hidden, cell = lstm_custom(x, hidden, cell, t) + all_hidden.append(hidden) + + return mx.stack(all_hidden, axis=-2) + + +class EncodecConv1d(nn.Module): + """Conv1d with asymmetric or causal padding and normalization.""" + + def __init__( + self, + config, + in_channels: int, + out_channels: int, + kernel_size: int, + stride: int = 1, + dilation: int = 1, + ): + super().__init__() + self.causal = config.use_causal_conv + self.pad_mode = config.pad_mode + self.norm_type = config.norm_type + + self.conv = nn.Conv1d( + in_channels, out_channels, kernel_size, stride, dilation=dilation + ) + if self.norm_type == "time_group_norm": + self.norm = nn.GroupNorm(1, out_channels, pytorch_compatible=True) + + self.stride = stride + + # Effective kernel size with dilations. + self.kernel_size = (kernel_size - 1) * dilation + 1 + + self.padding_total = kernel_size - stride + + def _get_extra_padding_for_conv1d( + self, + hidden_states: mx.array, + ) -> mx.array: + length = hidden_states.shape[1] + n_frames = (length - self.kernel_size + self.padding_total) / self.stride + 1 + n_frames = int(math.ceil(n_frames)) - 1 + ideal_length = n_frames * self.stride + self.kernel_size - self.padding_total + return ideal_length - length + + def _pad1d( + self, + hidden_states: mx.array, + paddings: Tuple[int, int], + mode: str = "zero", + value: float = 0.0, + ): + if mode != "reflect": + return mx.pad( + hidden_states, paddings, mode="constant", constant_values=value + ) + + length = hidden_states.shape[1] + prefix = hidden_states[:, 1 : paddings[0] + 1][:, ::-1] + suffix = hidden_states[:, max(length - (paddings[1] + 1), 0) : -1][:, ::-1] + return mx.concatenate([prefix, hidden_states, suffix], axis=1) + + def __call__(self, hidden_states): + extra_padding = self._get_extra_padding_for_conv1d(hidden_states) + + if self.causal: + # Left padding for causal + hidden_states = self._pad1d( + hidden_states, (self.padding_total, extra_padding), mode=self.pad_mode + ) + else: + # Asymmetric padding required for odd strides + padding_right = self.padding_total // 2 + padding_left = self.padding_total - padding_right + hidden_states = self._pad1d( + hidden_states, + (padding_left, padding_right + extra_padding), + mode=self.pad_mode, + ) + + hidden_states = self.conv(hidden_states) + + if self.norm_type == "time_group_norm": + hidden_states = self.norm(hidden_states) + + return hidden_states + + +class EncodecConvTranspose1d(nn.Module): + """ConvTranspose1d with asymmetric or causal padding and normalization.""" + + def __init__( + self, + config, + in_channels: int, + out_channels: int, + kernel_size: int, + stride: int = 1, + ): + super().__init__() + self.causal = config.use_causal_conv + self.trim_right_ratio = config.trim_right_ratio + self.norm_type = config.norm_type + self.conv = nn.ConvTranspose1d(in_channels, out_channels, kernel_size, stride) + if config.norm_type == "time_group_norm": + self.norm = nn.GroupNorm(1, out_channels, pytorch_compatible=True) + self.padding_total = kernel_size - stride + + def __call__(self, hidden_states): + hidden_states = self.conv(hidden_states) + + if self.norm_type == "time_group_norm": + hidden_states = self.norm(hidden_states) + + if self.causal: + padding_right = math.ceil(self.padding_total * self.trim_right_ratio) + else: + padding_right = self.padding_total // 2 + + padding_left = self.padding_total - padding_right + + end = hidden_states.shape[1] - padding_right + hidden_states = hidden_states[:, padding_left:end, :] + return hidden_states + + +class EncodecLSTM(nn.Module): + def __init__(self, config, dimension): + super().__init__() + self.lstm = [LSTM(dimension, dimension) for _ in range(config.num_lstm_layers)] + + def __call__(self, hidden_states): + h = hidden_states + for lstm in self.lstm: + h = lstm(h) + return h + hidden_states + + +class EncodecResnetBlock(nn.Module): + """ + Residual block from SEANet model as used by EnCodec. + """ + + def __init__(self, config, dim: int, dilations: List[int]): + super().__init__() + kernel_sizes = (config.residual_kernel_size, 1) + if len(kernel_sizes) != len(dilations): + raise ValueError("Number of kernel sizes should match number of dilations") + + hidden = dim // config.compress + block = [] + for i, (kernel_size, dilation) in enumerate(zip(kernel_sizes, dilations)): + in_chs = dim if i == 0 else hidden + out_chs = dim if i == len(kernel_sizes) - 1 else hidden + block += [nn.ELU()] + block += [ + EncodecConv1d(config, in_chs, out_chs, kernel_size, dilation=dilation) + ] + self.block = block + + if getattr(config, "use_conv_shortcut", True): + self.shortcut = EncodecConv1d(config, dim, dim, kernel_size=1) + else: + self.shortcut = nn.Identity() + + def __call__(self, hidden_states): + residual = hidden_states + for layer in self.block: + hidden_states = layer(hidden_states) + + return self.shortcut(residual) + hidden_states + + +class EncodecEncoder(nn.Module): + """SEANet encoder as used by EnCodec.""" + + def __init__(self, config): + super().__init__() + model = [ + EncodecConv1d( + config, config.audio_channels, config.num_filters, config.kernel_size + ) + ] + scaling = 1 + + for ratio in reversed(config.upsampling_ratios): + current_scale = scaling * config.num_filters + for j in range(config.num_residual_layers): + model += [ + EncodecResnetBlock( + config, current_scale, [config.dilation_growth_rate**j, 1] + ) + ] + model += [nn.ELU()] + model += [ + EncodecConv1d( + config, + current_scale, + current_scale * 2, + kernel_size=ratio * 2, + stride=ratio, + ) + ] + scaling *= 2 + + model += [EncodecLSTM(config, scaling * config.num_filters)] + model += [nn.ELU()] + model += [ + EncodecConv1d( + config, + scaling * config.num_filters, + config.hidden_size, + config.last_kernel_size, + ) + ] + + self.layers = model + + def __call__(self, hidden_states): + for layer in self.layers: + hidden_states = layer(hidden_states) + return hidden_states + + +class EncodecDecoder(nn.Module): + """SEANet decoder as used by EnCodec.""" + + def __init__(self, config): + super().__init__() + scaling = int(2 ** len(config.upsampling_ratios)) + model = [ + EncodecConv1d( + config, + config.hidden_size, + scaling * config.num_filters, + config.kernel_size, + ) + ] + + model += [EncodecLSTM(config, scaling * config.num_filters)] + + for ratio in config.upsampling_ratios: + current_scale = scaling * config.num_filters + model += [nn.ELU()] + model += [ + EncodecConvTranspose1d( + config, + current_scale, + current_scale // 2, + kernel_size=ratio * 2, + stride=ratio, + ) + ] + for j in range(config.num_residual_layers): + model += [ + EncodecResnetBlock( + config, current_scale // 2, (config.dilation_growth_rate**j, 1) + ) + ] + scaling //= 2 + + model += [nn.ELU()] + model += [ + EncodecConv1d( + config, + config.num_filters, + config.audio_channels, + config.last_kernel_size, + ) + ] + self.layers = model + + def __call__(self, hidden_states): + for layer in self.layers: + hidden_states = layer(hidden_states) + return hidden_states + + +class EncodecEuclideanCodebook(nn.Module): + """Codebook with Euclidean distance.""" + + def __init__(self, config): + super().__init__() + self.embed = mx.zeros((config.codebook_size, config.codebook_dim)) + + def quantize(self, hidden_states): + embed = self.embed.T + scaled_states = hidden_states.square().sum(axis=1, keepdims=True) + dist = -( + scaled_states + - 2 * hidden_states @ embed + + embed.square().sum(axis=0, keepdims=True) + ) + embed_ind = dist.argmax(axis=-1) + return embed_ind + + def encode(self, hidden_states): + shape = hidden_states.shape + hidden_states = hidden_states.reshape((-1, shape[-1])) + embed_ind = self.quantize(hidden_states) + embed_ind = embed_ind.reshape(*shape[:-1]) + return embed_ind + + def decode(self, embed_ind): + return self.embed[embed_ind] + + +class EncodecVectorQuantization(nn.Module): + """ + Vector quantization implementation. Currently supports only euclidean distance. + """ + + def __init__(self, config): + super().__init__() + self.codebook = EncodecEuclideanCodebook(config) + + def encode(self, hidden_states): + return self.codebook.encode(hidden_states) + + def decode(self, embed_ind): + return self.codebook.decode(embed_ind) + + +class EncodecResidualVectorQuantizer(nn.Module): + """Residual Vector Quantizer.""" + + def __init__(self, config): + super().__init__() + self.codebook_size = config.codebook_size + + hop_length = np.prod(config.upsampling_ratios) + self.frame_rate = math.ceil(config.sampling_rate / hop_length) + self.num_quantizers = int( + 1000 * config.target_bandwidths[-1] // (self.frame_rate * 10) + ) + self.layers = [ + EncodecVectorQuantization(config) for _ in range(self.num_quantizers) + ] + + def get_num_quantizers_for_bandwidth( + self, bandwidth: Optional[float] = None + ) -> int: + """Return num_quantizers based on specified target bandwidth.""" + bw_per_q = math.log2(self.codebook_size) * self.frame_rate + num_quantizers = self.num_quantizers + if bandwidth is not None and bandwidth > 0.0: + num_quantizers = int(max(1, math.floor(bandwidth * 1000 / bw_per_q))) + return num_quantizers + + def encode( + self, embeddings: mx.array, bandwidth: Optional[float] = None + ) -> mx.array: + """ + Encode a given input array with the specified frame rate at the given + bandwidth. The RVQ encode method sets the appropriate number of + quantizers to use and returns indices for each quantizer. + """ + num_quantizers = self.get_num_quantizers_for_bandwidth(bandwidth) + residual = embeddings + all_indices = [] + for layer in self.layers[:num_quantizers]: + indices = layer.encode(residual) + quantized = layer.decode(indices) + residual = residual - quantized + all_indices.append(indices) + out_indices = mx.stack(all_indices, axis=1) + return out_indices + + def decode(self, codes: mx.array) -> mx.array: + """Decode the given codes to the quantized representation.""" + quantized_out = None + for i, indices in enumerate(codes.split(codes.shape[1], axis=1)): + layer = self.layers[i] + quantized = layer.decode(indices.squeeze(1)) + if quantized_out is None: + quantized_out = quantized + else: + quantized_out = quantized + quantized_out + return quantized_out + + +class EncodecModel(nn.Module): + def __init__(self, config): + super().__init__() + self.config = config + self.encoder = EncodecEncoder(config) + self.decoder = EncodecDecoder(config) + self.quantizer = EncodecResidualVectorQuantizer(config) + + def _encode_frame( + self, input_values: mx.array, bandwidth: float, padding_mask: mx.array + ) -> Tuple[mx.array, Optional[mx.array]]: + """ + Encodes the given input using the underlying VQVAE. + """ + length = input_values.shape[1] + duration = length / self.config.sampling_rate + + if ( + self.config.chunk_length_s is not None + and duration > 1e-5 + self.config.chunk_length_s + ): + raise RuntimeError( + f"Duration of frame ({duration}) is longer than chunk {self.config.chunk_length_s}" + ) + + scale = None + if self.config.normalize: + # if the padding is non zero + input_values = input_values * padding_mask[..., None] + mono = mx.sum(input_values, axis=2, keepdims=True) / input_values.shape[2] + scale = mono.square().mean(axis=1, keepdims=True).sqrt() + 1e-8 + input_values = input_values / scale + + embeddings = self.encoder(input_values) + codes = self.quantizer.encode(embeddings, bandwidth) + return codes, scale + + def encode( + self, + input_values: mx.array, + padding_mask: mx.array = None, + bandwidth: Optional[float] = None, + ) -> Tuple[mx.array, Optional[mx.array]]: + """ + Encodes the input audio waveform into discrete codes. + + Args: + input_values (mx.array): The input audio waveform with shape + ``(batch_size, channels, sequence_length)``. + padding_mask (mx.array): Padding mask used to pad the ``input_values``. + bandwidth (float, optional): The target bandwidth. Must be one of + ``config.target_bandwidths``. If ``None``, uses the smallest + possible bandwidth. bandwidth is represented as a thousandth of + what it is, e.g. 6kbps bandwidth is represented as bandwidth == 6.0 + + Returns: + A list of frames containing the discrete encoded codes for the + input audio waveform, along with rescaling factors for each chunk + when ``config.normalize==True``. Each frame is a tuple ``(codebook, + scale)``, with ``codebook`` of shape ``(batch_size, num_codebooks, + frames)``. + """ + + if bandwidth is None: + bandwidth = self.config.target_bandwidths[0] + if bandwidth not in self.config.target_bandwidths: + raise ValueError( + f"This model doesn't support the bandwidth {bandwidth}. " + f"Select one of {self.config.target_bandwidths}." + ) + + _, input_length, channels = input_values.shape + + if channels < 1 or channels > 2: + raise ValueError( + f"Number of audio channels must be 1 or 2, but got {channels}" + ) + + chunk_length = self.chunk_length + if chunk_length is None: + chunk_length = input_length + stride = input_length + else: + stride = self.chunk_stride + + if padding_mask is None: + padding_mask = mx.ones(input_values.shape[:2], dtype=mx.bool_) + encoded_frames = [] + scales = [] + + step = chunk_length - stride + if (input_length % stride) != step: + raise ValueError( + "The input length is not properly padded for batched chunked " + "encoding. Make sure to pad the input correctly." + ) + + for offset in range(0, input_length - step, stride): + mask = padding_mask[:, offset : offset + chunk_length].astype(mx.bool_) + frame = input_values[:, offset : offset + chunk_length] + encoded_frame, scale = self._encode_frame(frame, bandwidth, mask) + encoded_frames.append(encoded_frame) + scales.append(scale) + + encoded_frames = mx.stack(encoded_frames) + + return (encoded_frames, scales) + + @staticmethod + def _linear_overlap_add(frames: List[mx.array], stride: int): + if len(frames) == 0: + raise ValueError("`frames` cannot be an empty list.") + + dtype = frames[0].dtype + N, frame_length, C = frames[0].shape + total_size = stride * (len(frames) - 1) + frames[-1].shape[1] + + time_vec = mx.linspace(0, 1, frame_length + 2, dtype=dtype)[1:-1] + weight = 0.5 - (time_vec - 0.5).abs() + + weight = weight[:, None] + sum_weight = mx.zeros((total_size, 1), dtype=dtype) + out = mx.zeros((N, total_size, C), dtype=dtype) + offset = 0 + + for frame in frames: + frame_length = frame.shape[1] + out[:, offset : offset + frame_length] += weight[:frame_length] * frame + sum_weight[offset : offset + frame_length] += weight[:frame_length] + offset += stride + + return out / sum_weight + + def _decode_frame( + self, codes: mx.array, scale: Optional[mx.array] = None + ) -> mx.array: + embeddings = self.quantizer.decode(codes) + outputs = self.decoder(embeddings) + if scale is not None: + outputs = outputs * scale + return outputs + + @property + def channels(self): + return self.config.audio_channels + + @property + def sampling_rate(self): + return self.config.sampling_rate + + @property + def chunk_length(self): + if self.config.chunk_length_s is None: + return None + else: + return int(self.config.chunk_length_s * self.config.sampling_rate) + + @property + def chunk_stride(self): + if self.config.chunk_length_s is None or self.config.overlap is None: + return None + else: + return max(1, int((1.0 - self.config.overlap) * self.chunk_length)) + + def decode( + self, + audio_codes: mx.array, + audio_scales: Union[mx.array, List[mx.array]], + padding_mask: Optional[mx.array] = None, + ) -> Tuple[mx.array, mx.array]: + """ + Decodes the given frames into an output audio waveform. + + Note that the output might be a bit bigger than the input. In that + case, any extra steps at the end should be trimmed. + + Args: + audio_codes (mx.array): Discret code embeddings of shape + ``(batch_size, nb_chunks, chunk_length)``. + audio_scales (mx.array): Scaling factor for each input. + padding_mask (mx.array): Padding mask. + """ + chunk_length = self.chunk_length + if chunk_length is None: + if audio_codes.shape[1] != 1: + raise ValueError(f"Expected one frame, got {len(audio_codes)}") + audio_values = self._decode_frame(audio_codes[:, 0], audio_scales[0]) + else: + decoded_frames = [] + + for frame, scale in zip(audio_codes, audio_scales): + frames = self._decode_frame(frame, scale) + decoded_frames.append(frames) + + audio_values = self._linear_overlap_add( + decoded_frames, self.chunk_stride or 1 + ) + + # truncate based on padding mask + if padding_mask is not None and padding_mask.shape[1] < audio_values.shape[1]: + audio_values = audio_values[:, : padding_mask.shape[1]] + return audio_values diff --git a/encodec/example.py b/encodec/example.py new file mode 100644 index 00000000..97b311a1 --- /dev/null +++ b/encodec/example.py @@ -0,0 +1,37 @@ +# Copyright © 2024 Apple Inc. + +import mlx.core as mx +from utils import load, load_audio, save_audio + +# Load the 48 KHz model and preprocessor. +model, processor = load("mlx-community/encodec-48khz-float32") + +# Load an audio file +audio = load_audio("/path/to/audio", model.sampling_rate, model.channels) + +# Preprocess the audio (this can also be a list of arrays for batched +# processing). +feats, mask = processor(audio) + + +# Encode at the given bandwidth. A lower bandwidth results in more +# compression but lower reconstruction quality. +@mx.compile +def encode(feats, mask): + return model.encode(feats, mask, bandwidth=3) + + +# Decode to reconstruct the audio +@mx.compile +def decode(codes, scales, mask): + return model.decode(codes, scales, mask) + + +codes, scales = encode(feats, mask) +reconstructed = decode(codes, scales, mask) + +# Trim any padding: +reconstructed = reconstructed[0, : len(audio)] + +# Save the audio as a wave file +save_audio("reconstructed.wav", reconstructed, model.sampling_rate) diff --git a/encodec/requirements.txt b/encodec/requirements.txt new file mode 100644 index 00000000..de5cc646 --- /dev/null +++ b/encodec/requirements.txt @@ -0,0 +1,3 @@ +mlx>=0.18 +numpy +huggingface_hub diff --git a/encodec/test.py b/encodec/test.py new file mode 100644 index 00000000..ffc23505 --- /dev/null +++ b/encodec/test.py @@ -0,0 +1,66 @@ +# Copyright © 2024 Apple Inc. + +import mlx.core as mx +import numpy as np +import torch +from datasets import Audio, load_dataset +from transformers import AutoProcessor, EncodecModel +from utils import load, load_audio, preprocess_audio + + +def compare_processors(): + np.random.seed(0) + audio_length = 95500 + audio = np.random.uniform(size=(2, audio_length)).astype(np.float32) + + processor = AutoProcessor.from_pretrained("facebook/encodec_48khz") + + pt_inputs = processor( + raw_audio=audio, sampling_rate=processor.sampling_rate, return_tensors="pt" + ) + mx_inputs = preprocess_audio( + mx.array(audio).T, + processor.sampling_rate, + processor.chunk_length, + processor.chunk_stride, + ) + + assert np.array_equal(pt_inputs["input_values"], mx_inputs[0].moveaxis(2, 1)) + assert np.array_equal(pt_inputs["padding_mask"], mx_inputs[1]) + + +def compare_models(): + pt_model = EncodecModel.from_pretrained("facebook/encodec_48khz") + mx_model, _ = load("mlx-community/encodec-48khz-float32") + + np.random.seed(0) + audio_length = 190560 + audio = np.random.uniform(size=(1, audio_length, 2)).astype(np.float32) + mask = np.ones((1, audio_length), dtype=np.int32) + pt_encoded = pt_model.encode( + torch.tensor(audio).moveaxis(2, 1), torch.tensor(mask)[None] + ) + mx_encoded = mx_model.encode(mx.array(audio), mx.array(mask)) + pt_codes = pt_encoded.audio_codes.numpy() + mx_codes = mx_encoded[0] + assert np.array_equal(pt_codes, mx_codes), "Encoding codes mismatch" + + for mx_scale, pt_scale in zip(mx_encoded[1], pt_encoded.audio_scales): + if mx_scale is not None: + pt_scale = pt_scale.numpy() + assert np.allclose(pt_scale, mx_scale, atol=1e-3, rtol=1e-4) + + pt_audio = pt_model.decode( + pt_encoded.audio_codes, pt_encoded.audio_scales, torch.tensor(mask)[None] + ) + pt_audio = pt_audio[0].squeeze().T.detach().numpy() + mx_audio = mx_model.decode(*mx_encoded, mx.array(mask)) + mx_audio = mx_audio.squeeze() + assert np.allclose( + pt_audio, mx_audio, atol=1e-4, rtol=1e-4 + ), "Decoding audio mismatch" + + +if __name__ == "__main__": + compare_processors() + compare_models() diff --git a/encodec/utils.py b/encodec/utils.py new file mode 100644 index 00000000..18b3f063 --- /dev/null +++ b/encodec/utils.py @@ -0,0 +1,129 @@ +# Copyright © 2024 Apple Inc. + +import functools +import json +from pathlib import Path +from types import SimpleNamespace +from typing import List, Optional, Union + +import mlx.core as mx +import numpy as np +from huggingface_hub import snapshot_download + +import encodec + + +def save_audio(file: str, audio: mx.array, sampling_rate: int): + """ + Save audio to a wave (.wav) file. + """ + from scipy.io.wavfile import write + + audio = (audio * 32767).astype(mx.int16) + write(file, sampling_rate, np.array(audio)) + + +def load_audio(file: str, sampling_rate: int, channels: int): + """ + Read audio into an mx.array, resampling if necessary. + + Args: + file (str): The audio file to open. + sampling_rate (int): The sample rate to resample the audio at if needed. + channels (int): The number of audio channels. + + Returns: + An mx.array containing the audio waveform in float32. + """ + from subprocess import CalledProcessError, run + + # This launches a subprocess to decode audio while down-mixing + # and resampling as necessary. Requires the ffmpeg CLI in PATH. + # fmt: off + cmd = [ + "ffmpeg", + "-nostdin", + "-threads", "0", + "-i", file, + "-f", "s16le", + "-ac", str(channels), + "-acodec", "pcm_s16le", + "-ar", str(sampling_rate), + "-" + ] + # fmt: on + try: + out = run(cmd, capture_output=True, check=True).stdout + except CalledProcessError as e: + raise RuntimeError(f"Failed to load audio: {e.stderr.decode()}") from e + + out = mx.array(np.frombuffer(out, np.int16)) + return out.reshape(-1, channels).astype(mx.float32) / 32767.0 + + +def preprocess_audio( + raw_audio: Union[mx.array, List[mx.array]], + sampling_rate: int = 24000, + chunk_length: Optional[int] = None, + chunk_stride: Optional[int] = None, +): + r""" + Prepare inputs for the EnCodec model. + + Args: + raw_audio (mx.array or List[mx.array]): The sequence or batch of + sequences to be processed. + sampling_rate (int): The sampling rate at which the audio waveform + should be digitalized. + chunk_length (int, optional): The model's chunk length. + chunk_stride (int, optional): The model's chunk stride. + """ + if not isinstance(raw_audio, list): + raw_audio = [raw_audio] + + raw_audio = [x[..., None] if x.ndim == 1 else x for x in raw_audio] + + max_length = max(array.shape[0] for array in raw_audio) + if chunk_length is not None: + max_length += chunk_length - (max_length % chunk_stride) + + inputs = [] + masks = [] + for x in raw_audio: + length = x.shape[0] + mask = mx.ones((length,), dtype=mx.bool_) + difference = max_length - length + if difference > 0: + mask = mx.pad(mask, (0, difference)) + x = mx.pad(x, ((0, difference), (0, 0))) + inputs.append(x) + masks.append(mask) + return mx.stack(inputs), mx.stack(masks) + + +def load(path_or_repo): + """ + Load the model and audo preprocessor. + """ + path = Path(path_or_repo) + if not path.exists(): + path = Path( + snapshot_download( + repo_id=path_or_repo, + allow_patterns=["*.json", "*.safetensors", "*.model"], + ) + ) + + with open(path / "config.json", "r") as f: + config = SimpleNamespace(**json.load(f)) + + model = encodec.EncodecModel(config) + model.load_weights(str(path / "model.safetensors")) + processor = functools.partial( + preprocess_audio, + sampling_rate=config.sampling_rate, + chunk_length=model.chunk_length, + chunk_stride=model.chunk_stride, + ) + mx.eval(model) + return model, processor From e776c970f708e7a64e192d92cd90f100105a4fd6 Mon Sep 17 00:00:00 2001 From: Cheng Date: Wed, 25 Sep 2024 23:19:41 +0900 Subject: [PATCH 032/188] Fix llava model when using text-only prompt (#998) --- llava/llava.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/llava/llava.py b/llava/llava.py index 06e56059..9e6b7511 100644 --- a/llava/llava.py +++ b/llava/llava.py @@ -68,11 +68,10 @@ class LlavaModel(nn.Module): input_ids: Optional[mx.array] = None, pixel_values: Optional[mx.array] = None, ): - if pixel_values is None: - return self.language_model(input_ids) - # Get the input embeddings from the language model inputs_embeds = self.language_model.model.embed_tokens(input_ids) + if pixel_values is None: + return inputs_embeds # Get the ouptut hidden states from the vision model *_, hidden_states = self.vision_tower( From 76710f61af401457e505b851ff729d93489e07b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6kdeniz=20G=C3=BClmez?= <60228478+Goekdeniz-Guelmez@users.noreply.github.com> Date: Sat, 28 Sep 2024 16:02:53 +0200 Subject: [PATCH 033/188] Adding support for mamba (#940) * initial commit * initial commit * Adding first lines * adding x, and dt projection layers * adding the clamping mechanism * First succesful inference * last commit for today - added custom geenrate function and it works as expected, will try training and then with loading a model from the hub * clean up * save up * almost * update * update * fixed cache handeling * fixed loading * added seperate generat_step method in the model and also in the utils to automaticaly use the generate step mthod in the model class * quick update * still not working * save * still not working * initial commit * utils.py logits = logits[:, -1, :] TypeError: tuple indices must be integers or slices, not tuple * update * update * Fixing the Batching Depfwise Comnvolution and multi token input * fixing generate and logits outputs * Done! * Fixing the cache handling, generating works now trying training * update ACKNOWLEDGEMENTS * removing the model_type if stuff in the _step loop in generate_step and adding MambaCache in base.py for training easier generations and removing mamba in tuner/utils. * quick clean up * update trainer/utils for right initialisation of the layers for LoRA, but not working. * clean up * Forther update to trainer/utils for correct layer selection. Successfull training * removing extra mamba-infer.py file * clean up, reformating will come later * reformat and big clean up, final commit * some speedups and cleanups * fix test * nits * nits --------- Co-authored-by: Awni Hannun --- ACKNOWLEDGMENTS.md | 1 + llms/mlx_lm/models/mamba.py | 231 ++++++++++++++++++++++++++++++++++++ llms/mlx_lm/tuner/utils.py | 10 +- llms/tests/test_models.py | 29 +++-- 4 files changed, 263 insertions(+), 8 deletions(-) create mode 100644 llms/mlx_lm/models/mamba.py diff --git a/ACKNOWLEDGMENTS.md b/ACKNOWLEDGMENTS.md index 2b98bc95..2037a076 100644 --- a/ACKNOWLEDGMENTS.md +++ b/ACKNOWLEDGMENTS.md @@ -14,3 +14,4 @@ MLX Examples was developed with contributions from the following individuals: - Markus Enzweiler: Added the `cvae` examples. - Prince Canuma: Helped add support for `Starcoder2` models. - Shiyu Li: Added the `Segment Anything Model`. +- Gökdeniz Gülmez: Added support for `MiniCPM` and `Mamba`. diff --git a/llms/mlx_lm/models/mamba.py b/llms/mlx_lm/models/mamba.py new file mode 100644 index 00000000..26408426 --- /dev/null +++ b/llms/mlx_lm/models/mamba.py @@ -0,0 +1,231 @@ +# Copyright © 2024 Apple Inc. + +import math +from dataclasses import dataclass + +import mlx.core as mx +import mlx.nn as nn + +from .base import BaseModelArgs + + +@dataclass +class ModelArgs(BaseModelArgs): + model_type: str + vocab_size: int + hidden_size: int + intermediate_size: int + state_size: int + num_hidden_layers: int + conv_kernel: int + use_bias: bool + use_conv_bias: bool + time_step_rank: int + tie_word_embeddings: bool = True + + def __post_init__(self): + if not hasattr(self, "hidden_size") and hasattr(self, "d_model"): + self.hidden_size = self.d_model + if not hasattr(self, "intermediate_size") and hasattr(self, "d_inner"): + self.intermediate_size = self.d_inner + if not hasattr(self, "state_size") and hasattr(self, "d_state"): + self.state_size = self.d_state + if not hasattr(self, "num_hidden_layers") and hasattr(self, "n_layer"): + self.num_hidden_layers = self.n_layer + if not hasattr(self, "num_hidden_layers") and hasattr(self, "n_layers"): + self.num_hidden_layers = self.n_layers + if not hasattr(self, "conv_kernel") and hasattr(self, "d_conv"): + self.conv_kernel = self.d_conv + if not hasattr(self, "use_bias") and hasattr(self, "bias"): + self.use_bias = self.bias + if not hasattr(self, "use_conv_bias") and hasattr(self, "conv_bias"): + self.use_conv_bias = self.conv_bias + + if self.time_step_rank == "auto": + self.time_step_rank = math.ceil(self.hidden_size / 16) + + +class MambaCache: + def __init__(self): + self.cache = [None, None] + + def __setitem__(self, idx, value): + self.cache[idx] = value + + def __getitem__(self, idx): + return self.cache[idx] + + @property + def state(self): + return self.cache + + +class DepthWiseConv1d(nn.Module): + def __init__(self, channels, kernel_size, bias=True, padding=0): + super().__init__() + self.channels = channels + self.kernel_size = kernel_size + self.padding = padding + self.weight = mx.random.normal((self.channels, kernel_size, 1)) + self.bias = mx.zeros((channels,)) if bias else None + + def __call__(self, x, cache=None): + B, L, C = x.shape + groups, K, _ = self.weight.shape + + if cache is not None: + x = mx.concatenate([cache, x], axis=1) + else: + x = mx.pad(x, [(0, 0), (K - 1, 0), (0, 0)]) + + y = mx.conv_general(x, self.weight, groups=groups) + + if self.bias is not None: + y = y + self.bias + + return y, x[:, -K + 1 :, :] + + +class MambaBlock(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.args = args + + self.hidden_size = args.hidden_size + self.ssm_state_size = args.state_size + self.conv_kernel_size = args.conv_kernel + self.intermediate_size = args.intermediate_size + self.time_step_rank = int(args.time_step_rank) + self.use_conv_bias = args.use_conv_bias + + self.in_proj = nn.Linear( + self.hidden_size, self.intermediate_size * 2, bias=args.use_bias + ) + + self.conv1d = DepthWiseConv1d( + channels=self.intermediate_size, + kernel_size=self.conv_kernel_size, + bias=self.use_conv_bias, + padding=self.conv_kernel_size - 1, + ) + + self.x_proj = nn.Linear( + self.intermediate_size, + self.time_step_rank + 2 * self.ssm_state_size, + bias=False, + ) + self.dt_proj = nn.Linear(self.time_step_rank, self.intermediate_size, bias=True) + + A = mx.repeat( + mx.arange(1.0, self.ssm_state_size + 1.0).reshape([1, self.ssm_state_size]), + repeats=self.intermediate_size, + axis=0, + ) + self.A_log = mx.log(A) + self.D = mx.ones([self.intermediate_size]) + + self.out_proj = nn.Linear( + self.intermediate_size, self.hidden_size, bias=args.use_bias + ) + + def ssm_step(self, x, state=None): + A = -mx.exp(self.A_log) + D = self.D + deltaBC = self.x_proj(x) + delta, B, C = mx.split( + deltaBC, + indices_or_sections=[ + self.time_step_rank, + self.time_step_rank + self.ssm_state_size, + ], + axis=-1, + ) + delta = nn.softplus(self.dt_proj(delta)) + new_state = mx.expand_dims(delta * x, -1) * mx.expand_dims(B, 1) + if state is not None: + new_state += state * mx.exp(mx.expand_dims(delta, -1) * A) + y = (new_state @ mx.expand_dims(C, -1)).squeeze(2) + y = y + D * x + return y, new_state + + def __call__(self, x, cache): + B, T, D = x.shape + if cache is None: + cache = [None, None] + + outputs = [] + for t in range(T): + xt = x[:, t, :] + xz = self.in_proj(xt) + x_t, z_t = xz.split(indices_or_sections=2, axis=1) + conv_out, cache[0] = self.conv1d(mx.expand_dims(x_t, 1), cache[0]) + x_t = conv_out.squeeze(1) + x_t = nn.silu(x_t) + y_t, cache[1] = self.ssm_step(x_t, cache[1]) + z_t = nn.silu(z_t) + output_t = y_t * z_t + output_t = self.out_proj(output_t) + outputs.append(output_t) + output = mx.stack(outputs, axis=1) + return output + + +class ResidualBlock(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.mixer = MambaBlock(args) + self.norm = nn.RMSNorm(args.hidden_size) + + def __call__(self, x: mx.array, cache): + return self.mixer(self.norm(x), cache) + x + + +class Mamba(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.embeddings = nn.Embedding(args.vocab_size, args.hidden_size) + self.layers = [ResidualBlock(args) for _ in range(args.num_hidden_layers)] + self.norm_f = nn.RMSNorm(args.hidden_size) + + def __call__(self, x: mx.array, cache): + x = self.embeddings(x) + if cache is None: + cache = [None] * len(self.layers) + for layer, c in zip(self.layers, cache): + x = layer(x, c) + return self.norm_f(x) + + +class Model(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.args = args + self.model_type = args.model_type + self.backbone = Mamba(args) + if not args.tie_word_embeddings: + self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=False) + + def __call__(self, inputs: mx.array, cache=None): + B, T = inputs.shape + + x = self.backbone(inputs, cache) + + if self.args.tie_word_embeddings: + logits = self.backbone.embeddings.as_linear(x) + else: + logits = self.lm_head(x) + + return logits + + def sanitize(self, weights): + for k, v in weights.items(): + if "conv1d.weight" in k and v.ndim == 3: + weights[k] = v.moveaxis(2, 1) + return weights + + def make_cache(self, batch_size: int = 1): + return [MambaCache() for _ in range(len(self.layers))] + + @property + def layers(self): + return self.backbone.layers diff --git a/llms/mlx_lm/tuner/utils.py b/llms/mlx_lm/tuner/utils.py index 71fbfaab..ab9d37aa 100644 --- a/llms/mlx_lm/tuner/utils.py +++ b/llms/mlx_lm/tuner/utils.py @@ -52,7 +52,6 @@ def linear_to_lora_layers( use_dora (bool): If True, uses DoRA instead of LoRA. Default: ``False`` """ - num_layers = len(model.layers) if num_lora_layers < 0: @@ -140,6 +139,15 @@ def linear_to_lora_layers( "self_attn.kv_b_proj", ] ) + elif model.model_type == "mamba": + keys = set( + [ + "mixer.in_proj", + "mixer.x_proj", + "mixer.dt_proj", + "mixer.out_proj", + ] + ) else: raise ValueError(f"Lora does not support {model.model_type}") diff --git a/llms/tests/test_models.py b/llms/tests/test_models.py index fcf1dc33..cd7e7fd0 100644 --- a/llms/tests/test_models.py +++ b/llms/tests/test_models.py @@ -5,6 +5,7 @@ import unittest import mlx.core as mx from mlx.utils import tree_map from mlx_lm.models.base import KVCache, RotatingKVCache +from mlx_lm.utils import make_kv_caches class TestModels(unittest.TestCase): @@ -100,13 +101,7 @@ class TestModels(unittest.TestCase): self.assertEqual(outputs.shape, (1, 2, vocab_size)) self.assertEqual(outputs.dtype, t) - kv_heads = ( - [model.n_kv_heads] * len(model.layers) - if isinstance(model.n_kv_heads, int) - else model.n_kv_heads - ) - cache = [KVCache(model.head_dim, n) for n in kv_heads] - + cache = make_kv_caches(model) outputs = model(inputs, cache) self.assertEqual(outputs.shape, (1, 2, vocab_size)) self.assertEqual(outputs.dtype, t) @@ -397,6 +392,26 @@ class TestModels(unittest.TestCase): model, args.model_type, args.vocab_size, args.num_hidden_layers ) + def test_mamba(self): + from mlx_lm.models import mamba + + args = mamba.ModelArgs( + model_type="mamba", + vocab_size=10000, + use_bias=False, + use_conv_bias=True, + conv_kernel=4, + hidden_size=768, + num_hidden_layers=24, + state_size=16, + intermediate_size=1536, + time_step_rank=48, + ) + model = mamba.Model(args) + self.model_test_runner( + model, args.model_type, args.vocab_size, args.num_hidden_layers + ) + def test_gpt2(self): from mlx_lm.models import gpt2 From d812516d3d55c25e55238aee1e083fac5378a07f Mon Sep 17 00:00:00 2001 From: jamesm131 <20141156+jamesm131@users.noreply.github.com> Date: Sun, 29 Sep 2024 00:21:11 +1000 Subject: [PATCH 034/188] Add /v1/models endpoint to mlx_lm.server (#984) * Add 'models' endpoint to server * Add test for new 'models' server endpoint * Check hf_cache for mlx models * update tests to check hf_cache for models * simplify test * doc --------- Co-authored-by: Awni Hannun --- llms/mlx_lm/SERVER.md | 14 +++++++++++++ llms/mlx_lm/server.py | 41 +++++++++++++++++++++++++++++++++++++++ llms/tests/test_server.py | 15 ++++++++++++++ 3 files changed, 70 insertions(+) diff --git a/llms/mlx_lm/SERVER.md b/llms/mlx_lm/SERVER.md index 9c42d410..55be1c9c 100644 --- a/llms/mlx_lm/SERVER.md +++ b/llms/mlx_lm/SERVER.md @@ -85,3 +85,17 @@ curl localhost:8080/v1/chat/completions \ - `adapters`: (Optional) A string path to low-rank adapters. The path must be rlative to the directory the server was started in. + +### List Models + +Use the `v1/models` endpoint to list available models: + +```shell +curl localhost:8080/v1/models -H "Content-Type: application/json" +``` + +This will return a list of locally available models where each model in the +list contains the following fields: + +- `"id"`: The Hugging Face repo id. +- `"created"`: A timestamp representing the model creation time. diff --git a/llms/mlx_lm/server.py b/llms/mlx_lm/server.py index 79ac1836..f2d8b86a 100644 --- a/llms/mlx_lm/server.py +++ b/llms/mlx_lm/server.py @@ -11,6 +11,7 @@ from pathlib import Path from typing import Dict, List, Literal, NamedTuple, Optional, Sequence, Union import mlx.core as mx +from huggingface_hub import scan_cache_dir from .utils import generate_step, load @@ -618,6 +619,46 @@ class APIHandler(BaseHTTPRequestHandler): prompt = self.tokenizer.encode(prompt_text) return mx.array(prompt) + def do_GET(self): + """ + Respond to a GET request from a client. + """ + if self.path == "/v1/models": + self.handle_models_request() + else: + self._set_completion_headers(404) + self.end_headers() + self.wfile.write(b"Not Found") + + def handle_models_request(self): + """ + Handle a GET request for the /v1/models endpoint. + """ + self._set_completion_headers(200) + self.end_headers() + + # Scan the cache directory for downloaded mlx models + hf_cache_info = scan_cache_dir() + downloaded_models = [ + repo for repo in hf_cache_info.repos if "mlx" in repo.repo_id + ] + + # Create a list of available models + models = [ + { + "id": repo.repo_id, + "object": "model", + "created": self.created, + } + for repo in downloaded_models + ] + + response = {"object": "list", "data": models} + + response_json = json.dumps(response).encode() + self.wfile.write(response_json) + self.wfile.flush() + def run( host: str, diff --git a/llms/tests/test_server.py b/llms/tests/test_server.py index baea664a..cbcccfbe 100644 --- a/llms/tests/test_server.py +++ b/llms/tests/test_server.py @@ -1,5 +1,7 @@ # Copyright © 2024 Apple Inc. + import http +import json import threading import unittest @@ -77,6 +79,19 @@ class TestServer(unittest.TestCase): self.assertIn("id", response_body) self.assertIn("choices", response_body) + def test_handle_models(self): + url = f"http://localhost:{self.port}/v1/models" + response = requests.get(url) + self.assertEqual(response.status_code, 200) + response_body = json.loads(response.text) + self.assertEqual(response_body["object"], "list") + self.assertIsInstance(response_body["data"], list) + self.assertGreater(len(response_body["data"]), 0) + model = response_body["data"][0] + self.assertIn("id", model) + self.assertEqual(model["object"], "model") + self.assertIn("created", model) + def test_sequence_overlap(self): from mlx_lm.server import sequence_overlap From ace2bb58900dd382bbf3e6d9c58cbc3beee0261c Mon Sep 17 00:00:00 2001 From: nathan <97126670+nathanrchn@users.noreply.github.com> Date: Sat, 28 Sep 2024 19:08:49 +0200 Subject: [PATCH 035/188] Add logits_processor option to generate_step function (#983) * Add logits_processor option for the generation as in huggingface transformers library * concatenation correction * Rename the tokens variable for clarity * remove the logit_bias argument from generate_step method * fix the variable name * nits + test * test * add back logit bias + test --------- Co-authored-by: Awni Hannun --- llms/mlx_lm/utils.py | 25 +++++++++++++---- llms/tests/test_generate.py | 55 +++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 llms/tests/test_generate.py diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index 5621609d..16271c3e 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -154,10 +154,11 @@ def generate_step( top_p: float = 1.0, min_p: float = 0.0, min_tokens_to_keep: int = 1, - logit_bias: Optional[Dict[int, float]] = None, prefill_step_size: int = 512, max_kv_size: Optional[int] = None, cache_history: Optional[List[Tuple[mx.array, mx.array]]] = None, + logit_bias: Optional[Dict[int, float]] = None, + logits_processor: Optional[Callable[[mx.array, mx.array], mx.array]] = None, ) -> Generator[Tuple[mx.array, mx.array], None, None]: """ A generator producing token ids based on the given prompt from the model. @@ -177,10 +178,13 @@ def generate_step( probability) that a token probability must have to be considered. min_tokens_to_keep (int, optional): Minimum number of tokens that cannot be filtered by min_p sampling. - logit_bias (dictionary, optional): Additive logit bias. prefill_step_size (int): Step size for processing the prompt. max_kv_size (int, optional): Maximum size of the key-value cache. Old entries (except the first 4 tokens) will be overwritten. + logit_bias (dictionary, optional): Additive logit bias. + logits_processor (Callable[[mx.array, mx.array], mx.array], optional): + A function that takes tokens and logits and returns the processed + logits. Default: ``None``. Yields: Generator[Tuple[mx.array, mx.array], None, None]: A generator producing @@ -188,10 +192,6 @@ def generate_step( """ def sample(logits: mx.array) -> Tuple[mx.array, float]: - if logit_bias: - indices = mx.array(list(logit_bias.keys())) - values = mx.array(list(logit_bias.values())) - logits[:, indices] += values logprobs = logits - mx.logsumexp(logits) if temp == 0: @@ -214,6 +214,7 @@ def generate_step( ) y = prompt + tokens = None # Create the KV cache for generation cache = make_kv_caches(model, max_kv_size) @@ -233,11 +234,23 @@ def generate_step( if repetition_context_size: repetition_context = repetition_context[-repetition_context_size:] + if logit_bias: + indices = mx.array(list(logit_bias.keys())) + values = mx.array(list(logit_bias.values())) + def _step(y): nonlocal repetition_context logits = model(y[None], cache=cache) logits = logits[:, -1, :] + if logits_processor: + nonlocal tokens + tokens = mx.concat([tokens, y]) if tokens is not None else y + logits = logits_processor(tokens, logits) + + if logit_bias: + logits[:, indices] += values + if repetition_penalty: logits = apply_repetition_penalty( logits, repetition_context, repetition_penalty diff --git a/llms/tests/test_generate.py b/llms/tests/test_generate.py new file mode 100644 index 00000000..bc969844 --- /dev/null +++ b/llms/tests/test_generate.py @@ -0,0 +1,55 @@ +# Copyright © 2024 Apple Inc. + +import unittest + +from mlx_lm.utils import generate, load + + +class TestGenerate(unittest.TestCase): + + @classmethod + def setUpClass(cls): + HF_MODEL_PATH = "mlx-community/Qwen1.5-0.5B-Chat-4bit" + cls.model, cls.tokenizer = load(HF_MODEL_PATH) + + def test_generate(self): + # Simple test that generation runs + text = generate( + self.model, self.tokenizer, "hello", max_tokens=5, verbose=False + ) + + def test_generate_with_logit_bias(self): + logit_bias = {0: 2000.0, 1: -20.0} + text = generate( + self.model, + self.tokenizer, + "hello", + max_tokens=5, + verbose=False, + logit_bias=logit_bias, + ) + self.assertEqual(text, "!!!!!") + + def test_generate_with_processor(self): + init_toks = self.tokenizer.encode("hello") + + all_toks = None + + def logits_processor(toks, logits): + nonlocal all_toks + all_toks = toks + return logits + + generate( + self.model, + self.tokenizer, + "hello", + max_tokens=5, + verbose=False, + logits_processor=logits_processor, + ) + self.assertEqual(len(all_toks), len(init_toks) + 5) + + +if __name__ == "__main__": + unittest.main() From 7ec2021bb99de16f89c59949439f428fc968a0d7 Mon Sep 17 00:00:00 2001 From: madroid Date: Sun, 29 Sep 2024 01:41:36 +0800 Subject: [PATCH 036/188] LoRA: support tools(function calling) format datasets (#995) * LoRA: support fine-tuning tools datasets * LoRA: Split small function * LoRA: add tools format to lora docs * LoRA: pre-commit fix * Revert "LoRA: pre-commit fix" This reverts commit b94b7e0fe7c6adfb642e1392710c027096d91d49. * Revert "LoRA: Split small function" This reverts commit 3f6a5f19fd8ba24bf6933c3a9bdcc66c8b29825f. * LoRA: remove ToolsDataset In a JSONL file, not all data is required to include the tools value. * nit in readme * nit in readme * nit in readme --------- Co-authored-by: Awni Hannun --- llms/mlx_lm/LORA.md | 68 +++++++++++++++++++++++++++++++---- llms/mlx_lm/tuner/datasets.py | 5 ++- llms/mlx_lm/tuner/trainer.py | 4 +-- 3 files changed, 66 insertions(+), 11 deletions(-) diff --git a/llms/mlx_lm/LORA.md b/llms/mlx_lm/LORA.md index 2d9a2553..8aec89ec 100644 --- a/llms/mlx_lm/LORA.md +++ b/llms/mlx_lm/LORA.md @@ -160,8 +160,8 @@ For fine-tuning (`--train`), the data loader expects a `train.jsonl` and a `valid.jsonl` to be in the data directory. For evaluation (`--test`), the data loader expects a `test.jsonl` in the data directory. -Currently, `*.jsonl` files support three data formats: `chat`, -`completions`, and `text`. Here are three examples of these formats: +Currently, `*.jsonl` files support `chat`, `tools`, `completions`, and `text` +data formats. Here are examples of these formats: `chat`: @@ -169,6 +169,58 @@ Currently, `*.jsonl` files support three data formats: `chat`, {"messages": [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "Hello."}, {"role": "assistant", "content": "How can I assistant you today."}]} ``` +`tools`: + +```jsonl +{"messages":[{"role":"user","content":"What is the weather in San Francisco?"},{"role":"assistant","tool_calls":[{"id":"call_id","type":"function","function":{"name":"get_current_weather","arguments":"{\"location\": \"San Francisco, USA\", \"format\": \"celsius\"}"}}]}],"tools":[{"type":"function","function":{"name":"get_current_weather","description":"Get the current weather","parameters":{"type":"object","properties":{"location":{"type":"string","description":"The city and country, eg. San Francisco, USA"},"format":{"type":"string","enum":["celsius","fahrenheit"]}},"required":["location","format"]}}}]} +``` + +
+View the expanded single data tool format + +```jsonl +{ + "messages": [ + { "role": "user", "content": "What is the weather in San Francisco?" }, + { + "role": "assistant", + "tool_calls": [ + { + "id": "call_id", + "type": "function", + "function": { + "name": "get_current_weather", + "arguments": "{\"location\": \"San Francisco, USA\", \"format\": \"celsius\"}" + } + } + ] + } + ], + "tools": [ + { + "type": "function", + "function": { + "name": "get_current_weather", + "description": "Get the current weather", + "parameters": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "The city and country, eg. San Francisco, USA" + }, + "format": { "type": "string", "enum": ["celsius", "fahrenheit"] } + }, + "required": ["location", "format"] + } + } + } + ] +} +``` + +
+ `completions`: ```jsonl @@ -215,11 +267,13 @@ hf_dataset: - Arguments specified in `config` will be passed as keyword arguments to [`datasets.load_dataset`](https://huggingface.co/docs/datasets/v2.20.0/en/package_reference/loading_methods#datasets.load_dataset). -In general, for the `chat` and `completions` formats, Hugging Face [chat -templates](https://huggingface.co/blog/chat-templates) are used. This applies -the model's chat template by default. If the model does not have a chat -template, then Hugging Face will use a default. For example, the final text in -the `chat` example above with Hugging Face's default template becomes: +In general, for the `chat`, `tools` and `completions` formats, Hugging Face +[chat +templates](https://huggingface.co/docs/transformers/main/en/chat_templating) +are used. This applies the model's chat template by default. If the model does +not have a chat template, then Hugging Face will use a default. For example, +the final text in the `chat` example above with Hugging Face's default template +becomes: ```text <|im_start|>system diff --git a/llms/mlx_lm/tuner/datasets.py b/llms/mlx_lm/tuner/datasets.py index 3d99894c..2b8abf43 100644 --- a/llms/mlx_lm/tuner/datasets.py +++ b/llms/mlx_lm/tuner/datasets.py @@ -36,7 +36,10 @@ class ChatDataset(Dataset): def __getitem__(self, idx: int): messages = self._data[idx]["messages"] text = self._tokenizer.apply_chat_template( - messages, tokenize=False, add_generation_prompt=True + messages, + tools=self._data[idx].get("tools", None), + tokenize=False, + add_generation_prompt=True, ) return text diff --git a/llms/mlx_lm/tuner/trainer.py b/llms/mlx_lm/tuner/trainer.py index 24fcc5c6..b15801a5 100644 --- a/llms/mlx_lm/tuner/trainer.py +++ b/llms/mlx_lm/tuner/trainer.py @@ -93,9 +93,7 @@ def iterate_batches(dataset, tokenizer, batch_size, max_seq_length, train=False) # Encode batch batch = [tokenizer.encode(dataset[j]) for j in batch_idx[i]] for b in batch: - if b[-1] == tokenizer.eos_token_id: - print("[WARNING] Example already has an EOS token appended") - else: + if b[-1] != tokenizer.eos_token_id: b.append(tokenizer.eos_token_id) lengths = [len(x) for x in batch] From 50e5ca81a8c06f4c49cec48795330209a885d2c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6kdeniz=20G=C3=BClmez?= <60228478+Goekdeniz-Guelmez@users.noreply.github.com> Date: Mon, 30 Sep 2024 02:12:47 +0200 Subject: [PATCH 037/188] Adding full finetuning (#903) * Adding full model weights finetuning * Updating the LORA.md and ACKNOWLEDGMENTS.md files. * removing --use-dora and --fulll-training and adding --fine-tune-type * some clean up * reformating and fixing dora training * updated CONFIG_DEFAULTS * update config example * update in the config example fie * Update LORA.md * merge and commit * adding argument for dora linear layer * clean up * clean up in the example yaml file * fix * final fix before sending * small addition to re md file * fix for loading the fully trained model by saving all the files and configs correctly * clean up * removing the unnesesairy files * changing lora layers back to 16 * removed max file size * nits * resolve merge * some consistency changes --------- Co-authored-by: Awni Hannun --- ACKNOWLEDGMENTS.md | 2 +- llms/README.md | 2 +- llms/mlx_lm/LORA.md | 16 ++++++---- llms/mlx_lm/examples/lora_config.yaml | 7 ++-- llms/mlx_lm/fuse.py | 15 ++++----- llms/mlx_lm/lora.py | 46 +++++++++++++++++---------- llms/mlx_lm/tuner/trainer.py | 22 ++++++------- llms/mlx_lm/tuner/utils.py | 35 ++++++++++---------- llms/mlx_lm/utils.py | 4 +-- 9 files changed, 79 insertions(+), 70 deletions(-) diff --git a/ACKNOWLEDGMENTS.md b/ACKNOWLEDGMENTS.md index 2037a076..41557c29 100644 --- a/ACKNOWLEDGMENTS.md +++ b/ACKNOWLEDGMENTS.md @@ -14,4 +14,4 @@ MLX Examples was developed with contributions from the following individuals: - Markus Enzweiler: Added the `cvae` examples. - Prince Canuma: Helped add support for `Starcoder2` models. - Shiyu Li: Added the `Segment Anything Model`. -- Gökdeniz Gülmez: Added support for `MiniCPM` and `Mamba`. +- Gökdeniz Gülmez: Added support for `MiniCPM`, `Mamba` and support for `full-fine-tuning`. \ No newline at end of file diff --git a/llms/README.md b/llms/README.md index b8e1914d..75677865 100644 --- a/llms/README.md +++ b/llms/README.md @@ -16,7 +16,7 @@ conda install -c conda-forge mlx-lm The `mlx-lm` package also has: -- [LoRA and QLoRA fine-tuning](https://github.com/ml-explore/mlx-examples/blob/main/llms/mlx_lm/LORA.md) +- [LoRA, QLoRA, and full fine-tuning](https://github.com/ml-explore/mlx-examples/blob/main/llms/mlx_lm/LORA.md) - [Merging models](https://github.com/ml-explore/mlx-examples/blob/main/llms/mlx_lm/MERGE.md) - [HTTP model serving](https://github.com/ml-explore/mlx-examples/blob/main/llms/mlx_lm/SERVER.md) diff --git a/llms/mlx_lm/LORA.md b/llms/mlx_lm/LORA.md index 8aec89ec..80c25b4b 100644 --- a/llms/mlx_lm/LORA.md +++ b/llms/mlx_lm/LORA.md @@ -57,6 +57,9 @@ mlx_lm.lora \ --iters 600 ``` +To fine-tune the full model weights, add the `--fine-tune-type full` flag. +Currently supported fine-tuning types are `lora` (default), `dora`, and `full`. + The `--data` argument must specify a path to a `train.jsonl`, `valid.jsonl` when using `--train` and a path to a `test.jsonl` when using `--test`. For more details on the data format see the section on [Data](#Data). @@ -67,8 +70,8 @@ mistralai/Mistral-7B-v0.1`. If `--model` points to a quantized model, then the training will use QLoRA, otherwise it will use regular LoRA. -By default, the adapter config and weights are saved in `adapters/`. You can -specify the output location with `--adapter-path`. +By default, the adapter config and learned weights are saved in `adapters/`. +You can specify the output location with `--adapter-path`. You can resume fine-tuning with an existing adapter with `--resume-adapter-file `. @@ -118,7 +121,7 @@ mlx_lm.fuse --model ``` This will by default load the adapters from `adapters/`, and save the fused -model in the path `lora_fused_model/`. All of these are configurable. +model in the path `fused_model/`. All of these are configurable. To upload a fused model, supply the `--upload-repo` and `--hf-path` arguments to `mlx_lm.fuse`. The latter is the repo name of the original model, which is @@ -141,7 +144,7 @@ mlx_lm.fuse \ --export-gguf ``` -This will save the GGUF model in `lora_fused_model/ggml-model-f16.gguf`. You +This will save the GGUF model in `fused_model/ggml-model-f16.gguf`. You can specify the file name with `--gguf-path`. ## Data @@ -301,7 +304,7 @@ of memory. Here are some tips to reduce memory use should you need to do so: setting this to `2` or `1` will reduce memory consumption. This may slow things down a little, but will also reduce the memory use. -3. Reduce the number of layers to fine-tune with `--lora-layers`. The default +3. Reduce the number of layers to fine-tune with `--num-layers`. The default is `16`, so you can try `8` or `4`. This reduces the amount of memory needed for back propagation. It may also reduce the quality of the fine-tuned model if you are fine-tuning with a lot of data. @@ -323,7 +326,7 @@ mlx_lm.lora \ --model mistralai/Mistral-7B-v0.1 \ --train \ --batch-size 1 \ - --lora-layers 4 \ + --num-layers 4 \ --data wikisql ``` @@ -333,4 +336,5 @@ tokens-per-second, using the MLX Example data set. [^lora]: Refer to the [arXiv paper](https://arxiv.org/abs/2106.09685) for more details on LoRA. + [^qlora]: Refer to the paper [QLoRA: Efficient Finetuning of Quantized LLMs](https://arxiv.org/abs/2305.14314) diff --git a/llms/mlx_lm/examples/lora_config.yaml b/llms/mlx_lm/examples/lora_config.yaml index 073a5b6f..4ec9a23c 100644 --- a/llms/mlx_lm/examples/lora_config.yaml +++ b/llms/mlx_lm/examples/lora_config.yaml @@ -1,8 +1,12 @@ # The path to the local model directory or Hugging Face repo. model: "mlx_model" + # Whether or not to train (boolean) train: true +# The fine-tuning method: "lora", "dora", or "full". +fine_tune_type: lora + # Directory with {train, valid, test}.jsonl files data: "/path/to/training/data" @@ -51,9 +55,6 @@ max_seq_length: 2048 # Use gradient checkpointing to reduce memory use. grad_checkpoint: false -# Use DoRA instead of LoRA. -use_dora: false - # LoRA parameters can only be specified in a config file lora_parameters: # The layer keys to apply LoRA to. diff --git a/llms/mlx_lm/fuse.py b/llms/mlx_lm/fuse.py index 16457036..b0c46a74 100644 --- a/llms/mlx_lm/fuse.py +++ b/llms/mlx_lm/fuse.py @@ -8,7 +8,7 @@ from mlx.utils import tree_flatten, tree_unflatten from .gguf import convert_to_gguf from .tuner.dora import DoRAEmbedding, DoRALinear from .tuner.lora import LoRAEmbedding, LoRALinear, LoRASwitchLinear -from .tuner.utils import apply_lora_layers, dequantize +from .tuner.utils import dequantize, load_adapters from .utils import ( fetch_from_hub, get_model_path, @@ -29,7 +29,7 @@ def parse_arguments() -> argparse.Namespace: ) parser.add_argument( "--save-path", - default="lora_fused_model", + default="fused_model", help="The path to save the fused model.", ) parser.add_argument( @@ -77,17 +77,14 @@ def main() -> None: model, config, tokenizer = fetch_from_hub(model_path) model.freeze() - model = apply_lora_layers(model, args.adapter_path) + model = load_adapters(model, args.adapter_path) fused_linears = [ - (n, m.fuse()) - for n, m in model.named_modules() - if isinstance( - m, (LoRASwitchLinear, LoRALinear, LoRAEmbedding, DoRALinear, DoRAEmbedding) - ) + (n, m.fuse()) for n, m in model.named_modules() if hasattr(m, "fuse") ] - model.update_modules(tree_unflatten(fused_linears)) + if fused_linears: + model.update_modules(tree_unflatten(fused_linears)) if args.de_quantize: print("De-quantizing model") diff --git a/llms/mlx_lm/lora.py b/llms/mlx_lm/lora.py index 580e3d3c..69232774 100644 --- a/llms/mlx_lm/lora.py +++ b/llms/mlx_lm/lora.py @@ -15,9 +15,9 @@ from .tokenizer_utils import TokenizerWrapper from .tuner.datasets import load_dataset from .tuner.trainer import TrainingArgs, TrainingCallback, evaluate, train from .tuner.utils import ( - apply_lora_layers, build_schedule, linear_to_lora_layers, + load_adapters, print_trainable_parameters, ) from .utils import load, save_config @@ -41,9 +41,10 @@ yaml_loader.add_implicit_resolver( CONFIG_DEFAULTS = { "model": "mlx_model", "train": False, + "fine_tune_type": "lora", "data": "data/", "seed": 0, - "lora_layers": 16, + "num_layers": 16, "batch_size": 4, "iters": 1000, "val_batches": 25, @@ -58,7 +59,6 @@ CONFIG_DEFAULTS = { "max_seq_length": 2048, "lr_schedule": None, "lora_parameters": {"rank": 8, "alpha": 16, "dropout": 0.0, "scale": 10.0}, - "use_dora": False, } @@ -82,7 +82,14 @@ def build_parser(): help="Directory with {train, valid, test}.jsonl files", ) parser.add_argument( - "--lora-layers", + "--fine-tune-type", + type=str, + choices=["lora", "dora", "full"], + default="lora", + help="Type of fine-tuning to perform: lora, dora, or full.", + ) + parser.add_argument( + "--num-layers", type=int, help="Number of layers to fine-tune. Default is 16, use -1 for all.", ) @@ -107,12 +114,12 @@ def build_parser(): parser.add_argument( "--resume-adapter-file", type=str, - help="Load path to resume training with the given adapters.", + help="Load path to resume training from the given fine-tuned weights.", ) parser.add_argument( "--adapter-path", type=str, - help="Save/load path for the adapters.", + help="Save/load path for the fine-tuned weights.", ) parser.add_argument( "--save-every", @@ -148,9 +155,6 @@ def build_parser(): default=None, ) parser.add_argument("--seed", type=int, default=None, help="The PRNG seed") - parser.add_argument( - "--use-dora", action="store_true", default=None, help="Use DoRA to finetune." - ) return parser @@ -162,21 +166,31 @@ def train_model( valid_set, training_callback: TrainingCallback = None, ): - # Freeze all layers model.freeze() + if args.fine_tune_type == "full": + for l in model.layers[-min(args.num_layers, 0) :]: + l.unfreeze() + elif args.fine_tune_type in ["lora", "dora"]: + # Convert linear layers to lora/dora layers and unfreeze in the process + linear_to_lora_layers( + model, + args.num_layers, + args.lora_parameters, + use_dora=(args.fine_tune_type == "dora"), + ) + else: + raise ValueError(f"Received unknown fine-tune-type {args.fine_tune_type}") - # Convert linear layers to lora layers and unfreeze in the process - linear_to_lora_layers(model, args.lora_layers, args.lora_parameters, args.use_dora) - - # Resume training the given adapters. + # Resume from weights if provided if args.resume_adapter_file is not None: - print(f"Loading pretrained adapters from {args.resume_adapter_file}") + print(f"Loading fine-tuned weights from {args.resume_adapter_file}") model.load_weights(args.resume_adapter_file, strict=False) print_trainable_parameters(model) adapter_path = Path(args.adapter_path) adapter_path.mkdir(parents=True, exist_ok=True) + adapter_file = adapter_path / "adapters.safetensors" save_config(vars(args), adapter_path / "adapter_config.json") @@ -240,7 +254,7 @@ def run(args, training_callback: TrainingCallback = None): if args.test and not args.train: # Allow testing without LoRA layers by providing empty path if args.adapter_path != "": - apply_lora_layers(model, args.adapter_path) + load_adapters(model, args.adapter_path) elif args.train: print("Training") diff --git a/llms/mlx_lm/tuner/trainer.py b/llms/mlx_lm/tuner/trainer.py index b15801a5..1d934a72 100644 --- a/llms/mlx_lm/tuner/trainer.py +++ b/llms/mlx_lm/tuner/trainer.py @@ -1,5 +1,7 @@ # Copyright © 2024 Apple Inc. +import glob +import shutil import time from dataclasses import dataclass, field from pathlib import Path @@ -285,24 +287,18 @@ def train( # Save adapter weights if it % args.steps_per_save == 0: - save_adapter(model, args.adapter_file) + adapter_weights = dict(tree_flatten(model.trainable_parameters())) + mx.save_safetensors(str(args.adapter_file), adapter_weights) checkpoint = ( Path(args.adapter_file).parent / f"{it:07d}_adapters.safetensors" ) - save_adapter(model, checkpoint) + mx.save_safetensors(str(checkpoint), adapter_weights) print( f"Iter {it}: Saved adapter weights to " f"{args.adapter_file} and {checkpoint}." ) - # save final adapter weights - save_adapter(model, args.adapter_file) - print(f"Saved final adapter weights to {args.adapter_file}.") - - -def save_adapter( - model: nn.Module, - adapter_file: Union[str, Path], -): - flattened_tree = tree_flatten(model.trainable_parameters()) - mx.save_safetensors(str(adapter_file), dict(flattened_tree)) + # Save final weights + adapter_weights = dict(tree_flatten(model.trainable_parameters())) + mx.save_safetensors(str(args.adapter_file), adapter_weights) + print(f"Saved final weights to {args.adapter_file}.") diff --git a/llms/mlx_lm/tuner/utils.py b/llms/mlx_lm/tuner/utils.py index ab9d37aa..7c78ee91 100644 --- a/llms/mlx_lm/tuner/utils.py +++ b/llms/mlx_lm/tuner/utils.py @@ -36,7 +36,7 @@ def build_schedule(schedule_config: Dict): def linear_to_lora_layers( model: nn.Module, - num_lora_layers: int, + num_layers: int, config: Dict, use_dora: bool = False, ): @@ -45,22 +45,17 @@ def linear_to_lora_layers( Args: model (nn.Module): The neural network model. - num_lora_layers (int): The number of blocks to convert to lora layers + num_layers (int): The number of blocks to convert to lora layers starting from the last layer. config (dict): More configuration parameters for LoRA, including the rank, scale, and optional layer keys. use_dora (bool): If True, uses DoRA instead of LoRA. Default: ``False`` """ - num_layers = len(model.layers) - - if num_lora_layers < 0: - num_lora_layers = num_layers - - if num_lora_layers > num_layers: + if num_layers > len(model.layers): raise ValueError( - f"Requested {num_lora_layers} LoRA layers " - f"but the model only has {num_layers} layers." + f"Requested {num_layers} LoRA layers " + f"but the model only has {len(model.layers)} layers." ) def to_lora(layer): @@ -151,7 +146,7 @@ def linear_to_lora_layers( else: raise ValueError(f"Lora does not support {model.model_type}") - for l in model.layers[num_layers - num_lora_layers :]: + for l in model.layers[-min(num_layers, 0) :]: lora_layers = [(k, to_lora(m)) for k, m in l.named_modules() if k in keys] if lora_layers: l.update_modules(tree_unflatten(lora_layers)) @@ -161,9 +156,9 @@ def linear_to_lora_layers( model.update_modules(tree_unflatten(lora_modules)) -def apply_lora_layers(model: nn.Module, adapter_path: str) -> nn.Module: +def load_adapters(model: nn.Module, adapter_path: str) -> nn.Module: """ - Apply LoRA layers to the model. + Load any fine-tuned adapters / layers. Args: model (nn.Module): The neural network model. @@ -177,12 +172,14 @@ def apply_lora_layers(model: nn.Module, adapter_path: str) -> nn.Module: raise FileNotFoundError(f"The adapter path does not exist: {adapter_path}") with open(adapter_path / "adapter_config.json", "r") as fid: config = types.SimpleNamespace(**json.load(fid)) - linear_to_lora_layers( - model, - config.lora_layers, - config.lora_parameters, - getattr(config, "use_dora", False), - ) + fine_tune_type = getattr(config, "fine_tune_type", "lora") + if fine_tune_type != "full": + linear_to_lora_layers( + model, + config.num_layers, + config.lora_parameters, + use_dora=(fine_tune_type == "dora"), + ) model.load_weights(str(adapter_path / "adapters.safetensors"), strict=False) return model diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index 16271c3e..9411138d 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -21,8 +21,8 @@ from transformers import PreTrainedTokenizer from .models.base import KVCache, RotatingKVCache from .sample_utils import categorical_sampling, min_p_sampling, top_p_sampling from .tokenizer_utils import TokenizerWrapper, load_tokenizer -from .tuner.utils import apply_lora_layers from .tuner.utils import dequantize as dequantize_model +from .tuner.utils import load_adapters # Constants MODEL_REMAPPING = { @@ -515,7 +515,7 @@ def load( model = load_model(model_path, lazy, model_config) if adapter_path is not None: - model = apply_lora_layers(model, adapter_path) + model = load_adapters(model, adapter_path) model.eval() tokenizer = load_tokenizer(model_path, tokenizer_config) From aa1c8abdc67be20a1efcacb0e3102ac732a87967 Mon Sep 17 00:00:00 2001 From: madroid Date: Mon, 30 Sep 2024 22:36:21 +0800 Subject: [PATCH 038/188] LoRA: Support HuggingFace dataset via data parameter (#996) * LoRA: support huggingface dataset via `data` argument * LoRA: Extract the load_custom_hf_dataset function * LoRA: split small functions * fix spelling errors * handle load hf dataset error * fix pre-commit lint * update data argument help * nits and doc --------- Co-authored-by: Awni Hannun --- llms/mlx_lm/LORA.md | 8 ++- llms/mlx_lm/lora.py | 5 +- llms/mlx_lm/tuner/datasets.py | 131 +++++++++++++++++++++------------- 3 files changed, 93 insertions(+), 51 deletions(-) diff --git a/llms/mlx_lm/LORA.md b/llms/mlx_lm/LORA.md index 80c25b4b..2d0dcf60 100644 --- a/llms/mlx_lm/LORA.md +++ b/llms/mlx_lm/LORA.md @@ -251,7 +251,13 @@ To use Hugging Face datasets, first install the `datasets` package: pip install datasets ``` -Specify the Hugging Face dataset arguments in a YAML config. For example: +If the Hugging Face dataset is already in a supported format, you can specify +it on the command line. For example, pass `--data mlx-community/wikisql` to +train on the pre-formatted WikiwSQL data. + +Otherwise, provide a mapping of keys in the dataset to the features MLX LM +expects. Use a YAML config to specify the Hugging Face dataset arguments. For +example: ``` hf_dataset: diff --git a/llms/mlx_lm/lora.py b/llms/mlx_lm/lora.py index 69232774..c96e75a7 100644 --- a/llms/mlx_lm/lora.py +++ b/llms/mlx_lm/lora.py @@ -79,7 +79,10 @@ def build_parser(): parser.add_argument( "--data", type=str, - help="Directory with {train, valid, test}.jsonl files", + help=( + "Directory with {train, valid, test}.jsonl files or the name " + "of a Hugging Face dataset (e.g., 'mlx-community/wikisql')" + ), ) parser.add_argument( "--fine-tune-type", diff --git a/llms/mlx_lm/tuner/datasets.py b/llms/mlx_lm/tuner/datasets.py index 2b8abf43..20b32eff 100644 --- a/llms/mlx_lm/tuner/datasets.py +++ b/llms/mlx_lm/tuner/datasets.py @@ -76,17 +76,14 @@ class CompletionsDataset(Dataset): return text -def create_dataset(path: Path, tokenizer: PreTrainedTokenizer = None): - # Return empty dataset for non-existent paths - if not path.exists(): - return [] - with open(path, "r") as fid: - data = [json.loads(l) for l in fid] - if "messages" in data[0]: +def create_dataset(data, tokenizer: PreTrainedTokenizer = None): + sample = data[0] + + if "messages" in sample: return ChatDataset(data, tokenizer) - elif "prompt" in data[0] and "completion" in data[0]: + elif "prompt" in sample and "completion" in sample: return CompletionsDataset(data, tokenizer) - elif "text" in data[0]: + elif "text" in sample: return Dataset(data) else: raise ValueError( @@ -95,54 +92,90 @@ def create_dataset(path: Path, tokenizer: PreTrainedTokenizer = None): ) -def load_dataset(args, tokenizer: PreTrainedTokenizer): - if getattr(args, "hf_dataset", None) is not None: - import datasets +def load_local_dataset(data_path: Path, tokenizer: PreTrainedTokenizer): + def load_subset(path): + if not path.exists(): + return [] + with open(path, "r") as fid: + data = [json.loads(l) for l in fid] + return create_dataset(data, tokenizer) - hf_args = args.hf_dataset - dataset_name = hf_args["name"] - print(f"Loading Hugging Face dataset {dataset_name}.") - text_feature = hf_args.get("text_feature") - prompt_feature = hf_args.get("prompt_feature") - completion_feature = hf_args.get("completion_feature") + names = ("train", "valid", "test") + train, valid, test = [load_subset(data_path / f"{n}.jsonl") for n in names] + return train, valid, test - def create_hf_dataset(split: str = None): - ds = datasets.load_dataset( - dataset_name, - split=split, - **hf_args.get("config", {}), - ) - if prompt_feature and completion_feature: - return CompletionsDataset( - ds, tokenizer, prompt_feature, completion_feature - ) - elif text_feature: - return Dataset(train_ds, text_key=text_feature) - else: - raise ValueError( - "Specify either a prompt and completion feature or a text " - "feature for the Hugging Face dataset." - ) - if args.train: - train_split = hf_args.get("train_split", "train[:80%]") - valid_split = hf_args.get("valid_split", "train[-10%:]") - train = create_hf_dataset(split=train_split) - valid = create_hf_dataset(split=valid_split) - else: - train, valid = [], [] - if args.test: - test = create_hf_dataset(split=hf_args.get("test_split")) - else: - test = [] +def load_hf_dataset(data_id: str, tokenizer: PreTrainedTokenizer): + from datasets import exceptions, load_dataset + + try: + dataset = load_dataset(data_id) - else: names = ("train", "valid", "test") - data_path = Path(args.data) train, valid, test = [ - create_dataset(data_path / f"{n}.jsonl", tokenizer) for n in names + create_dataset(dataset[n], tokenizer) if n in dataset.keys() else [] + for n in names ] + + except exceptions.DatasetNotFoundError: + raise ValueError(f"Not found Hugging Face dataset: {data_id} .") + + return train, valid, test + + +def load_custom_hf_dataset(args, tokenizer: PreTrainedTokenizer): + import datasets + + hf_args = args.hf_dataset + dataset_name = hf_args["name"] + print(f"Loading Hugging Face dataset {dataset_name}.") + text_feature = hf_args.get("text_feature") + prompt_feature = hf_args.get("prompt_feature") + completion_feature = hf_args.get("completion_feature") + + def create_hf_dataset(split: str = None): + ds = datasets.load_dataset( + dataset_name, + split=split, + **hf_args.get("config", {}), + ) + if prompt_feature and completion_feature: + return CompletionsDataset(ds, tokenizer, prompt_feature, completion_feature) + elif text_feature: + return Dataset(train_ds, text_key=text_feature) + else: + raise ValueError( + "Specify either a prompt and completion feature or a text " + "feature for the Hugging Face dataset." + ) + + if args.train: + train_split = hf_args.get("train_split", "train[:80%]") + valid_split = hf_args.get("valid_split", "train[-10%:]") + train = create_hf_dataset(split=train_split) + valid = create_hf_dataset(split=valid_split) + else: + train, valid = [], [] + if args.test: + test = create_hf_dataset(split=hf_args.get("test_split")) + else: + test = [] + + return train, valid, test + + +def load_dataset(args, tokenizer: PreTrainedTokenizer): + if getattr(args, "hf_dataset", None) is not None: + train, valid, test = load_custom_hf_dataset(args, tokenizer) + else: + data_path = Path(args.data) + if data_path.exists(): + train, valid, test = load_local_dataset(data_path, tokenizer) + else: + print(f"Loading Hugging Face dataset {args.data}.") + train, valid, test = load_hf_dataset(args.data, tokenizer) + if args.train and len(train) == 0: raise ValueError( "Training set not found or empty. Must provide training set for fine-tuning." From 418d9a5511fbefced1229cfed1d311a771aa5db5 Mon Sep 17 00:00:00 2001 From: Zai Thottakath Date: Mon, 30 Sep 2024 10:01:11 -0500 Subject: [PATCH 039/188] Feature: QDoRA (#891) * feat: QDoRA with tests and a small bug fix for recalculation of self.m * some simplifications and fixes --------- Co-authored-by: Awni Hannun --- llms/mlx_lm/tuner/dora.py | 51 ++++++++++--- llms/tests/test_finetune.py | 143 +++++++++++++++++++++++++++++++++++- 2 files changed, 183 insertions(+), 11 deletions(-) diff --git a/llms/mlx_lm/tuner/dora.py b/llms/mlx_lm/tuner/dora.py index bd2dfb01..aba1f6f4 100644 --- a/llms/mlx_lm/tuner/dora.py +++ b/llms/mlx_lm/tuner/dora.py @@ -14,10 +14,11 @@ class DoRALinear(nn.Module): dropout: float = 0.0, scale: float = 20.0, ): - # TODO support quantized weights in DoRALinear + # TODO remove when input_dims and output_dims are attributes + # on linear and quantized linear output_dims, input_dims = linear.weight.shape if isinstance(linear, nn.QuantizedLinear): - raise ValueError("DoRALinear does not yet support quantization.") + input_dims *= 32 // linear.bits dora_lin = DoRALinear( input_dims=input_dims, output_dims=output_dims, @@ -31,13 +32,13 @@ class DoRALinear(nn.Module): def fuse(self, de_quantize: bool = False): linear = self.linear bias = "bias" in linear - weight = linear.weight + weight = self._dequantized_weight() - # Use the same type as the linear weight if not quantized + # Use the same type as the linear weight dtype = weight.dtype output_dims, input_dims = weight.shape - fused_linear = nn.Linear(input_dims, output_dims, bias=bias) + fused_linear = nn.Linear(input_dims, output_dims, bias=False) lora_b = (self.scale * self.lora_b.T).astype(dtype) lora_a = self.lora_a.T.astype(dtype) @@ -47,6 +48,13 @@ class DoRALinear(nn.Module): if bias: fused_linear.bias = linear.bias + + if self._is_quantized() and not de_quantize: + fused_linear = nn.QuantizedLinear.from_linear( + fused_linear, + linear.group_size, + linear.bits, + ) return fused_linear def __init__( @@ -76,22 +84,45 @@ class DoRALinear(nn.Module): ) self.lora_b = mx.zeros(shape=(r, output_dims)) - def set_linear(self, linear: nn.Linear): + def set_linear(self, linear): + """ + Set the self.linear layer and recompute self.m. + """ self.linear = linear - self.m = mx.linalg.norm(self.linear.weight, axis=1) + self.m = mx.linalg.norm(self._dequantized_weight().astype(mx.float32), axis=1) + + def _dequantized_weight(self): + """ + Return the weight of linear layer and dequantize it if is quantized + """ + weight = self.linear.weight + if self._is_quantized(): + weight = mx.dequantize( + weight, + self.linear.scales, + self.linear.biases, + self.linear.group_size, + self.linear.bits, + ) + return weight + + def _is_quantized(self): + return isinstance(self.linear, nn.QuantizedLinear) def __call__(self, x): # Regular LoRA (without a bias) - y = x @ self.linear.weight.T + w = self._dequantized_weight() + y = x @ w.T + z = (self.dropout(x) @ self.lora_a) @ self.lora_b out = y + (self.scale * z).astype(x.dtype) # Compute the norm of the adapted weights - adapted = self.linear.weight + (self.scale * self.lora_b.T) @ self.lora_a.T + adapted = w + (self.scale * self.lora_b.T) @ self.lora_a.T denom = mx.stop_gradient(mx.linalg.norm(adapted, axis=1)) # Remove the norm and scale by the learned magnitude - out = (self.m / denom) * out + out = (self.m / denom).astype(x.dtype) * out if "bias" in self.linear: out = out + self.linear.bias diff --git a/llms/tests/test_finetune.py b/llms/tests/test_finetune.py index 289b8cfb..107be092 100644 --- a/llms/tests/test_finetune.py +++ b/llms/tests/test_finetune.py @@ -11,7 +11,7 @@ import mlx.nn as nn import mlx.optimizers as opt from mlx.utils import tree_flatten from mlx_lm import lora, tuner -from mlx_lm.tuner.dora import DoRAEmbedding +from mlx_lm.tuner.dora import DoRAEmbedding, DoRALinear from mlx_lm.tuner.lora import LoRAEmbedding, LoRALinear from mlx_lm.tuner.trainer import evaluate from mlx_lm.tuner.utils import build_schedule @@ -164,6 +164,147 @@ class TestDora(unittest.TestCase): self.assertFalse(mx.array_equal(embedding.weight, new_embedding.weight)) self.assertFalse(mx.array_equal(embedding(tokens), dora_emb(tokens))) + def test_llama(self): + from mlx_lm.models import llama + + hidden_size = 1024 + intermediate_size = 2048 + args = llama.ModelArgs( + model_type="llama", + hidden_size=hidden_size, + num_hidden_layers=4, + intermediate_size=intermediate_size, + num_attention_heads=4, + rms_norm_eps=1e-5, + vocab_size=10_000, + ) + + dora_layers = 4 + + def check_config(params): + n_keys = 2 + if "keys" in params: + n_keys = len(params["keys"]) + model = llama.Model(args) + model.freeze() + tuner.utils.linear_to_lora_layers(model, dora_layers, params, use_dora=True) + trainable_params = sum( + v.size for _, v in tree_flatten(model.trainable_parameters()) + ) + self.assertEqual( + trainable_params, + dora_layers + * (params["rank"] * hidden_size * 2 * n_keys + n_keys * hidden_size), + ) + + params = {"rank": 8, "alpha": 16, "dropout": 0.0, "scale": 10.0} + check_config(params) + + params["rank"] = 1 + check_config(params) + + params["keys"] = ["self_attn.k_proj"] + check_config(params) + + def test_dora_m_parameter(self): + dora_lin = DoRALinear(input_dims=100, output_dims=100) + self.assertTrue( + mx.allclose(dora_lin.m, mx.linalg.norm(dora_lin.linear.weight, axis=1)) + ) + + # Recomputes m when changing Linear + inital_m = dora_lin.m + lin = nn.Linear(10, 10) + dora_lin.set_linear(lin) + self.assertTrue(mx.allclose(dora_lin.m, mx.linalg.norm(lin.weight, axis=1))) + + # Works with quantized weights + quantized_linear = nn.QuantizedLinear(512, 512) + dora_lin.set_linear(quantized_linear) + dequantized_weight = mx.dequantize( + quantized_linear.weight, + quantized_linear.scales, + quantized_linear.biases, + quantized_linear.group_size, + quantized_linear.bits, + ) + self.assertTrue( + mx.allclose(dora_lin.m, mx.linalg.norm(dequantized_weight, axis=1)) + ) + + def test_dora_from_linear(self): + in_dims = 256 + out_dims = 256 + r = 4 + + linear = nn.Linear(in_dims, out_dims) + dora_lin = DoRALinear.from_base(linear, r) + self.assertTrue(mx.allclose(dora_lin.m, mx.linalg.norm(linear.weight, axis=1))) + self.assertEqual(dora_lin.lora_a.shape, (in_dims, r)) + self.assertEqual(dora_lin.lora_b.shape, (r, out_dims)) + self.assertEqual(dora_lin.m.shape, (out_dims,)) + + quantized_linear = nn.QuantizedLinear(in_dims, out_dims) + dequantized_weight = mx.dequantize( + quantized_linear.weight, + quantized_linear.scales, + quantized_linear.biases, + quantized_linear.group_size, + quantized_linear.bits, + ) + dora_quant_lin = DoRALinear.from_base(quantized_linear, r) + self.assertTrue( + mx.allclose(dora_quant_lin.m, mx.linalg.norm(dequantized_weight, axis=1)) + ) + self.assertEqual(dora_quant_lin.lora_a.shape, (in_dims, r)) + self.assertEqual(dora_quant_lin.lora_b.shape, (r, out_dims)) + self.assertEqual(dora_quant_lin.m.shape, (out_dims,)) + + def test_dora_to_linear(self): + in_dims = 256 + out_dims = 256 + r = 4 + + linear = nn.Linear(in_dims, out_dims, bias=True) + dora_lin = DoRALinear.from_base(linear, r) + to_linear = dora_lin.fuse() + self.assertTrue(mx.allclose(linear.weight, to_linear.weight)) + self.assertTrue(mx.allclose(linear.bias, to_linear.bias)) + + def dequantize_weight(quantized_linear): + return mx.dequantize( + quantized_linear.weight, + quantized_linear.scales, + quantized_linear.biases, + quantized_linear.group_size, + quantized_linear.bits, + ) + + quantized_linear = nn.QuantizedLinear(in_dims, out_dims, bias=True) + dora_quantized_linear = DoRALinear.from_base(quantized_linear, r) + # Dequantize + to_linear_from_quantized = dora_quantized_linear.fuse(de_quantize=True) + self.assertTrue( + mx.allclose(quantized_linear.bias, to_linear_from_quantized.bias) + ) + self.assertTrue( + mx.allclose( + dequantize_weight(quantized_linear), to_linear_from_quantized.weight + ) + ) + + def test_dora_dtype(self): + in_dims = 256 + out_dims = 256 + r = 4 + + linear = nn.Linear(in_dims, out_dims, bias=True) + linear.set_dtype(mx.float16) + dora_lin = DoRALinear.from_base(linear, r) + + x = mx.random.uniform(shape=(2, 256)).astype(mx.float16) + self.assertEqual(dora_lin(x).dtype, mx.float16) + class TestScheduleConfig(unittest.TestCase): def test_join(self): From 0866e23a67c3d5ab8dbac352b112f13967103942 Mon Sep 17 00:00:00 2001 From: nathan <97126670+nathanrchn@users.noreply.github.com> Date: Mon, 30 Sep 2024 17:49:03 +0200 Subject: [PATCH 040/188] repetiton_penalty and logits_bias just using logits_processors (#1004) * refactor of repetition_penalty and logits_bias to use logits_processor * nits --------- Co-authored-by: Awni Hannun --- llms/mlx_lm/utils.py | 66 ++++++++++++++++++------------------- llms/tests/test_generate.py | 2 +- 2 files changed, 33 insertions(+), 35 deletions(-) diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index 9411138d..54a96457 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -101,7 +101,7 @@ def get_model_path(path_or_hf_repo: str, revision: Optional[str] = None) -> Path return model_path -def apply_repetition_penalty(logits: mx.array, generated_tokens: Any, penalty: float): +def apply_repetition_penalty(logits: mx.array, tokens: mx.array, penalty: float): """ Apply repetition penalty to specific logits based on the given context. @@ -109,19 +109,18 @@ def apply_repetition_penalty(logits: mx.array, generated_tokens: Any, penalty: f Args: logits (mx.array): The logits produced by the language model. - generated_tokens (any): A list of N previous tokens. + tokens (mx.array): A list of N previous tokens. penalty (float): The repetition penalty factor to be applied. Returns: logits (mx.array): Logits with repetition penalty applied to generated tokens. """ - if len(generated_tokens) > 0: - indices = mx.array([token for token in generated_tokens]) - selected_logits = logits[:, indices] + if len(tokens) > 0: + selected_logits = logits[:, tokens] selected_logits = mx.where( selected_logits < 0, selected_logits * penalty, selected_logits / penalty ) - logits[:, indices] = selected_logits + logits[:, tokens] = selected_logits return logits @@ -158,7 +157,7 @@ def generate_step( max_kv_size: Optional[int] = None, cache_history: Optional[List[Tuple[mx.array, mx.array]]] = None, logit_bias: Optional[Dict[int, float]] = None, - logits_processor: Optional[Callable[[mx.array, mx.array], mx.array]] = None, + logits_processor: Optional[List[Callable[[mx.array, mx.array], mx.array]]] = None, ) -> Generator[Tuple[mx.array, mx.array], None, None]: """ A generator producing token ids based on the given prompt from the model. @@ -182,8 +181,8 @@ def generate_step( max_kv_size (int, optional): Maximum size of the key-value cache. Old entries (except the first 4 tokens) will be overwritten. logit_bias (dictionary, optional): Additive logit bias. - logits_processor (Callable[[mx.array, mx.array], mx.array], optional): - A function that takes tokens and logits and returns the processed + logits_processor (List[Callable[[mx.array, mx.array], mx.array]], optional): + A list of functions that take tokens and logits and return the processed logits. Default: ``None``. Yields: @@ -213,6 +212,27 @@ def generate_step( f"repetition_penalty must be a non-negative float, got {repetition_penalty}" ) + logits_processor = logits_processor or [] + + if repetition_penalty: + + def repetition_penalty_processor(tokens, logits): + return apply_repetition_penalty( + logits, tokens[-repetition_context_size:], repetition_penalty + ) + + logits_processor.append(repetition_penalty_processor) + + if logit_bias: + indices = mx.array(list(logit_bias.keys())) + values = mx.array(list(logit_bias.values())) + + def logit_bias_processor(_, logits): + logits[:, indices] += values + return logits + + logits_processor.append(logit_bias_processor) + y = prompt tokens = None @@ -229,40 +249,18 @@ def generate_step( c.update_and_fetch(h[0], h[1]) mx.eval([c.state for c in cache]) - repetition_context = prompt.tolist() - - if repetition_context_size: - repetition_context = repetition_context[-repetition_context_size:] - - if logit_bias: - indices = mx.array(list(logit_bias.keys())) - values = mx.array(list(logit_bias.values())) - def _step(y): - nonlocal repetition_context logits = model(y[None], cache=cache) logits = logits[:, -1, :] if logits_processor: nonlocal tokens tokens = mx.concat([tokens, y]) if tokens is not None else y - logits = logits_processor(tokens, logits) - if logit_bias: - logits[:, indices] += values + for processor in logits_processor: + logits = processor(tokens, logits) - if repetition_penalty: - logits = apply_repetition_penalty( - logits, repetition_context, repetition_penalty - ) - y, logprobs = sample(logits) - repetition_context.append(y.item()) - else: - y, logprobs = sample(logits) - - if repetition_context_size: - if len(repetition_context) > repetition_context_size: - repetition_context = repetition_context[-repetition_context_size:] + y, logprobs = sample(logits) return y, logprobs.squeeze(0) while y.size > prefill_step_size: diff --git a/llms/tests/test_generate.py b/llms/tests/test_generate.py index bc969844..68f1670b 100644 --- a/llms/tests/test_generate.py +++ b/llms/tests/test_generate.py @@ -46,7 +46,7 @@ class TestGenerate(unittest.TestCase): "hello", max_tokens=5, verbose=False, - logits_processor=logits_processor, + logits_processor=[logits_processor], ) self.assertEqual(len(all_toks), len(init_toks) + 5) From 36c1d8e8dcd42a7104ab532ef4ae003c08f41c5f Mon Sep 17 00:00:00 2001 From: madroid Date: Thu, 3 Oct 2024 03:36:07 +0800 Subject: [PATCH 041/188] Server: support function calling (#1003) --- llms/mlx_lm/server.py | 1 + 1 file changed, 1 insertion(+) diff --git a/llms/mlx_lm/server.py b/llms/mlx_lm/server.py index f2d8b86a..42962b54 100644 --- a/llms/mlx_lm/server.py +++ b/llms/mlx_lm/server.py @@ -594,6 +594,7 @@ class APIHandler(BaseHTTPRequestHandler): ): prompt = self.tokenizer.apply_chat_template( body["messages"], + body.get("tools", None), tokenize=True, add_generation_prompt=True, ) From 9bc53fc2100319d59179a179efe34346372772cf Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Wed, 2 Oct 2024 13:13:33 -0700 Subject: [PATCH 042/188] convert (#1006) --- whisper/convert.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/whisper/convert.py b/whisper/convert.py index da7195e0..cdd50bc5 100644 --- a/whisper/convert.py +++ b/whisper/convert.py @@ -35,6 +35,8 @@ _MODELS = { "large-v2": "https://openaipublic.azureedge.net/main/whisper/models/81f7c96c852ee8fc832187b0132e569d6c3065a3252ed18e56effd0b6a73e524/large-v2.pt", "large-v3": "https://openaipublic.azureedge.net/main/whisper/models/e5b1a55b89c1367dacf97e3e19bfd829a01529dbfdeefa8caeb59b3f1b81dadb/large-v3.pt", "large": "https://openaipublic.azureedge.net/main/whisper/models/e5b1a55b89c1367dacf97e3e19bfd829a01529dbfdeefa8caeb59b3f1b81dadb/large-v3.pt", + "large-v3-turbo": "https://openaipublic.azureedge.net/main/whisper/models/aff26ae408abcba5fbf8813c21e62b0941638c5f6eebfb145be0c9839262a19a/large-v3-turbo.pt", + "turbo": "https://openaipublic.azureedge.net/main/whisper/models/aff26ae408abcba5fbf8813c21e62b0941638c5f6eebfb145be0c9839262a19a/large-v3-turbo.pt", } # base85-encoded (n_layers, n_heads) boolean arrays indicating the cross-attention heads that are @@ -52,6 +54,8 @@ _ALIGNMENT_HEADS = { "large-v2": b"ABzY8zd+h!0{>%R7=D0pU<_bnWW*tkYAhobTNnu$jnkEkXqp)j;w1Tzk)UH3X%SZd&fFZ2fC2yj", "large-v3": b"ABzY8gWO1E0{>%R7(9S+Kn!D~%ngiGaR?*L!iJG9p-nab0JQ=-{D1-g00", "large": b"ABzY8gWO1E0{>%R7(9S+Kn!D~%ngiGaR?*L!iJG9p-nab0JQ=-{D1-g00", + "large-v3-turbo": b"ABzY8j^C+e0{>%RARaKHP%t(lGR*)0g!tONPyhe`", + "turbo": b"ABzY8j^C+e0{>%RARaKHP%t(lGR*)0g!tONPyhe`", } From fca087be4906332ccc086013e96da93981e57037 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Mon, 7 Oct 2024 20:45:51 -0700 Subject: [PATCH 043/188] More cache improvements (#1015) * fix rotating kv cache for chat use case * reorg + fixes to caching, unify prompt caching across types and use cases for e.g. caching during a chat * nit in chat * fix tests * fix tests * fix tests * docs * chat command * comments + docs * Define meta_state on all Cache implementations * fixes + trim_prompt_cache api * fix default model --------- Co-authored-by: Angelos Katharopoulos --- .gitignore | 3 + llms/README.md | 39 ++- llms/mlx_lm/_version.py | 2 +- llms/mlx_lm/cache_prompt.py | 19 +- llms/mlx_lm/chat.py | 82 ++++++ llms/mlx_lm/examples/chat.py | 53 ++++ llms/mlx_lm/examples/generate_response.py | 2 + llms/mlx_lm/generate.py | 67 ++--- llms/mlx_lm/models/base.py | 155 +--------- llms/mlx_lm/models/cache.py | 333 ++++++++++++++++++++++ llms/mlx_lm/models/cohere.py | 14 +- llms/mlx_lm/models/dbrx.py | 16 +- llms/mlx_lm/models/deepseek.py | 24 +- llms/mlx_lm/models/deepseek_v2.py | 36 +-- llms/mlx_lm/models/gemma.py | 14 +- llms/mlx_lm/models/gemma2.py | 20 +- llms/mlx_lm/models/gpt2.py | 14 +- llms/mlx_lm/models/gpt_bigcode.py | 14 +- llms/mlx_lm/models/gpt_neox.py | 14 +- llms/mlx_lm/models/internlm2.py | 14 +- llms/mlx_lm/models/llama.py | 18 +- llms/mlx_lm/models/mamba.py | 18 +- llms/mlx_lm/models/minicpm.py | 14 +- llms/mlx_lm/models/mixtral.py | 14 +- llms/mlx_lm/models/nemotron.py | 18 +- llms/mlx_lm/models/olmo.py | 18 +- llms/mlx_lm/models/openelm.py | 14 +- llms/mlx_lm/models/phi.py | 12 +- llms/mlx_lm/models/phi3.py | 16 +- llms/mlx_lm/models/phi3small.py | 21 +- llms/mlx_lm/models/phimoe.py | 9 +- llms/mlx_lm/models/phixtral.py | 12 +- llms/mlx_lm/models/plamo.py | 26 +- llms/mlx_lm/models/qwen.py | 13 +- llms/mlx_lm/models/qwen2.py | 16 +- llms/mlx_lm/models/qwen2_moe.py | 16 +- llms/mlx_lm/models/recurrent_gemma.py | 126 ++------ llms/mlx_lm/models/stablelm.py | 13 +- llms/mlx_lm/models/starcoder2.py | 16 +- llms/mlx_lm/utils.py | 51 +--- llms/setup.py | 1 + llms/tests/test_models.py | 225 ++++++++++++++- llms/tests/test_prompt_cache.py | 220 ++++++++++++++ 43 files changed, 1151 insertions(+), 691 deletions(-) create mode 100644 llms/mlx_lm/chat.py create mode 100644 llms/mlx_lm/examples/chat.py create mode 100644 llms/mlx_lm/models/cache.py create mode 100644 llms/tests/test_prompt_cache.py diff --git a/.gitignore b/.gitignore index f3dfe929..45445fc8 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ __pycache__/ # C extensions *.so +# Vim +*.swp + # Distribution / packaging .Python build/ diff --git a/llms/README.md b/llms/README.md index 75677865..20863041 100644 --- a/llms/README.md +++ b/llms/README.md @@ -20,6 +20,31 @@ The `mlx-lm` package also has: - [Merging models](https://github.com/ml-explore/mlx-examples/blob/main/llms/mlx_lm/MERGE.md) - [HTTP model serving](https://github.com/ml-explore/mlx-examples/blob/main/llms/mlx_lm/SERVER.md) +### Quick Start + +To generate text with an LLM use: + +```bash +mlx_lm.generate --prompt "Hi!" +``` + +To chat with an LLM use: + +```bash +mlx_lm.chat +``` + +This will give you a chat REPL that you can use to interact with the LLM. The +chat context is preserved during the lifetime of the REPL. + +Commands in `mlx-lm` typically take command line options which let you specify +the model, sampling parameters, and more. Use `-h` to see a list of available +options for a command, e.g.: + +```bash +mlx_lm.generate -h +``` + ### Python API You can use `mlx-lm` as a module: @@ -138,7 +163,7 @@ mlx_lm.convert \ ### Long Prompts and Generations -MLX LM has some tools to scale efficiently to long prompts and generations: +`mlx-lm` has some tools to scale efficiently to long prompts and generations: - A rotating fixed-size key-value cache. - Prompt caching @@ -155,14 +180,14 @@ different queries. To cache a prompt use `mlx_lm.cache_prompt`. For example: cat prompt.txt | mlx_lm.cache_prompt \ --model mistralai/Mistral-7B-Instruct-v0.3 \ --prompt - \ - --kv-cache-file mistral_prompt.safetensors + --prompt-cache-file mistral_prompt.safetensors ``` Then use the cached prompt with `mlx_lm.generate`: ``` mlx_lm.generate \ - --kv-cache-file mistral_prompt.safetensors \ + --prompt-cache-file mistral_prompt.safetensors \ --prompt "\nSummarize the above text." ``` @@ -170,9 +195,15 @@ The cached prompt is treated as a prefix to the supplied prompt. Also notice when using a cached prompt, the model to use is read from the cache and need not be supplied explicitly. +Prompt caching can also be used in the Python API in order to to avoid +recomputing the prompt. This is useful in multi-turn dialogues or across +requests that use the same context. See the +[example](https://github.com/ml-explore/mlx-examples/blob/main/llms/mlx_lm/examples/chat.py) +for more usage details. + ### Supported Models -MLX LM supports thousands of Hugging Face format LLMs. If the model you want to +`mlx-lm` supports thousands of Hugging Face format LLMs. If the model you want to run is not supported, file an [issue](https://github.com/ml-explore/mlx-examples/issues/new) or better yet, submit a pull request. diff --git a/llms/mlx_lm/_version.py b/llms/mlx_lm/_version.py index 8110c823..70239db6 100644 --- a/llms/mlx_lm/_version.py +++ b/llms/mlx_lm/_version.py @@ -1,3 +1,3 @@ # Copyright © 2023-2024 Apple Inc. -__version__ = "0.18.2" +__version__ = "0.19.1" diff --git a/llms/mlx_lm/cache_prompt.py b/llms/mlx_lm/cache_prompt.py index 9829efb4..04e75a3e 100644 --- a/llms/mlx_lm/cache_prompt.py +++ b/llms/mlx_lm/cache_prompt.py @@ -7,13 +7,14 @@ import time import mlx.core as mx -from .utils import load, make_kv_caches +from .models.cache import make_prompt_cache, save_prompt_cache +from .utils import load def setup_arg_parser(): """Set up and return the argument parser.""" parser = argparse.ArgumentParser( - description="Cache the KV cache of a prompt to be reused with mlx_lm.generate" + description="Cache the state of a prompt to be reused with mlx_lm.generate" ) parser.add_argument( "--model", @@ -60,7 +61,9 @@ def setup_arg_parser(): help="Set the maximum key-value cache size", ) parser.add_argument( - "--kv-cache-file", help="The file to save the KV caches in", required=True + "--prompt-cache-file", + help="The file to save the prompt cache in", + required=True, ) parser.add_argument( "--prompt", @@ -115,7 +118,7 @@ def main(): else: prompt = args.prompt - cache = make_kv_caches(model, args.max_kv_size) + cache = make_prompt_cache(model, args.max_kv_size) y = mx.array(tokenizer.encode(prompt)) # Process the prompt @@ -137,16 +140,12 @@ def main(): print(f"Peak memory: {mx.metal.get_peak_memory() / 2**30:.3f} GB") print("Saving...") - cache_dict = {} - for i, c in enumerate(cache): - cache_dict[f"{i}_keys"] = c.state[0][..., : c.offset, :] - cache_dict[f"{i}_values"] = c.state[1][..., : c.offset, :] metadata = {} metadata["model"] = args.model metadata["chat_template"] = tokenizer.chat_template metadata["tokenizer_config"] = json.dumps(tokenizer_config) - metadata["max_kv_size"] = str(args.max_kv_size) - mx.save_safetensors(args.kv_cache_file, cache_dict, metadata) + print(f"Peak memory: {mx.metal.get_peak_memory() / 2**30:.3f} GB") + save_prompt_cache(args.prompt_cache_file, cache, metadata) if __name__ == "__main__": diff --git a/llms/mlx_lm/chat.py b/llms/mlx_lm/chat.py new file mode 100644 index 00000000..7968a868 --- /dev/null +++ b/llms/mlx_lm/chat.py @@ -0,0 +1,82 @@ +# Copyright © 2023-2024 Apple Inc. + +import argparse +import json + +import mlx.core as mx + +from .models.cache import load_prompt_cache, make_prompt_cache, save_prompt_cache +from .utils import load, stream_generate + +DEFAULT_TEMP = 0.0 +DEFAULT_TOP_P = 1.0 +DEFAULT_SEED = 0 +DEFAULT_MODEL = "mlx-community/Llama-3.2-3B-Instruct-4bit" + + +def setup_arg_parser(): + """Set up and return the argument parser.""" + parser = argparse.ArgumentParser(description="Chat with an LLM") + parser.add_argument( + "--model", + type=str, + help="The path to the local model directory or Hugging Face repo.", + default=DEFAULT_MODEL, + ) + parser.add_argument( + "--adapter-path", + type=str, + help="Optional path for the trained adapter weights and config.", + ) + parser.add_argument( + "--temp", type=float, default=DEFAULT_TEMP, help="Sampling temperature" + ) + parser.add_argument( + "--top-p", type=float, default=DEFAULT_TOP_P, help="Sampling top-p" + ) + parser.add_argument("--seed", type=int, default=DEFAULT_SEED, help="PRNG seed") + parser.add_argument( + "--max-kv-size", + type=int, + help="Set the maximum key-value cache size", + default=None, + ) + return parser + + +def main(): + parser = setup_arg_parser() + args = parser.parse_args() + + mx.random.seed(args.seed) + + model, tokenizer = load( + args.model, + adapter_path=args.adapter_path, + tokenizer_config={"trust_remote_code": True}, + ) + + print(f"[INFO] Starting chat sessiong with {args.model}. To exit, enter 'q'.") + prompt_cache = make_prompt_cache(model, args.max_kv_size) + while True: + query = input(">> ") + if query == "q": + break + messages = [{"role": "user", "content": query}] + prompt = tokenizer.apply_chat_template( + messages, tokenize=False, add_generation_prompt=True + ) + for response in stream_generate( + model, + tokenizer, + prompt, + temp=args.temp, + top_p=args.top_p, + prompt_cache=prompt_cache, + ): + print(response, flush=True, end="") + print() + + +if __name__ == "__main__": + main() diff --git a/llms/mlx_lm/examples/chat.py b/llms/mlx_lm/examples/chat.py new file mode 100644 index 00000000..3bf01688 --- /dev/null +++ b/llms/mlx_lm/examples/chat.py @@ -0,0 +1,53 @@ +# Copyright © 2024 Apple Inc. + +""" +An example of a multi-turn chat with prompt caching. +""" + +from mlx_lm import generate, load +from mlx_lm.models.cache import load_prompt_cache, make_prompt_cache, save_prompt_cache + +model, tokenizer = load("mlx-community/Mistral-7B-Instruct-v0.3-4bit") + +# Make the initial prompt cache for the model +prompt_cache = make_prompt_cache(model) + +# User turn +prompt = "Hi my name is ." +messages = [{"role": "user", "content": prompt}] +prompt = tokenizer.apply_chat_template( + messages, tokenize=False, add_generation_prompt=True +) + +# Assistant response +response = generate( + model, + tokenizer, + prompt=prompt, + verbose=True, + temp=0.0, + prompt_cache=prompt_cache, +) + +# User turn +prompt = "What's my name?" +messages = [{"role": "user", "content": prompt}] +prompt = tokenizer.apply_chat_template( + messages, tokenize=False, add_generation_prompt=True +) + +# Assistant response +response = generate( + model, + tokenizer, + prompt=prompt, + verbose=True, + temp=0.0, + prompt_cache=prompt_cache, +) + +# Save the prompt cache to disk to reuse it at a later time +save_prompt_cache("mistral_prompt.safetensors", prompt_cache) + +# Load the prompt cache from disk +prompt_cache = load_prompt_cache("mistral_prompt.safetensors") diff --git a/llms/mlx_lm/examples/generate_response.py b/llms/mlx_lm/examples/generate_response.py index af599c1b..25730617 100644 --- a/llms/mlx_lm/examples/generate_response.py +++ b/llms/mlx_lm/examples/generate_response.py @@ -1,3 +1,5 @@ +# Copyright © 2024 Apple Inc. + from mlx_lm import generate, load # Specify the checkpoint diff --git a/llms/mlx_lm/generate.py b/llms/mlx_lm/generate.py index 537bd853..0bf98ab2 100644 --- a/llms/mlx_lm/generate.py +++ b/llms/mlx_lm/generate.py @@ -6,13 +6,15 @@ import sys import mlx.core as mx +from .models.cache import load_prompt_cache from .utils import generate, load DEFAULT_PROMPT = "hello" DEFAULT_MAX_TOKENS = 100 -DEFAULT_TEMP = 0.6 +DEFAULT_TEMP = 0.0 DEFAULT_TOP_P = 1.0 DEFAULT_SEED = 0 +DEFAULT_MODEL = "mlx-community/Llama-3.2-3B-Instruct-4bit" def str2bool(string): @@ -25,7 +27,11 @@ def setup_arg_parser(): parser.add_argument( "--model", type=str, - help="The path to the local model directory or Hugging Face repo.", + help=( + "The path to the local model directory or Hugging Face repo. " + f"If no model is specified, then {DEFAULT_MODEL} is used." + ), + default=None, ) parser.add_argument( "--adapter-path", @@ -96,7 +102,7 @@ def setup_arg_parser(): default=None, ) parser.add_argument( - "--kv-cache-file", + "--prompt-cache-file", type=str, default=None, help="A file containing saved KV caches to avoid recomputing them", @@ -131,24 +137,6 @@ def colorprint_by_t0(s, t0): colorprint(color, s) -def load_kv_cache_from_file(kv_cache_file): - if kv_cache_file is None: - return None, None - - kv_cache, metadata = mx.load(kv_cache_file, return_metadata=True) - cache_per_layer = {} - for k, x in kv_cache.items(): - layer, kv_type = k.split("_") - if layer not in cache_per_layer: - cache_per_layer[layer] = {} - cache_per_layer[layer][kv_type] = x - - cache_history = [None] * len(cache_per_layer) - for layer, c in cache_per_layer.items(): - cache_history[int(layer)] = (c["keys"], c["values"]) - return cache_history, metadata - - def main(): parser = setup_arg_parser() args = parser.parse_args() @@ -158,22 +146,33 @@ def main(): if args.cache_limit_gb is not None: mx.metal.set_cache_limit(args.cache_limit_gb * 1024 * 1024 * 1024) - # Load the kv cache and metadata if a kv cache file is provided - cache_history, metadata = load_kv_cache_from_file(args.kv_cache_file) + # Load the prompt cache and metadata if a cache file is provided + using_cache = args.prompt_cache_file is not None + if using_cache: + prompt_cache, metadata = load_prompt_cache( + args.prompt_cache_file, return_metadata=True + ) # Building tokenizer_config tokenizer_config = ( - {} if cache_history is None else json.loads(metadata["tokenizer_config"]) + {} if not using_cache else json.loads(metadata["tokenizer_config"]) ) if args.trust_remote_code: tokenizer_config["trust_remote_code"] = True if args.eos_token is not None: tokenizer_config["eos_token"] = args.eos_token - # If no model path is provided then use the one in the kv cache history model_path = args.model - if cache_history is not None and model_path is None: - model_path = metadata["model"] + if using_cache: + if model_path is None: + model_path = metadata["model"] + elif model_path != metadata["model"]: + raise ValueError( + f"Providing a different model ({model_path}) than that " + f"used to create the prompt cache ({metadata['model']}) " + "is an error." + ) + model_path = model_path or DEFAULT_MODEL model, tokenizer = load( model_path, @@ -184,7 +183,7 @@ def main(): if args.use_default_chat_template: if tokenizer.chat_template is None: tokenizer.chat_template = tokenizer.default_chat_template - elif cache_history is not None: + elif using_cache: tokenizer.chat_template = metadata["chat_template"] if not args.ignore_chat_template and ( @@ -203,7 +202,7 @@ def main(): # Treat the prompt as a suffix assuming that the prefix is in the # stored kv cache. - if cache_history is not None: + if using_cache: test_prompt = tokenizer.apply_chat_template( [{"role": "user", "content": ""}], tokenize=False, @@ -217,12 +216,6 @@ def main(): raise ValueError("Cannot use --colorize with --verbose=False") formatter = colorprint_by_t0 if args.colorize else None - # Determine the max kv size from the kv cache or passed arguments - max_kv_size = args.max_kv_size - if cache_history is not None: - max_kv_size = metadata["max_kv_size"] - max_kv_size = int(max_kv_size) if max_kv_size.isdigit() else None - response = generate( model, tokenizer, @@ -232,8 +225,8 @@ def main(): formatter=formatter, temp=args.temp, top_p=args.top_p, - max_kv_size=max_kv_size, - cache_history=cache_history, + max_kv_size=args.max_kv_size, + prompt_cache=prompt_cache if using_cache else None, ) if not args.verbose: print(response) diff --git a/llms/mlx_lm/models/base.py b/llms/mlx_lm/models/base.py index dc19dd05..3628a808 100644 --- a/llms/mlx_lm/models/base.py +++ b/llms/mlx_lm/models/base.py @@ -2,145 +2,9 @@ import inspect from dataclasses import dataclass -from typing import Any, List, Optional +from typing import Any, Optional import mlx.core as mx -import mlx.nn as nn - - -class KVCache: - - def __init__(self, head_dim, n_kv_heads): - self.n_kv_heads = n_kv_heads - if isinstance(head_dim, int): - self.k_head_dim = self.v_head_dim = head_dim - elif isinstance(head_dim, tuple) and len(head_dim) == 2: - self.k_head_dim, self.v_head_dim = head_dim - else: - raise ValueError("head_dim must be an int or a tuple of two ints") - self.keys = None - self.values = None - self.offset = 0 - self.step = 256 - - def update_and_fetch(self, keys, values): - prev = self.offset - if self.keys is None or (prev + keys.shape[2]) > self.keys.shape[2]: - B = keys.shape[0] - n_steps = (self.step + keys.shape[2] - 1) // self.step - k_shape = (B, self.n_kv_heads, n_steps * self.step, self.k_head_dim) - v_shape = (B, self.n_kv_heads, n_steps * self.step, self.v_head_dim) - new_k = mx.zeros(k_shape, keys.dtype) - new_v = mx.zeros(v_shape, values.dtype) - if self.keys is not None: - if prev % self.step != 0: - self.keys = self.keys[..., :prev, :] - self.values = self.values[..., :prev, :] - self.keys = mx.concatenate([self.keys, new_k], axis=2) - self.values = mx.concatenate([self.values, new_v], axis=2) - else: - self.keys, self.values = new_k, new_v - - self.offset += keys.shape[2] - self.keys[..., prev : self.offset, :] = keys - self.values[..., prev : self.offset, :] = values - return self.keys[..., : self.offset, :], self.values[..., : self.offset, :] - - @property - def state(self): - return self.keys, self.values - - -class RotatingKVCache: - - def __init__(self, head_dim, n_kv_heads, max_size, keep=0, step=256): - self.n_kv_heads = n_kv_heads - if isinstance(head_dim, int): - self.k_head_dim = self.v_head_dim = head_dim - elif isinstance(head_dim, tuple) and len(head_dim) == 2: - self.k_head_dim, self.v_head_dim = head_dim - else: - raise ValueError("head_dim must be an int or a tuple of two ints") - self.keep = keep - self.keys = None - self.values = None - self.offset = 0 - self.max_size = max_size - self.step = step - self._idx = 0 - - def _trim(self, trim_size, v, append=None): - to_cat = [] - if trim_size > 0: - to_cat = [v[..., : self.keep, :], v[..., trim_size + self.keep :, :]] - else: - to_cat = [v] - if append is not None: - to_cat.append(append) - return mx.concatenate(to_cat, axis=2) - - def update_and_fetch(self, keys, values): - prev = self.offset - B, _, S = keys.shape[:3] - - # Prefill mode - if S > 1: - if self.keys is None: - self.keys = keys - self.values = values - else: - # The largest size is self.max_size + S - 1 to ensure - # every token gets at least self.max_size context - trim_size = self.keys.shape[2] - self.max_size + 1 - self.keys = self._trim(trim_size, self.keys, keys) - self.values = self._trim(trim_size, self.values, values) - self.offset += S - self._idx = self.keys.shape[2] - return self.keys, self.values - - # Generation mode - # May not have hit the max size yet, so potentially - # keep growing the cache - if self.keys is None or ( - prev >= self.keys.shape[2] and self.keys.shape[2] < self.max_size - ): - new_size = min(self.step, self.max_size - prev) - k_shape = (B, self.n_kv_heads, new_size, self.k_head_dim) - v_shape = (B, self.n_kv_heads, new_size, self.v_head_dim) - new_k = mx.zeros(k_shape, keys.dtype) - new_v = mx.zeros(v_shape, values.dtype) - if self.keys is not None: - self.keys = mx.concatenate([self.keys, new_k], axis=2) - self.values = mx.concatenate([self.values, new_v], axis=2) - else: - self.keys, self.values = new_k, new_v - self._idx = prev - - # Trim if needed - trim_size = self.keys.shape[2] - self.max_size - if trim_size > 0: - self.keys = self._trim(trim_size, self.keys) - self.values = self._trim(trim_size, self.values) - self._idx = self.max_size - - # Rotate - if self._idx == self.max_size: - self._idx = self.keep - - # Assign - self.keys[..., self._idx : self._idx + 1, :] = keys - self.values[..., self._idx : self._idx + 1, :] = values - self.offset += 1 - self._idx += 1 - - # If the buffer is not full, slice off the end - if self.offset < self.max_size: - return self.keys[..., : self.offset, :], self.values[..., : self.offset, :] - return self.keys, self.values - - @property - def state(self): - return self.keys, self.values @dataclass @@ -156,25 +20,30 @@ class BaseModelArgs: ) -def create_additive_causal_mask(N: int, offset: int = 0): +def create_causal_mask(N: int, offset: int = 0, window_size: Optional[int] = None): rinds = mx.arange(offset + N) linds = mx.arange(offset, offset + N) if offset else rinds - mask = linds[:, None] < rinds[None] + linds = linds[:, None] + rinds = rinds[None] + mask = linds < rinds + if window_size is not None: + mask = mask | (linds > rinds + window_size) return mask * -1e9 def create_attention_mask(h: mx.array, cache: Optional[Any] = None): T = h.shape[1] if T > 1: + window_size = None + offset = 0 if cache is not None and cache[0] is not None: c = cache[0] - if isinstance(c, RotatingKVCache): + if hasattr(c, "max_size"): offset = min(c.max_size - 1, c.offset) + window_size = c.max_size else: offset = c.offset - else: - offset = 0 - mask = create_additive_causal_mask(T, offset) + mask = create_causal_mask(T, offset, window_size=window_size) mask = mask.astype(h.dtype) else: mask = None diff --git a/llms/mlx_lm/models/cache.py b/llms/mlx_lm/models/cache.py new file mode 100644 index 00000000..b06422e5 --- /dev/null +++ b/llms/mlx_lm/models/cache.py @@ -0,0 +1,333 @@ +# Copyright © 2023-2024 Apple Inc. + +from typing import Any, Dict, List, Optional + +import mlx.core as mx +import mlx.nn as nn +from mlx.utils import tree_flatten, tree_unflatten + + +def make_prompt_cache(model: nn.Module, max_kv_size: Optional[int] = None) -> List[Any]: + """ + Construct the model's cache for use when cgeneration. + + This function will defer the cache construction to the model if it has a + ``make_cache`` method, otherwise it will make a default KV cache. + + Args: + model (nn.Module): The language model. + max_kv_size (Optional[int]): If provided and the model does not have a + ``make_cache`` method, a ``RotatingKVCache`` is used with a maximum + size of ``max_kv_size`` + """ + if hasattr(model, "make_cache"): + return model.make_cache() + + num_layers = len(model.layers) + if max_kv_size is not None: + return [ + RotatingKVCache(max_size=max_kv_size, keep=4) for _ in range(num_layers) + ] + else: + return [KVCache() for _ in range(num_layers)] + + +def save_prompt_cache(file_name: str, cache: List[Any], metadata: Dict[str, str] = {}): + """ + Save a pre-computed prompt cache to a file. + + Args: + file_name (str): The ``.safetensors`` file name. + cache (List[Any]): The model state. + metadata (Dict[str, str]): Optional metadata to save along with model + state. + """ + cache_data = [c.state for c in cache] + cache_info = [c.meta_state for c in cache] + cache_data = dict(tree_flatten(cache_data)) + cache_classes = [type(c).__name__ for c in cache] + cache_metadata = [cache_info, metadata, cache_classes] + cache_metadata = dict(tree_flatten(cache_metadata)) + mx.save_safetensors(file_name, cache_data, cache_metadata) + + +def load_prompt_cache(file_name, return_metadata=False): + """ + Load a prompt cache from a file. + + Args: + file_name (str): The ``.safetensors`` file name. + return_metadata (bool): Whether or not to return metadata. + Default: ``False``. + + Returns: + List[Any] or Tuple[List[Any], Dict[str, str]]: The prompt cache and + the metadata if requested. + """ + arrays, cache_metadata = mx.load(file_name, return_metadata=True) + arrays = tree_unflatten(list(arrays.items())) + cache_metadata = tree_unflatten(list(cache_metadata.items())) + info, metadata, classes = cache_metadata + cache = [globals()[c]() for c in classes] + for c, state, meta_state in zip(cache, arrays, info): + c.state = state + c.meta_state = meta_state + if return_metadata: + return cache, metadata + return cache + + +def trim_prompt_cache(cache: List[Any], num_tokens: int) -> List[Any]: + """ + Trim the model's cache by the given number of tokens. + + This function will trim the cache if possible (in-place) and return the + number of tokens that were trimmed. + + Args: + cache (List[Any]): The model's cache. + num_tokens (int): The number of tokens to trim. + + Returns: + (int): The number of tokens that were trimmed. + """ + if not all(c.is_trimmable() for c in cache) or len(cache) == 0: + return 0 + return [c.trim(num_tokens) for c in cache][0] + + +class _BaseCache: + @property + def state(self): + return [] + + @state.setter + def state(self, v): + if v is not None and v: + raise ValueError("This cache has no state but a state was set.") + + @property + def meta_state(self): + return "" + + @meta_state.setter + def meta_state(self, v): + if v is not None and v: + raise ValueError("This cache has no meta_state but a meta_state was set.") + + def is_trimmable(self): + return False + + +class KVCache(_BaseCache): + def __init__(self): + self.keys = None + self.values = None + self.offset = 0 + self.step = 256 + + def update_and_fetch(self, keys, values): + prev = self.offset + if self.keys is None or (prev + keys.shape[2]) > self.keys.shape[2]: + B, n_kv_heads, _, k_head_dim = keys.shape + v_head_dim = values.shape[3] + n_steps = (self.step + keys.shape[2] - 1) // self.step + k_shape = (B, n_kv_heads, n_steps * self.step, k_head_dim) + v_shape = (B, n_kv_heads, n_steps * self.step, v_head_dim) + new_k = mx.zeros(k_shape, keys.dtype) + new_v = mx.zeros(v_shape, values.dtype) + if self.keys is not None: + if prev % self.step != 0: + self.keys = self.keys[..., :prev, :] + self.values = self.values[..., :prev, :] + self.keys = mx.concatenate([self.keys, new_k], axis=2) + self.values = mx.concatenate([self.values, new_v], axis=2) + else: + self.keys, self.values = new_k, new_v + + self.offset += keys.shape[2] + self.keys[..., prev : self.offset, :] = keys + self.values[..., prev : self.offset, :] = values + return self.keys[..., : self.offset, :], self.values[..., : self.offset, :] + + @property + def state(self): + if self.offset == self.keys.shape[2]: + return self.keys, self.values + else: + return ( + self.keys[..., : self.offset, :], + self.values[..., : self.offset, :], + ) + + @state.setter + def state(self, v): + self.keys, self.values = v + self.offset = self.keys.shape[2] + + def is_trimmable(self): + return True + + def trim(self, n): + n = min(self.offset, n) + self.offset -= n + return n + + +class RotatingKVCache(_BaseCache): + + def __init__(self, max_size=None, keep=0, step=256): + self.keep = keep + self.keys = None + self.values = None + self.offset = 0 + self.max_size = max_size + self.step = step + self._idx = 0 + + def _trim(self, trim_size, v, append=None): + to_cat = [] + if trim_size > 0: + to_cat = [v[..., : self.keep, :], v[..., trim_size + self.keep :, :]] + else: + to_cat = [v] + if append is not None: + to_cat.append(append) + return mx.concatenate(to_cat, axis=2) + + def _temporal_order(self, v): + """ + Rearrange the cache into temporal order, slicing off the end if unused. + """ + if self._idx == v.shape[2]: + return v + elif self._idx < self.offset: + return mx.concatenate( + [ + v[..., : self.keep, :], + v[..., self._idx :, :], + v[..., self.keep : self._idx, :], + ], + axis=2, + ) + else: + return v[..., : self._idx, :] + + def _update_concat(self, keys, values): + if self.keys is None: + self.keys = keys + self.values = values + else: + # Put the keys/values in temporal order to + # preserve context + self.keys = self._temporal_order(self.keys) + self.values = self._temporal_order(self.values) + + # The largest size is self.max_size + S - 1 to ensure + # every token gets at least self.max_size context + trim_size = self._idx - self.max_size + 1 + self.keys = self._trim(trim_size, self.keys, keys) + self.values = self._trim(trim_size, self.values, values) + self.offset += keys.shape[2] + self._idx = self.keys.shape[2] + return self.keys, self.values + + def _update_in_place(self, keys, values): + # May not have hit the max size yet, so potentially + # keep growing the cache + B, n_kv_heads, S, k_head_dim = keys.shape + prev = self.offset + if self.keys is None or ( + prev >= self.keys.shape[2] and self.keys.shape[2] < self.max_size + ): + v_head_dim = values.shape[3] + new_size = min(self.step, self.max_size - prev) + k_shape = (B, n_kv_heads, new_size, k_head_dim) + v_shape = (B, n_kv_heads, new_size, v_head_dim) + new_k = mx.zeros(k_shape, keys.dtype) + new_v = mx.zeros(v_shape, values.dtype) + if self.keys is not None: + self.keys = mx.concatenate([self.keys, new_k], axis=2) + self.values = mx.concatenate([self.values, new_v], axis=2) + else: + self.keys, self.values = new_k, new_v + self._idx = prev + + # Trim if needed + trim_size = self.keys.shape[2] - self.max_size + if trim_size > 0: + self.keys = self._trim(trim_size, self.keys) + self.values = self._trim(trim_size, self.values) + self._idx = self.max_size + + # Rotate + if self._idx == self.max_size: + self._idx = self.keep + + # Assign + self.keys[..., self._idx : self._idx + S, :] = keys + self.values[..., self._idx : self._idx + S, :] = values + self.offset += S + self._idx += S + + # If the buffer is not full, slice off the end + if self.offset < self.max_size: + return self.keys[..., : self.offset, :], self.values[..., : self.offset, :] + return self.keys, self.values + + def update_and_fetch(self, keys, values): + if keys.shape[2] == 1: + return self._update_in_place(keys, values) + return self._update_concat(keys, values) + + @property + def state(self): + if self.offset < self.keys.shape[2]: + return self.keys[..., : self.offset, :], self.values[..., : self.offset, :] + else: + return self.keys, self.values + + @state.setter + def state(self, v): + self.keys, self.values = v + + @property + def meta_state(self): + return tuple( + map(str, (self.keep, self.max_size, self.step, self.offset, self._idx)) + ) + + @meta_state.setter + def meta_state(self, v): + self.keep, self.max_size, self.step, self.offset, self._idx = map( + int, + v, + ) + + def is_trimmable(self): + return self.offset < self.max_size + + def trim(self, n): + n = min(self.offset, n) + self.offset -= n + self._idx -= n + return n + + +class MambaCache(_BaseCache): + def __init__(self): + self.cache = [None, None] + + def __setitem__(self, idx, value): + self.cache[idx] = value + + def __getitem__(self, idx): + return self.cache[idx] + + @property + def state(self): + return self.cache + + @state.setter + def state(self, v): + self.cache = v diff --git a/llms/mlx_lm/models/cohere.py b/llms/mlx_lm/models/cohere.py index cfcf2945..057c816d 100644 --- a/llms/mlx_lm/models/cohere.py +++ b/llms/mlx_lm/models/cohere.py @@ -1,7 +1,7 @@ # Copyright © 2023-2024 Apple Inc. from dataclasses import dataclass -from typing import Optional, Tuple +from typing import Any, Optional, Tuple import mlx.core as mx import mlx.nn as nn @@ -69,7 +69,7 @@ class Attention(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[Tuple[mx.array, mx.array]] = None, + cache: Optional[Any] = None, ) -> mx.array: B, L, D = x.shape @@ -129,7 +129,7 @@ class TransformerBlock(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[Tuple[mx.array, mx.array]] = None, + cache: Optional[Any] = None, ) -> mx.array: h = self.input_layernorm(x) attn_h = self.self_attn(h, mask, cache) @@ -190,11 +190,3 @@ class Model(nn.Module): @property def layers(self): return self.model.layers - - @property - def head_dim(self): - return self.args.hidden_size // self.args.num_attention_heads - - @property - def n_kv_heads(self): - return self.args.num_key_value_heads diff --git a/llms/mlx_lm/models/dbrx.py b/llms/mlx_lm/models/dbrx.py index f0214549..3b7e83d7 100644 --- a/llms/mlx_lm/models/dbrx.py +++ b/llms/mlx_lm/models/dbrx.py @@ -1,7 +1,7 @@ # Copyright © 2023-2024 Apple Inc. from dataclasses import dataclass -from typing import Optional, Tuple +from typing import Any, Optional, Tuple import mlx.core as mx import mlx.nn as nn @@ -49,7 +49,7 @@ class Attention(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[Tuple[mx.array, mx.array]] = None, + cache: Optional[Any] = None, ) -> mx.array: qkv = self.Wqkv(x) @@ -92,7 +92,7 @@ class NormAttnNorm(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[Tuple[mx.array, mx.array]] = None, + cache: Optional[Any] = None, ) -> mx.array: h = self.attn(self.norm_1(x), mask=mask, cache=cache) x = h + x @@ -179,7 +179,7 @@ class DecoderLayer(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[Tuple[mx.array, mx.array]] = None, + cache: Optional[Any] = None, ) -> mx.array: r, h = self.norm_attn_norm(x, mask, cache) out = self.ffn(h) + r @@ -249,11 +249,3 @@ class Model(nn.Module): experts = [(s, sv.T) for s, sv in experts] new_weights.update(experts) return new_weights - - @property - def head_dim(self): - return self.args.d_model // self.args.n_heads - - @property - def n_kv_heads(self): - return self.args.attn_config["kv_n_heads"] diff --git a/llms/mlx_lm/models/deepseek.py b/llms/mlx_lm/models/deepseek.py index dcfa331c..03cb3b1a 100644 --- a/llms/mlx_lm/models/deepseek.py +++ b/llms/mlx_lm/models/deepseek.py @@ -1,10 +1,10 @@ from dataclasses import dataclass -from typing import Dict, Optional +from typing import Any, Dict, Optional import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, KVCache, create_attention_mask +from .base import BaseModelArgs, create_attention_mask from .switch_layers import SwitchGLU @@ -77,7 +77,7 @@ class DeepseekAttention(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[KVCache] = None, + cache: Optional[Any] = None, ) -> mx.array: B, L, _ = x.shape @@ -108,8 +108,8 @@ class DeepseekMLP(nn.Module): def __init__( self, config: ModelArgs, - hidden_size: int | None = None, - intermediate_size: int | None = None, + hidden_size: Optional[int] = None, + intermediate_size: Optional[int] = None, ): super().__init__() self.config = config @@ -188,7 +188,7 @@ class DeepseekDecoderLayer(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[KVCache] = None, + cache: Optional[Any] = None, ) -> mx.array: r = self.self_attn(self.input_layernorm(x), mask, cache) h = x + r @@ -210,7 +210,7 @@ class DeepseekModel(nn.Module): def __call__( self, x: mx.array, - cache: Optional[KVCache] = None, + cache: Optional[Any] = None, ) -> mx.array: h = self.embed_tokens(x) mask = create_attention_mask(h, cache) @@ -235,7 +235,7 @@ class Model(nn.Module): def __call__( self, inputs: mx.array, - cache: Optional[KVCache] = None, + cache: Optional[Any] = None, ): out = self.model(inputs, cache) return self.lm_head(out) @@ -256,11 +256,3 @@ class Model(nn.Module): @property def layers(self): return self.model.layers - - @property - def head_dim(self): - return self.args.hidden_size // self.args.num_attention_heads - - @property - def n_kv_heads(self): - return self.args.num_key_value_heads diff --git a/llms/mlx_lm/models/deepseek_v2.py b/llms/mlx_lm/models/deepseek_v2.py index 602a9710..17d061a8 100644 --- a/llms/mlx_lm/models/deepseek_v2.py +++ b/llms/mlx_lm/models/deepseek_v2.py @@ -2,12 +2,12 @@ import math from dataclasses import dataclass -from typing import Dict, Optional, Tuple +from typing import Any, Dict, Optional, Tuple import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, KVCache, create_attention_mask +from .base import BaseModelArgs, create_attention_mask from .switch_layers import SwitchGLU @@ -38,7 +38,7 @@ class ModelArgs(BaseModelArgs): max_position_embeddings: int = 2048 rms_norm_eps: float = 1e-6 rope_theta: float = 10000.0 - rope_scaling: Optional[Dict] = None + rope_scaling: Dict = None attention_bias: bool = False @@ -172,12 +172,11 @@ class DeepseekV2Attention(nn.Module): bias=config.attention_bias, ) - if self.config.rope_scaling is not None: - mscale_all_dim = self.config.rope_scaling.get("mscale_all_dim", 0) - scaling_factor = self.config.rope_scaling["factor"] - if mscale_all_dim: - mscale = yarn_get_mscale(scaling_factor, mscale_all_dim) - self.scale = self.scale * mscale * mscale + mscale_all_dim = self.config.rope_scaling.get("mscale_all_dim", 0) + scaling_factor = self.config.rope_scaling["factor"] + if mscale_all_dim: + mscale = yarn_get_mscale(scaling_factor, mscale_all_dim) + self.scale = self.scale * mscale * mscale rope_kwargs = { key: self.config.rope_scaling[key] @@ -202,7 +201,7 @@ class DeepseekV2Attention(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[KVCache] = None, + cache: Optional[Any] = None, ) -> mx.array: B, L, D = x.shape @@ -347,7 +346,7 @@ class DeepseekV2DecoderLayer(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[KVCache] = None, + cache: Optional[Any] = None, ) -> mx.array: r = self.self_attn(self.input_layernorm(x), mask, cache) h = x + r @@ -370,7 +369,7 @@ class DeepseekV2Model(nn.Module): def __call__( self, x: mx.array, - cache: Optional[KVCache] = None, + cache: Optional[Any] = None, ) -> mx.array: h = self.embed_tokens(x) mask = create_attention_mask(h, cache) @@ -395,7 +394,7 @@ class Model(nn.Module): def __call__( self, inputs: mx.array, - cache: Optional[KVCache] = None, + cache: Optional[Any] = None, ): out = self.model(inputs, cache) return self.lm_head(out) @@ -416,14 +415,3 @@ class Model(nn.Module): @property def layers(self): return self.model.layers - - @property - def head_dim(self): - return ( - self.args.qk_nope_head_dim + self.args.qk_rope_head_dim, - self.args.v_head_dim, - ) - - @property - def n_kv_heads(self): - return self.args.num_key_value_heads diff --git a/llms/mlx_lm/models/gemma.py b/llms/mlx_lm/models/gemma.py index c6150284..61de781e 100644 --- a/llms/mlx_lm/models/gemma.py +++ b/llms/mlx_lm/models/gemma.py @@ -1,7 +1,7 @@ # Copyright © 2023-2024 Apple Inc. from dataclasses import dataclass -from typing import Optional, Tuple +from typing import Any, Optional, Tuple import mlx.core as mx import mlx.nn as nn @@ -60,7 +60,7 @@ class Attention(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[Tuple[mx.array, mx.array]] = None, + cache: Optional[Any] = None, ) -> mx.array: B, L, D = x.shape @@ -113,7 +113,7 @@ class TransformerBlock(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[Tuple[mx.array, mx.array]] = None, + cache: Optional[Any] = None, ) -> mx.array: r = self.self_attn(self.input_layernorm(x), mask, cache) h = x + r @@ -173,11 +173,3 @@ class Model(nn.Module): @property def layers(self): return self.model.layers - - @property - def head_dim(self): - return self.args.head_dim - - @property - def n_kv_heads(self): - return self.args.num_key_value_heads diff --git a/llms/mlx_lm/models/gemma2.py b/llms/mlx_lm/models/gemma2.py index 1d410a15..ccc327a8 100644 --- a/llms/mlx_lm/models/gemma2.py +++ b/llms/mlx_lm/models/gemma2.py @@ -1,7 +1,7 @@ # Copyright © 2023-2024 Apple Inc. from dataclasses import dataclass -from typing import Optional, Tuple +from typing import Any, Optional, Tuple import mlx.core as mx import mlx.nn as nn @@ -64,7 +64,7 @@ class Attention(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[Tuple[mx.array, mx.array]] = None, + cache: Optional[Any] = None, ) -> mx.array: B, L, D = x.shape queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x) @@ -135,13 +135,11 @@ class TransformerBlock(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[Tuple[mx.array, mx.array]] = None, + cache: Optional[Any] = None, ) -> mx.array: - r = self.self_attn(self.input_layernorm(x.astype(mx.float32)), mask, cache) + r = self.self_attn(self.input_layernorm(x), mask, cache) h = x + self.post_attention_layernorm(r) - r = self.mlp(self.pre_feedforward_layernorm(h).astype(mx.float16)).astype( - mx.float32 - ) + r = self.mlp(self.pre_feedforward_layernorm(h)) out = h + self.post_feedforward_layernorm(r) return out @@ -200,11 +198,3 @@ class Model(nn.Module): @property def layers(self): return self.model.layers - - @property - def head_dim(self): - return self.args.head_dim - - @property - def n_kv_heads(self): - return self.args.num_key_value_heads diff --git a/llms/mlx_lm/models/gpt2.py b/llms/mlx_lm/models/gpt2.py index 8a770936..97d9a8ff 100644 --- a/llms/mlx_lm/models/gpt2.py +++ b/llms/mlx_lm/models/gpt2.py @@ -1,7 +1,7 @@ # Copyright © 2023-2024 Apple Inc. from dataclasses import dataclass -from typing import Dict, Optional, Tuple, Union +from typing import Any, Dict, Optional, Tuple, Union import mlx.core as mx import mlx.nn as nn @@ -46,7 +46,7 @@ class Attention(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[Tuple[mx.array, mx.array]] = None, + cache: Optional[Any] = None, ) -> mx.array: B, L, D = x.shape @@ -100,7 +100,7 @@ class TransformerBlock(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[Tuple[mx.array, mx.array]] = None, + cache: Optional[Any] = None, ) -> mx.array: r = self.attn(self.ln_1(x), mask, cache) h = x + r @@ -196,11 +196,3 @@ class Model(nn.Module): @property def layers(self): return self.model.h - - @property - def head_dim(self): - return self.args.n_embd // self.args.n_head - - @property - def n_kv_heads(self): - return self.args.num_key_value_heads diff --git a/llms/mlx_lm/models/gpt_bigcode.py b/llms/mlx_lm/models/gpt_bigcode.py index 652eb9e4..068046ea 100644 --- a/llms/mlx_lm/models/gpt_bigcode.py +++ b/llms/mlx_lm/models/gpt_bigcode.py @@ -1,7 +1,7 @@ # Copyright © 2023-2024 Apple Inc. from dataclasses import dataclass -from typing import Dict, Optional, Tuple, Union +from typing import Any, Dict, Optional, Tuple, Union import mlx.core as mx import mlx.nn as nn @@ -57,7 +57,7 @@ class Attention(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[Tuple[mx.array, mx.array]] = None, + cache: Optional[Any] = None, ) -> mx.array: B, L, D = x.shape @@ -114,7 +114,7 @@ class TransformerBlock(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[Tuple[mx.array, mx.array]] = None, + cache: Optional[Any] = None, ) -> mx.array: r = self.attn(self.ln_1(x), mask, cache) h = x + r @@ -184,11 +184,3 @@ class Model(nn.Module): @property def layers(self): return self.transformer.h - - @property - def head_dim(self): - return self.args.n_embd // self.args.n_head - - @property - def n_kv_heads(self): - return self.args.num_key_value_heads diff --git a/llms/mlx_lm/models/gpt_neox.py b/llms/mlx_lm/models/gpt_neox.py index c2aaa9ea..9f662491 100644 --- a/llms/mlx_lm/models/gpt_neox.py +++ b/llms/mlx_lm/models/gpt_neox.py @@ -1,7 +1,7 @@ # Copyright © 2023-2024 Apple Inc. from dataclasses import dataclass -from typing import Dict, Optional, Tuple, Union +from typing import Any, Dict, Optional, Tuple, Union import mlx.core as mx import mlx.nn as nn @@ -60,7 +60,7 @@ class Attention(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[Tuple[mx.array, mx.array]] = None, + cache: Optional[Any] = None, ) -> mx.array: B, L, D = x.shape @@ -120,7 +120,7 @@ class TransformerBlock(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[Tuple[mx.array, mx.array]] = None, + cache: Optional[Any] = None, ) -> mx.array: residual = x # NeoX runs attention and feedforward network in parallel. @@ -214,11 +214,3 @@ class Model(nn.Module): @property def layers(self): return self.model.h - - @property - def head_dim(self): - return self.args.hidden_size // self.args.num_attention_heads - - @property - def n_kv_heads(self): - return self.args.num_key_value_heads diff --git a/llms/mlx_lm/models/internlm2.py b/llms/mlx_lm/models/internlm2.py index bcc0cf0c..5264cb57 100644 --- a/llms/mlx_lm/models/internlm2.py +++ b/llms/mlx_lm/models/internlm2.py @@ -1,7 +1,7 @@ # Copyright © 2023-2024 Apple Inc. from dataclasses import dataclass -from typing import Dict, Optional, Tuple, Union +from typing import Any, Dict, Optional, Tuple, Union import mlx.core as mx import mlx.nn as nn @@ -116,7 +116,7 @@ class Attention(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[Tuple[mx.array, mx.array]] = None, + cache: Optional[Any] = None, ) -> mx.array: B, L, D = x.shape @@ -171,7 +171,7 @@ class TransformerBlock(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[Tuple[mx.array, mx.array]] = None, + cache: Optional[Any] = None, ) -> mx.array: r = self.attention(self.attention_norm(x), mask, cache) h = x + r @@ -236,11 +236,3 @@ class Model(nn.Module): @property def layers(self): return self.model.layers - - @property - def head_dim(self): - return self.args.hidden_size // self.args.num_attention_heads - - @property - def n_kv_heads(self): - return self.args.num_key_value_heads diff --git a/llms/mlx_lm/models/llama.py b/llms/mlx_lm/models/llama.py index c4a947a5..7da6b333 100644 --- a/llms/mlx_lm/models/llama.py +++ b/llms/mlx_lm/models/llama.py @@ -1,12 +1,12 @@ # Copyright © 2023-2024 Apple Inc. from dataclasses import dataclass -from typing import Dict, Optional, Tuple, Union +from typing import Any, Dict, Optional, Tuple, Union import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, KVCache, create_attention_mask +from .base import BaseModelArgs, create_attention_mask @dataclass @@ -171,7 +171,7 @@ class Attention(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[KVCache] = None, + cache: Optional[Any] = None, ) -> mx.array: B, L, D = x.shape @@ -233,7 +233,7 @@ class TransformerBlock(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[KVCache] = None, + cache: Optional[Any] = None, ) -> mx.array: r = self.self_attn(self.input_layernorm(x), mask, cache) h = x + r @@ -303,13 +303,3 @@ class Model(nn.Module): @property def layers(self): return self.model.layers - - @property - def head_dim(self): - return ( - self.args.head_dim or self.args.hidden_size // self.args.num_attention_heads - ) - - @property - def n_kv_heads(self): - return self.args.num_key_value_heads diff --git a/llms/mlx_lm/models/mamba.py b/llms/mlx_lm/models/mamba.py index 26408426..d2740dc1 100644 --- a/llms/mlx_lm/models/mamba.py +++ b/llms/mlx_lm/models/mamba.py @@ -7,6 +7,7 @@ import mlx.core as mx import mlx.nn as nn from .base import BaseModelArgs +from .cache import MambaCache @dataclass @@ -45,21 +46,6 @@ class ModelArgs(BaseModelArgs): self.time_step_rank = math.ceil(self.hidden_size / 16) -class MambaCache: - def __init__(self): - self.cache = [None, None] - - def __setitem__(self, idx, value): - self.cache[idx] = value - - def __getitem__(self, idx): - return self.cache[idx] - - @property - def state(self): - return self.cache - - class DepthWiseConv1d(nn.Module): def __init__(self, channels, kernel_size, bias=True, padding=0): super().__init__() @@ -223,7 +209,7 @@ class Model(nn.Module): weights[k] = v.moveaxis(2, 1) return weights - def make_cache(self, batch_size: int = 1): + def make_cache(self): return [MambaCache() for _ in range(len(self.layers))] @property diff --git a/llms/mlx_lm/models/minicpm.py b/llms/mlx_lm/models/minicpm.py index df0670be..4ac3c3b4 100644 --- a/llms/mlx_lm/models/minicpm.py +++ b/llms/mlx_lm/models/minicpm.py @@ -1,7 +1,7 @@ # Copyright © 2023-2024 Apple Inc. from dataclasses import dataclass -from typing import Dict, Optional, Tuple, Union +from typing import Any, Dict, Optional, Tuple, Union import mlx.core as mx import mlx.nn as nn @@ -85,7 +85,7 @@ class Attention(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[Tuple[mx.array, mx.array]] = None, + cache: Optional[Any] = None, ): B, L, _ = x.shape @@ -135,7 +135,7 @@ class DecoderLayer(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[Tuple[mx.array, mx.array]] = None, + cache: Optional[Any] = None, ) -> mx.array: r = self.self_attn(self.input_layernorm(x), mask, cache) h = x + r * (self.scale_depth / np.sqrt(self.num_hidden_layers)) @@ -205,11 +205,3 @@ class Model(nn.Module): @property def layers(self): return self.model.layers - - @property - def head_dim(self): - return self.args.hidden_size // self.args.num_attention_heads - - @property - def n_kv_heads(self): - return self.args.num_key_value_heads diff --git a/llms/mlx_lm/models/mixtral.py b/llms/mlx_lm/models/mixtral.py index 2db57752..20944fe3 100644 --- a/llms/mlx_lm/models/mixtral.py +++ b/llms/mlx_lm/models/mixtral.py @@ -2,7 +2,7 @@ import math from dataclasses import dataclass -from typing import Dict, Optional, Tuple, Union +from typing import Any, Dict, Optional, Tuple, Union import mlx.core as mx import mlx.nn as nn @@ -66,7 +66,7 @@ class MixtralAttention(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[Tuple[mx.array, mx.array]] = None, + cache: Optional[Any] = None, ) -> mx.array: B, L, D = x.shape @@ -138,7 +138,7 @@ class MixtralDecoderLayer(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[Tuple[mx.array, mx.array]] = None, + cache: Optional[Any] = None, ) -> mx.array: r = self.self_attn(self.input_layernorm(x), mask, cache) h = x + r @@ -215,11 +215,3 @@ class Model(nn.Module): @property def layers(self): return self.model.layers - - @property - def head_dim(self): - return self.args.hidden_size // self.args.num_attention_heads - - @property - def n_kv_heads(self): - return self.args.num_key_value_heads diff --git a/llms/mlx_lm/models/nemotron.py b/llms/mlx_lm/models/nemotron.py index ef55d1d7..3ea06e27 100644 --- a/llms/mlx_lm/models/nemotron.py +++ b/llms/mlx_lm/models/nemotron.py @@ -2,12 +2,12 @@ from dataclasses import dataclass from functools import partial -from typing import Dict, Optional, Union +from typing import Any, Dict, Optional, Union import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, KVCache, create_attention_mask +from .base import BaseModelArgs, create_attention_mask @dataclass @@ -94,7 +94,7 @@ class Attention(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[KVCache] = None, + cache: Optional[Any] = None, ) -> mx.array: B, L, _ = x.shape @@ -151,7 +151,7 @@ class TransformerBlock(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[KVCache] = None, + cache: Optional[Any] = None, ) -> mx.array: r = self.self_attn(self.input_layernorm(x), mask, cache) h = x + r @@ -215,13 +215,3 @@ class Model(nn.Module): @property def layers(self): return self.model.layers - - @property - def head_dim(self): - return ( - self.args.head_dim or self.args.hidden_size // self.args.num_attention_heads - ) - - @property - def n_kv_heads(self): - return self.args.num_key_value_heads diff --git a/llms/mlx_lm/models/olmo.py b/llms/mlx_lm/models/olmo.py index 59849c96..3627df06 100644 --- a/llms/mlx_lm/models/olmo.py +++ b/llms/mlx_lm/models/olmo.py @@ -1,8 +1,8 @@ # Copyright © 2023-2024 Apple Inc. +import sys from dataclasses import dataclass -from sys import exit -from typing import Optional, Tuple +from typing import Any, Optional, Tuple import mlx.core as mx import mlx.nn as nn @@ -13,7 +13,7 @@ try: import hf_olmo except ImportError: print("To run olmo install ai2-olmo: pip install ai2-olmo") - exit(1) + sys.exit(1) @dataclass @@ -68,7 +68,7 @@ class TransformerBlock(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[Tuple[mx.array, mx.array]] = None, + cache: Optional[Any] = None, ) -> mx.array: B, L, D = x.shape @@ -98,7 +98,7 @@ class TransformerBlock(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[Tuple[mx.array, mx.array]] = None, + cache: Optional[Any] = None, ) -> mx.array: r = self.attend(self.att_norm(x), mask, cache) h = x + r @@ -174,11 +174,3 @@ class Model(nn.Module): @property def layers(self): return self.model.transformer.blocks - - @property - def head_dim(self): - return self.args.d_model // self.args.n_heads - - @property - def n_kv_heads(self): - return self.args.n_heads diff --git a/llms/mlx_lm/models/openelm.py b/llms/mlx_lm/models/openelm.py index 19d3c027..090e21c6 100644 --- a/llms/mlx_lm/models/openelm.py +++ b/llms/mlx_lm/models/openelm.py @@ -1,7 +1,7 @@ # Copyright © 2023-2024 Apple Inc. from dataclasses import dataclass -from typing import Dict, List, Optional, Tuple, Union +from typing import Any, Dict, List, Optional, Tuple, Union import mlx.core as mx import mlx.nn as nn @@ -80,7 +80,7 @@ class Attention(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[Tuple[mx.array, mx.array]] = None, + cache: Optional[Any] = None, ) -> mx.array: B, L, D = x.shape @@ -152,7 +152,7 @@ class TransformerBlock(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[Tuple[mx.array, mx.array]] = None, + cache: Optional[Any] = None, ) -> mx.array: r = self.attn(self.attn_norm(x), mask, cache) h = x + r @@ -218,11 +218,3 @@ class Model(nn.Module): @property def layers(self): return self.transformer.layers - - @property - def head_dim(self): - return self.args.head_dim - - @property - def n_kv_heads(self): - return self.args.num_kv_heads diff --git a/llms/mlx_lm/models/phi.py b/llms/mlx_lm/models/phi.py index fd3fd709..56b383b2 100644 --- a/llms/mlx_lm/models/phi.py +++ b/llms/mlx_lm/models/phi.py @@ -162,19 +162,11 @@ class Model(nn.Module): def __call__( self, x: mx.array, - cache: mx.array = None, - ) -> Tuple[mx.array, mx.array]: + cache=None, + ) -> mx.array: y = self.model(x, cache) return self.lm_head(y) @property def layers(self): return self.model.layers - - @property - def head_dim(self): - return self.args.hidden_size // self.args.num_attention_heads - - @property - def n_kv_heads(self): - return self.args.num_key_value_heads diff --git a/llms/mlx_lm/models/phi3.py b/llms/mlx_lm/models/phi3.py index 112ade7d..9ef76f04 100644 --- a/llms/mlx_lm/models/phi3.py +++ b/llms/mlx_lm/models/phi3.py @@ -1,12 +1,12 @@ # Copyright © 2023-2024 Apple Inc. from dataclasses import dataclass -from typing import Dict, List, Optional, Tuple, Union +from typing import Any, Dict, List, Optional, Tuple, Union import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, KVCache, create_attention_mask +from .base import BaseModelArgs, create_attention_mask from .su_rope import SuScaledRotaryEmbedding @@ -84,7 +84,7 @@ class Attention(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[KVCache] = None, + cache: Optional[Any] = None, ) -> mx.array: B, L, D = x.shape @@ -143,7 +143,7 @@ class TransformerBlock(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[KVCache] = None, + cache: Optional[Any] = None, ) -> mx.array: r = self.self_attn(self.input_layernorm(x), mask, cache) h = x + r @@ -202,11 +202,3 @@ class Model(nn.Module): @property def layers(self): return self.model.layers - - @property - def head_dim(self): - return self.args.hidden_size // self.args.num_attention_heads - - @property - def n_kv_heads(self): - return self.args.num_key_value_heads diff --git a/llms/mlx_lm/models/phi3small.py b/llms/mlx_lm/models/phi3small.py index 665dbc73..6b0759b4 100644 --- a/llms/mlx_lm/models/phi3small.py +++ b/llms/mlx_lm/models/phi3small.py @@ -3,12 +3,12 @@ import math from dataclasses import dataclass from functools import partial -from typing import Dict, Optional, Tuple, Union +from typing import Any, Optional import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, KVCache, create_attention_mask +from .base import BaseModelArgs, create_attention_mask @dataclass @@ -22,14 +22,14 @@ class ModelArgs(BaseModelArgs): num_attention_heads: int layer_norm_epsilon: float vocab_size: int - num_key_value_heads: Optional[int] = None + num_key_value_heads: int mup_attn_multiplier: float = 1.0 mup_use_scaling: bool = True mup_embedding_multiplier: float = 10.0 mup_width_multiplier: float = 8.0 rope_embedding_base: float = 1000000 rope_position_scale: float = 1.0 - blocksparse_block_size: Tuple[int] = (64,) + blocksparse_block_size: int = 64 blocksparse_num_local_blocks: int = 16 blocksparse_vert_stride: int = 8 @@ -61,7 +61,6 @@ class Attention(nn.Module): dim = args.hidden_size self.n_heads = n_heads = args.num_attention_heads - assert args.num_key_value_heads is not None self.n_kv_heads = n_kv_heads = args.num_key_value_heads self.n_q_per_kv = n_heads // n_kv_heads @@ -161,7 +160,7 @@ class Attention(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[KVCache] = None, + cache: Optional[Any] = None, ) -> mx.array: B, L, D = x.shape @@ -230,7 +229,7 @@ class TransformerBlock(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[KVCache] = None, + cache: Optional[Any] = None, ) -> mx.array: r = self.self_attn(self.input_layernorm(x), mask, cache) h = x + r @@ -304,16 +303,8 @@ class Model(nn.Module): def layers(self): return self.model.layers - @property - def head_dim(self): - return self.args.hidden_size // self.args.num_attention_heads - def sanitize(self, weights): # Remove unused precomputed rotary freqs return { k: v for k, v in weights.items() if "self_attn.rotary_emb.inv_freq" not in k } - - @property - def n_kv_heads(self): - return self.args.num_key_value_heads diff --git a/llms/mlx_lm/models/phimoe.py b/llms/mlx_lm/models/phimoe.py index db6bd4b5..ca20a388 100644 --- a/llms/mlx_lm/models/phimoe.py +++ b/llms/mlx_lm/models/phimoe.py @@ -173,6 +173,7 @@ class PhiMoEModel(nn.Module): class Model(nn.Module): def __init__(self, args: ModelArgs): super().__init__() + self.model_type = args.model_type self.args = args self.model = PhiMoEModel(args) self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=True) @@ -208,11 +209,3 @@ class Model(nn.Module): @property def layers(self): return self.model.layers - - @property - def head_dim(self): - return self.args.hidden_size // self.args.num_attention_heads - - @property - def n_kv_heads(self): - return self.args.num_key_value_heads diff --git a/llms/mlx_lm/models/phixtral.py b/llms/mlx_lm/models/phixtral.py index bb67615d..865d0d8e 100644 --- a/llms/mlx_lm/models/phixtral.py +++ b/llms/mlx_lm/models/phixtral.py @@ -168,8 +168,8 @@ class Model(nn.Module): self, x: mx.array, mask: mx.array = None, - cache: mx.array = None, - ) -> Tuple[mx.array, mx.array]: + cache=None, + ) -> mx.array: mask = create_attention_mask(x, cache) y = self.transformer(x, mask, cache) @@ -193,11 +193,3 @@ class Model(nn.Module): @property def layers(self): return self.transformer.h - - @property - def head_dim(self): - return self.args.model_dim // self.args.num_heads - - @property - def n_kv_heads(self): - return self.args.num_heads diff --git a/llms/mlx_lm/models/plamo.py b/llms/mlx_lm/models/plamo.py index 5d2b7586..090922ae 100644 --- a/llms/mlx_lm/models/plamo.py +++ b/llms/mlx_lm/models/plamo.py @@ -1,7 +1,7 @@ # Copyright © 2023-2024 Apple Inc. from dataclasses import dataclass -from typing import Any, List, Optional, Tuple, Union +from typing import Any, Optional import mlx.core as mx import mlx.nn as nn @@ -62,8 +62,8 @@ class Attention(nn.Module): self, hidden_states: mx.array, attention_mask: Optional[mx.array] = None, - cache: Optional[Tuple[mx.array, mx.array]] = None, - ) -> Tuple[mx.array, Tuple[mx.array, mx.array]]: + cache: Optional[Any] = None, + ) -> mx.array: bsz, q_len, _ = hidden_states.shape queries = self.q_proj(hidden_states) @@ -127,8 +127,8 @@ class PlamoDecoderLayer(nn.Module): self, hidden_states: mx.array, attention_mask: Optional[mx.array] = None, - cache: Optional[Tuple[mx.array, mx.array]] = None, - ) -> Tuple[Any, ...]: + cache: Optional[Any] = None, + ): # from LlamaDecoder residual = hidden_states @@ -169,8 +169,8 @@ class PlamoModel(nn.Module): def __call__( self, inputs: mx.array, - cache: Optional[List[Union[Tuple[mx.array, mx.array], None]]] = None, - ) -> Tuple[mx.array, Optional[List[Union[Tuple[mx.array, mx.array], None]]]]: + cache: Optional[Any] = None, + ) -> mx.array: h = self.embed_tokens(inputs) mask = create_attention_mask(h, cache) @@ -197,19 +197,11 @@ class Model(nn.Module): def __call__( self, inputs: mx.array, - cache: Optional[List[Tuple[mx.array, mx.array]]] = None, - ) -> Tuple[mx.array, mx.array]: + cache: Optional[Any] = None, + ) -> mx.array: out = self.model(inputs, cache) return self.lm_head(out) @property def layers(self): return self.model.layers.layers - - @property - def head_dim(self): - return self.args.hidden_size // self.args.num_attention_heads - - @property - def n_kv_heads(self): - return self.args.num_attention_heads // self.args.n_shared_head diff --git a/llms/mlx_lm/models/qwen.py b/llms/mlx_lm/models/qwen.py index 6d2c7bbf..2b69d5ec 100644 --- a/llms/mlx_lm/models/qwen.py +++ b/llms/mlx_lm/models/qwen.py @@ -1,7 +1,6 @@ # Copyright © 2023-2024 Apple Inc. from dataclasses import dataclass -from typing import Tuple import mlx.core as mx import mlx.nn as nn @@ -149,19 +148,11 @@ class Model(nn.Module): self, x: mx.array, mask: mx.array = None, - cache: mx.array = None, - ) -> Tuple[mx.array, mx.array]: + cache=None, + ) -> mx.array: y = self.transformer(x, mask, cache) return self.lm_head(y) @property def layers(self): return self.transformer.h - - @property - def head_dim(self): - return self.args.hidden_size // self.args.num_attention_heads - - @property - def n_kv_heads(self): - return self.args.num_attention_heads diff --git a/llms/mlx_lm/models/qwen2.py b/llms/mlx_lm/models/qwen2.py index b3ce02a3..4e7858de 100644 --- a/llms/mlx_lm/models/qwen2.py +++ b/llms/mlx_lm/models/qwen2.py @@ -1,12 +1,12 @@ # Copyright © 2023-2024 Apple Inc. from dataclasses import dataclass -from typing import Dict, Optional, Tuple, Union +from typing import Any, Dict, Optional, Union import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, KVCache, create_attention_mask +from .base import BaseModelArgs, create_attention_mask @dataclass @@ -70,7 +70,7 @@ class Attention(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[KVCache] = None, + cache: Optional[Any] = None, ) -> mx.array: B, L, D = x.shape @@ -124,7 +124,7 @@ class TransformerBlock(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[KVCache] = None, + cache: Optional[Any] = None, ) -> mx.array: r = self.self_attn(self.input_layernorm(x), mask, cache) h = x + r @@ -196,11 +196,3 @@ class Model(nn.Module): @property def layers(self): return self.model.layers - - @property - def head_dim(self): - return self.args.hidden_size // self.args.num_attention_heads - - @property - def n_kv_heads(self): - return self.args.num_key_value_heads diff --git a/llms/mlx_lm/models/qwen2_moe.py b/llms/mlx_lm/models/qwen2_moe.py index ff7831f3..d199116f 100644 --- a/llms/mlx_lm/models/qwen2_moe.py +++ b/llms/mlx_lm/models/qwen2_moe.py @@ -2,12 +2,12 @@ import math from dataclasses import dataclass -from typing import Dict, Optional, Tuple, Union +from typing import Any, Dict, Optional, Union import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, KVCache, create_attention_mask +from .base import BaseModelArgs, create_attention_mask from .switch_layers import SwitchGLU @@ -70,7 +70,7 @@ class Attention(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[KVCache] = None, + cache: Optional[Any] = None, ) -> mx.array: B, L, D = x.shape @@ -162,7 +162,7 @@ class Qwen2MoeDecoderLayer(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[KVCache] = None, + cache: Optional[Any] = None, ) -> mx.array: r = self.self_attn(self.input_layernorm(x), mask, cache) h = x + r @@ -236,11 +236,3 @@ class Model(nn.Module): @property def layers(self): return self.model.layers - - @property - def head_dim(self): - return self.args.hidden_size // self.args.num_attention_heads - - @property - def n_kv_heads(self): - return self.args.num_key_value_heads diff --git a/llms/mlx_lm/models/recurrent_gemma.py b/llms/mlx_lm/models/recurrent_gemma.py index 34750ace..06a307a6 100644 --- a/llms/mlx_lm/models/recurrent_gemma.py +++ b/llms/mlx_lm/models/recurrent_gemma.py @@ -7,13 +7,13 @@ from typing import List, Literal, Optional import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs +from .base import BaseModelArgs, create_attention_mask +from .cache import MambaCache, RotatingKVCache @dataclass class ModelArgs(BaseModelArgs): model_type: str - hidden_size: int attention_bias: bool conv1d_width: int hidden_size: int @@ -36,59 +36,6 @@ class ModelArgs(BaseModelArgs): self.block_types = self._block_types -def create_window_causal_mask(N: int, window_size: int): - inds = mx.arange(N) - linds = inds[:, None] - rinds = inds[None] - mask = (linds < rinds) | (linds > rinds + window_size) - return mask * -1e9 - - -class RecurrentCache: - - def __init__(self): - self._cache = (None, None) - - def __getitem__(self, idx): - return self._cache[idx] - - def update(self, conv_state, recurrent_state): - self._cache = (conv_state, recurrent_state) - - def state(self): - return self._cache - - -class WindowKVCache: - - def __init__(self, window_size): - self.keys = None - self.values = None - self.offset = 0 - self.window_size = window_size - - def update_and_fetch(self, keys, values): - # TODO consider using rotating buffer here - # especially for very long generations - def _update(x, v): - t = x.shape[2] - self.window_size - if t > 0: - x = x[..., t:, :] - return mx.concatenate([x, v], axis=2) - - self.offset += keys.shape[2] - if self.keys is None: - self.keys = keys - self.values = values - else: - self.keys = _update(self.keys, keys) - self.values = _update(self.values, values) - return self.keys, self.values - - def state(self): - return self.keys, self.values - - class RMSNorm(nn.Module): def __init__(self, dims: int, eps: float = 1e-5): super().__init__() @@ -136,31 +83,22 @@ class Conv1d(nn.Module): kernel_size: int, ): super().__init__() - self.weight = mx.zeros((kernel_size, channels)) + self.weight = mx.zeros((channels, kernel_size, 1)) self.bias = mx.zeros((channels,)) def __call__(self, x, cache=None): - w = self.weight.T[..., None] - kw, groups = self.weight.shape - if cache is not None: - l = [] - # Pad the cache if needed - if cache.shape[1] < kw - 1: - l.append( - mx.zeros( - (x.shape[0], kw - 1 - cache.shape[1], groups), dtype=x.dtype - ) - ) - l.extend([cache, x]) - x = mx.concatenate(l, axis=1) - y = (x * w.swapaxes(0, 2)).sum(axis=1, keepdims=True) - else: - y = mx.conv_general(x, w, padding=([kw - 1], [0]), groups=groups) + B, L, C = x.shape + groups, K, _ = self.weight.shape - # The cache is always kw - 1 - cache = x[:, max(x.shape[1] - kw + 1, 0) :, :] + if cache is not None: + x = mx.concatenate([cache, x], axis=1) + else: + x = mx.pad(x, [(0, 0), (K - 1, 0), (0, 0)]) + + y = mx.conv_general(x, self.weight, groups=groups) y = y + self.bias - return y, cache + + return y, x[:, -K + 1 :, :] class RGLRU(nn.Module): @@ -269,19 +207,9 @@ class RecurrentBlock(nn.Module): # x branch. x = self.linear_x(x) if cache is None: - conv_state, recurrent_state = (None, None) - else: - conv_state, recurrent_state = cache[0], cache[1] - x, conv_state = self.conv_1d( - x=x, - cache=conv_state, - ) - x, recurrent_state = self.rg_lru( - x=x, - cache=recurrent_state, - ) - if cache is not None: - cache.update(conv_state, recurrent_state) + cache = [None, None] + x, cache[0] = self.conv_1d(x=x, cache=cache[0]) + x, cache[1] = self.rg_lru(x=x, cache=cache[1]) x = x * y x = self.linear_out(x) @@ -467,12 +395,14 @@ class Griffin(nn.Module): if self.scale_by_sqrt_dim: x = x * math.sqrt(x.shape[-1]) - mask = None - if x.shape[1] > 1: - mask = create_window_causal_mask( - x.shape[1], self.config.attention_window_size - ) - mask = mask.astype(x.dtype) + if cache is None: + cache = [None] * len(self.layers) + + for i, block in enumerate(self.layers): + if block.temporal_block_type != "recurrent": + mask_cache = [cache[i]] + + mask = create_attention_mask(x, mask_cache) for i, block in enumerate(self.layers): x = block(x, mask=mask, cache=cache[i]) @@ -485,6 +415,7 @@ class Model(nn.Module): def __init__(self, config): self.args = config self.model = Griffin(config) + self.model_type = config.model_type self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False) def __call__(self, tokens: mx.array, cache=None) -> mx.array: @@ -508,10 +439,9 @@ class Model(nn.Module): return self.model.layers def sanitize(self, weights): - # Remove unused precomputed rotary freqs for k, v in weights.items(): if "conv_1d.weight" in k and v.ndim == 3: - weights[k] = v.squeeze(1).T + weights[k] = v.moveaxis(2, 1) if "lm_head.weight" not in weights: self.pop("lm_head") return weights @@ -520,7 +450,7 @@ class Model(nn.Module): cache = [] for layer in self.layers: if layer.temporal_block_type == "recurrent": - cache.append(RecurrentCache()) + cache.append(MambaCache()) else: - cache.append(WindowKVCache(self.args.attention_window_size)) + cache.append(RotatingKVCache(max_size=self.args.attention_window_size)) return cache diff --git a/llms/mlx_lm/models/stablelm.py b/llms/mlx_lm/models/stablelm.py index b340de28..11202b02 100644 --- a/llms/mlx_lm/models/stablelm.py +++ b/llms/mlx_lm/models/stablelm.py @@ -2,7 +2,6 @@ import math from dataclasses import dataclass -from typing import Tuple import mlx.core as mx import mlx.nn as nn @@ -198,8 +197,8 @@ class Model(nn.Module): self, x: mx.array, mask: mx.array = None, - cache: mx.array = None, - ) -> Tuple[mx.array, mx.array]: + cache=None, + ) -> mx.array: mask = create_attention_mask(x, cache) y = self.model(x, mask, cache) return self.lm_head(y) @@ -207,11 +206,3 @@ class Model(nn.Module): @property def layers(self): return self.model.layers - - @property - def head_dim(self): - return self.args.hidden_size // self.args.num_attention_heads - - @property - def n_kv_heads(self): - return self.args.num_key_value_heads diff --git a/llms/mlx_lm/models/starcoder2.py b/llms/mlx_lm/models/starcoder2.py index 9cec0e39..ce0a2ec5 100644 --- a/llms/mlx_lm/models/starcoder2.py +++ b/llms/mlx_lm/models/starcoder2.py @@ -1,12 +1,12 @@ # Copyright © 2023-2024 Apple Inc. from dataclasses import dataclass -from typing import Optional, Tuple +from typing import Any, Optional import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, KVCache, create_attention_mask +from .base import BaseModelArgs, create_attention_mask @dataclass @@ -45,7 +45,7 @@ class Attention(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[KVCache] = None, + cache: Optional[Any] = None, ) -> mx.array: B, L, D = x.shape @@ -100,7 +100,7 @@ class TransformerBlock(nn.Module): self, x: mx.array, mask: Optional[mx.array] = None, - cache: Optional[KVCache] = None, + cache: Optional[Any] = None, ) -> mx.array: r = self.self_attn(self.input_layernorm(x), mask, cache) h = x + r @@ -164,11 +164,3 @@ class Model(nn.Module): @property def layers(self): return self.model.layers - - @property - def head_dim(self): - return self.args.hidden_size // self.args.num_attention_heads - - @property - def n_kv_heads(self): - return self.args.num_key_value_heads diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index 54a96457..8649fbe3 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -18,7 +18,7 @@ from mlx.utils import tree_flatten from transformers import PreTrainedTokenizer # Local imports -from .models.base import KVCache, RotatingKVCache +from .models import base, cache from .sample_utils import categorical_sampling, min_p_sampling, top_p_sampling from .tokenizer_utils import TokenizerWrapper, load_tokenizer from .tuner.utils import dequantize as dequantize_model @@ -124,26 +124,6 @@ def apply_repetition_penalty(logits: mx.array, tokens: mx.array, penalty: float) return logits -def make_kv_caches( - model: nn.Module, max_kv_size: Optional[int] = None -) -> List[Union[KVCache, RotatingKVCache]]: - if hasattr(model, "make_cache"): - return model.make_cache() - - kv_heads = ( - [model.n_kv_heads] * len(model.layers) - if isinstance(model.n_kv_heads, int) - else model.n_kv_heads - ) - if max_kv_size is not None: - return [ - RotatingKVCache(model.head_dim, n, max_size=max_kv_size, keep=4) - for n in kv_heads - ] - else: - return [KVCache(model.head_dim, n) for n in kv_heads] - - def generate_step( prompt: mx.array, model: nn.Module, @@ -155,7 +135,7 @@ def generate_step( min_tokens_to_keep: int = 1, prefill_step_size: int = 512, max_kv_size: Optional[int] = None, - cache_history: Optional[List[Tuple[mx.array, mx.array]]] = None, + prompt_cache: Optional[Any] = None, logit_bias: Optional[Dict[int, float]] = None, logits_processor: Optional[List[Callable[[mx.array, mx.array], mx.array]]] = None, ) -> Generator[Tuple[mx.array, mx.array], None, None]: @@ -180,6 +160,8 @@ def generate_step( prefill_step_size (int): Step size for processing the prompt. max_kv_size (int, optional): Maximum size of the key-value cache. Old entries (except the first 4 tokens) will be overwritten. + prompt_cache (List[Any], optional): A pre-computed prompt cache. Note, if + provided, the cache will be updated in place. logit_bias (dictionary, optional): Additive logit bias. logits_processor (List[Callable[[mx.array, mx.array], mx.array]], optional): A list of functions that take tokens and logits and return the processed @@ -237,20 +219,13 @@ def generate_step( tokens = None # Create the KV cache for generation - cache = make_kv_caches(model, max_kv_size) - - if cache_history is not None: - if len(cache_history) != len(cache): - raise ValueError("Wrong number of layers in the cache history") - - # Set the history in the cache objects and evaluate them to prepare for - # generation. - for c, h in zip(cache, cache_history): - c.update_and_fetch(h[0], h[1]) - mx.eval([c.state for c in cache]) + if prompt_cache is None: + prompt_cache = cache.make_prompt_cache(model, max_kv_size) + elif len(prompt_cache) != len(model.layers): + raise ValueError("Wrong number of layers in the prompt cache.") def _step(y): - logits = model(y[None], cache=cache) + logits = model(y[None], cache=prompt_cache) logits = logits[:, -1, :] if logits_processor: @@ -305,9 +280,9 @@ def stream_generate( detokenizer = tokenizer.detokenizer detokenizer.reset() - for (token, _), n in zip( - generate_step(prompt_tokens, model, **kwargs), + for n, (token, _) in zip( range(max_tokens), + generate_step(prompt_tokens, model, **kwargs), ): if token == tokenizer.eos_token_id: break @@ -357,9 +332,9 @@ def generate( tic = time.perf_counter() detokenizer.reset() - for (token, logprobs), n in zip( - generate_step(prompt_tokens, model, **kwargs), + for n, (token, logprobs) in zip( range(max_tokens), + generate_step(prompt_tokens, model, **kwargs), ): if n == 0: prompt_time = time.perf_counter() - tic diff --git a/llms/setup.py b/llms/setup.py index e2cfe0cd..1c696dc0 100644 --- a/llms/setup.py +++ b/llms/setup.py @@ -32,6 +32,7 @@ setup( entry_points={ "console_scripts": [ "mlx_lm.cache_prompt = mlx_lm.cache_prompt:main", + "mlx_lm.chat = mlx_lm.chat:main", "mlx_lm.convert = mlx_lm.convert:main", "mlx_lm.fuse = mlx_lm.fuse:main", "mlx_lm.generate = mlx_lm.generate:main", diff --git a/llms/tests/test_models.py b/llms/tests/test_models.py index cd7e7fd0..1efde5ae 100644 --- a/llms/tests/test_models.py +++ b/llms/tests/test_models.py @@ -1,17 +1,15 @@ # Copyright © 2024 Apple Inc. - import unittest import mlx.core as mx from mlx.utils import tree_map -from mlx_lm.models.base import KVCache, RotatingKVCache -from mlx_lm.utils import make_kv_caches +from mlx_lm.models.cache import KVCache, RotatingKVCache, make_prompt_cache class TestModels(unittest.TestCase): def test_kv_cache(self): - cache = KVCache(32, 4) + cache = KVCache() k = mx.ones((1, 4, 1, 32), mx.float16) v = mx.ones((1, 4, 1, 32), mx.float16) @@ -32,7 +30,7 @@ class TestModels(unittest.TestCase): def test_rotating_kv_cache(self): b, h, d = 1, 2, 32 - cache = RotatingKVCache(d, h, max_size=8, step=4) + cache = RotatingKVCache(max_size=8, step=4) k = mx.random.uniform(shape=(b, h, 2, d)) v = mx.random.uniform(shape=(b, h, 2, d)) @@ -65,7 +63,7 @@ class TestModels(unittest.TestCase): idx %= 8 # Try with nonzero keep - cache = RotatingKVCache(d, h, max_size=8, step=4, keep=2) + cache = RotatingKVCache(max_size=8, step=4, keep=2) # Check a large update k = mx.random.uniform(shape=(b, h, 20, d)) @@ -88,6 +86,46 @@ class TestModels(unittest.TestCase): if idx >= 8: idx = 2 + def test_rotating_kv_cache_chat_mode(self): + # Test that the rotating kv cache can handle + # alternating prompt/prefill with generation + d = 4 + h = 2 + cache = RotatingKVCache(max_size=18, step=4) + + x = mx.random.uniform(shape=(1, h, 8, d)) + k, v = cache.update_and_fetch(x, x) + self.assertEqual(k.shape[2], 8) + self.assertEqual(cache.offset, 8) + + x = mx.random.uniform(shape=(1, h, 1, d)) + k, v = cache.update_and_fetch(x, x) + self.assertEqual(k.shape[2], 9) + self.assertEqual(cache.offset, 9) + self.assertTrue(mx.allclose(x, k[..., 8:9, :])) + + x = mx.random.uniform(shape=(1, h, 2, d)) + k, v = cache.update_and_fetch(x, x) + self.assertEqual(k.shape[2], 11) + self.assertEqual(cache.offset, 11) + self.assertTrue(mx.allclose(x, k[..., 9:11, :])) + + x = mx.random.uniform(shape=(1, h, 3, d)) + k, v = cache.update_and_fetch(x, x) + self.assertEqual(k.shape[2], 14) + self.assertEqual(cache.offset, 14) + self.assertTrue(mx.allclose(x, k[..., 11:14, :])) + + x = mx.random.uniform(shape=(1, h, 6, d)) + k, v = cache.update_and_fetch(x, x) + self.assertEqual(cache.offset, 20) + self.assertTrue(mx.allclose(x, k[..., -6:, :])) + + x = mx.random.uniform(shape=(1, h, 2, d)) + k, v = cache.update_and_fetch(x, x) + self.assertEqual(cache.offset, 22) + self.assertTrue(mx.allclose(x, k[..., -2:, :])) + def model_test_runner(self, model, model_type, vocab_size, num_layers): self.assertEqual(len(model.layers), num_layers) @@ -101,7 +139,7 @@ class TestModels(unittest.TestCase): self.assertEqual(outputs.shape, (1, 2, vocab_size)) self.assertEqual(outputs.dtype, t) - cache = make_kv_caches(model) + cache = make_prompt_cache(model) outputs = model(inputs, cache) self.assertEqual(outputs.shape, (1, 2, vocab_size)) self.assertEqual(outputs.dtype, t) @@ -549,6 +587,179 @@ class TestModels(unittest.TestCase): model, args.model_type, args.vocab_size, args.num_hidden_layers ) + def test_deepseek(self): + from mlx_lm.models import deepseek + + args = deepseek.ModelArgs( + model_type="deepseek", + vocab_size=1024, + hidden_size=128, + intermediate_size=256, + moe_intermediate_size=256, + num_hidden_layers=4, + num_attention_heads=8, + num_key_value_heads=4, + ) + model = deepseek.Model(args) + self.model_test_runner( + model, args.model_type, args.vocab_size, args.num_hidden_layers + ) + + def test_deepseek_v2(self): + from mlx_lm.models import deepseek_v2 + + args = deepseek_v2.ModelArgs( + model_type="deepseek_v2", + vocab_size=1024, + hidden_size=128, + intermediate_size=256, + moe_intermediate_size=256, + num_hidden_layers=4, + num_attention_heads=4, + num_key_value_heads=2, + kv_lora_rank=4, + q_lora_rank=4, + qk_rope_head_dim=32, + v_head_dim=16, + qk_nope_head_dim=32, + rope_scaling={ + "beta_fast": 32, + "beta_slow": 1, + "factor": 40, + "mscale": 1.0, + "mscale_all_dim": 1.0, + "original_max_position_embeddings": 4096, + "type": "yarn", + }, + ) + model = deepseek_v2.Model(args) + self.model_test_runner( + model, args.model_type, args.vocab_size, args.num_hidden_layers + ) + + def test_gemma2(self): + from mlx_lm.models import gemma2 + + args = gemma2.ModelArgs( + model_type="gemma2", + hidden_size=128, + num_hidden_layers=4, + intermediate_size=256, + num_attention_heads=2, + head_dim=32, + rms_norm_eps=1e-4, + vocab_size=1024, + num_key_value_heads=2, + ) + model = gemma2.Model(args) + self.model_test_runner( + model, args.model_type, args.vocab_size, args.num_hidden_layers + ) + + def test_gpt_bigcode(self): + from mlx_lm.models import gpt_bigcode + + args = gpt_bigcode.ModelArgs( + model_type="gpt_bigcode", + n_embd=128, + n_layer=128, + n_inner=256, + n_head=4, + n_positions=1000, + layer_norm_epsilon=1e-5, + vocab_size=1024, + ) + model = gpt_bigcode.Model(args) + self.model_test_runner(model, args.model_type, args.vocab_size, args.n_layer) + + def test_nemotron(self): + from mlx_lm.models import nemotron + + args = nemotron.ModelArgs( + model_type="nemotron", + hidden_size=128, + hidden_act="gelu", + num_hidden_layers=4, + intermediate_size=256, + num_attention_heads=4, + norm_eps=1e-5, + vocab_size=1024, + num_key_value_heads=2, + ) + model = nemotron.Model(args) + self.model_test_runner( + model, args.model_type, args.vocab_size, args.num_hidden_layers + ) + + def test_phi3small(self): + from mlx_lm.models import phi3small + + args = phi3small.ModelArgs( + model_type="phi3small", + hidden_size=128, + dense_attention_every_n_layers=2, + ff_intermediate_size=256, + gegelu_limit=1.0, + num_hidden_layers=4, + num_attention_heads=4, + num_key_value_heads=2, + layer_norm_epsilon=1e-4, + vocab_size=1000, + ) + model = phi3small.Model(args) + self.model_test_runner( + model, args.model_type, args.vocab_size, args.num_hidden_layers + ) + + def test_phimoe(self): + from mlx_lm.models import phimoe + + args = phimoe.ModelArgs( + model_type="phimoe", + vocab_size=320, + hidden_size=128, + intermediate_size=256, + num_hidden_layers=4, + num_attention_heads=4, + num_key_value_heads=4, + rope_scaling={ + "long_factor": [1.0] * 16, + "long_mscale": 1.243163121016122, + "original_max_position_embeddings": 4096, + "short_factor": [1.0] * 16, + "short_mscale": 1.243163121016122, + "type": "longrope", + }, + ) + model = phimoe.Model(args) + self.model_test_runner( + model, args.model_type, args.vocab_size, args.num_hidden_layers + ) + + def test_recurrent_gemma(self): + from mlx_lm.models import recurrent_gemma + + args = recurrent_gemma.ModelArgs( + model_type="recurrent_gemma", + hidden_size=128, + attention_bias=False, + conv1d_width=3, + intermediate_size=256, + logits_soft_cap=1.0, + num_attention_heads=4, + num_hidden_layers=4, + num_key_value_heads=2, + rms_norm_eps=1e-4, + rope_theta=1000, + attention_window_size=1024, + vocab_size=1000, + block_types=["recurrent", "recurrent", "attention"], + ) + model = recurrent_gemma.Model(args) + self.model_test_runner( + model, args.model_type, args.vocab_size, args.num_hidden_layers + ) + if __name__ == "__main__": unittest.main() diff --git a/llms/tests/test_prompt_cache.py b/llms/tests/test_prompt_cache.py new file mode 100644 index 00000000..3c1ef49b --- /dev/null +++ b/llms/tests/test_prompt_cache.py @@ -0,0 +1,220 @@ +# Copyright © 2024 Apple Inc. + +import os +import tempfile +import unittest + +import mlx.core as mx +from mlx_lm.models.cache import ( + KVCache, + MambaCache, + RotatingKVCache, + load_prompt_cache, + make_prompt_cache, + save_prompt_cache, + trim_prompt_cache, +) +from mlx_lm.utils import generate_step, load + +HF_MODEL_PATH = "mlx-community/Qwen1.5-0.5B-Chat-4bit" + + +class TestPromptCache(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.test_dir_fid = tempfile.TemporaryDirectory() + cls.test_dir = cls.test_dir_fid.name + + @classmethod + def tearDownClass(cls): + cls.test_dir_fid.cleanup() + + def test_save_load(self): + cache = [KVCache() for _ in range(4)] + for c in cache: + x = mx.random.uniform(shape=(1, 8, 10, 4)) + c.update_and_fetch(x, x) + cache_file = os.path.join(self.test_dir, "prompt_cache.safetensors") + save_prompt_cache(cache_file, cache) + loaded_cache = load_prompt_cache(cache_file) + self.assertTrue(len(cache), len(loaded_cache)) + for c, lc in zip(cache, loaded_cache): + self.assertEqual(c.offset, lc.offset) + self.assertTrue(mx.array_equal(c.state[0], lc.state[0])) + self.assertTrue(mx.array_equal(c.state[1], lc.state[1])) + + # Test with metadata + cache_file = os.path.join(self.test_dir, "prompt_cache.safetensors") + metadata = {"a": "b", "c": "d"} + save_prompt_cache(cache_file, cache, metadata) + _, loaded_metadata = load_prompt_cache(cache_file, return_metadata=True) + self.assertEqual(metadata, loaded_metadata) + + def test_save_load_rotating_cache(self): + cache_file = os.path.join(self.test_dir, "prompt_cache.safetensors") + + # Test with rotating cache + cache = [RotatingKVCache(max_size=8, keep=2) for _ in range(4)] + for c in cache: + x = mx.random.uniform(shape=(1, 8, 10, 4)) + c.update_and_fetch(x, x) + + save_prompt_cache(cache_file, cache) + loaded_cache = load_prompt_cache(cache_file) + self.assertTrue(len(cache), len(loaded_cache)) + for c, lc in zip(cache, loaded_cache): + self.assertEqual(c.offset, lc.offset) + self.assertEqual(c.keep, lc.keep) + self.assertEqual(c.max_size, lc.max_size) + self.assertEqual(c.step, lc.step) + self.assertTrue(mx.array_equal(c.state[0], lc.state[0])) + self.assertTrue(mx.array_equal(c.state[1], lc.state[1])) + + # Do a couple single token updates to get a rotation + for _ in range(2): + for c in cache: + x = mx.random.uniform(shape=(1, 8, 1, 4)) + c.update_and_fetch(x, x) + + save_prompt_cache(cache_file, cache) + loaded_cache = load_prompt_cache(cache_file) + + for c, lc in zip(cache, loaded_cache): + x = mx.random.uniform(shape=(1, 8, 1, 4)) + k, v = c.update_and_fetch(x, x) + lk, lv = lc.update_and_fetch(x, x) + self.assertEqual(c.offset, lc.offset) + self.assertTrue(mx.array_equal(k, lk)) + self.assertTrue(mx.array_equal(v, lv)) + + def test_save_load_mixed_cache(self): + cache_file = os.path.join(self.test_dir, "prompt_cache.safetensors") + + cache = [MambaCache(), KVCache(), RotatingKVCache(8), MambaCache()] + for c in cache: + if isinstance(c, MambaCache): + c[0] = mx.random.uniform(shape=(4, 4, 4)) + c[1] = mx.random.uniform(shape=(4, 4, 4)) + else: + x = mx.random.uniform(shape=(4, 4, 7, 4)) + y = mx.random.uniform(shape=(4, 4, 7, 4)) + c.update_and_fetch(x, y) + + save_prompt_cache(cache_file, cache) + loaded_cache = load_prompt_cache(cache_file) + for c, lc in zip(cache, loaded_cache): + if isinstance(c, MambaCache): + self.assertTrue(mx.array_equal(c[0], lc[0])) + self.assertTrue(mx.array_equal(c[1], lc[1])) + else: + x = mx.random.uniform(shape=(4, 4, 1, 4)) + y = mx.random.uniform(shape=(4, 4, 1, 4)) + k, v = c.update_and_fetch(x, y) + lk, lv = lc.update_and_fetch(x, y) + self.assertEqual(c.offset, lc.offset) + self.assertTrue(mx.array_equal(k, lk)) + self.assertTrue(mx.array_equal(v, lv)) + + def test_cache_with_generate(self): + model, tokenizer = load(HF_MODEL_PATH) + prompt = tokenizer.encode("this is a prompt", return_tensors="mlx")[0] + results = zip(range(4), generate_step(prompt, model)) + toks, all_logits = zip(*(r[1] for r in results)) + + prompt_cache = make_prompt_cache(model) + i = 0 + for _, (tok, logits) in zip( + range(2), generate_step(prompt, model, prompt_cache=prompt_cache) + ): + self.assertEqual(tok, toks[i]) + self.assertTrue(mx.allclose(logits, all_logits[i])) + i += 1 + + for _, (tok, logits) in zip( + range(1), + generate_step(mx.array([toks[i]]), model, prompt_cache=prompt_cache), + ): + i += 1 + self.assertEqual(tok, toks[i]) + self.assertTrue(mx.allclose(logits, all_logits[i])) + + def test_trim_cache(self): + cache = [KVCache() for _ in range(2)] + for c in cache: + x = mx.random.uniform(shape=(1, 8, 10, 4)) + c.update_and_fetch(x, x) + + # Trim + num_trimmed = trim_prompt_cache(cache, 7) + self.assertEqual(num_trimmed, 7) + + # Trim more tokens than remain + num_trimmed = trim_prompt_cache(cache, 4) + self.assertEqual(num_trimmed, 3) + + # Can't trim mamba cache + cache = [MambaCache() for _ in range(2)] + for c in cache: + c.state = mx.zeros((5, 5)) + num_trimmed = trim_prompt_cache(cache, 7) + self.assertEqual(num_trimmed, 0) + + # All cache's have to be trimmable + cache = [MambaCache(), KVCache()] + cache[0].state = mx.zeros((5, 5)) + x = mx.random.uniform(shape=(1, 8, 10, 4)) + cache[1].update_and_fetch(x, x) + num_trimmed = trim_prompt_cache(cache, 1) + self.assertEqual(num_trimmed, 0) + + cache = [RotatingKVCache(max_size=6) for _ in range(2)] + for c in cache: + x = mx.random.uniform(shape=(1, 8, 5, 4)) + c.update_and_fetch(x, x) + + num_trimmed = trim_prompt_cache(cache, 4) + self.assertEqual(num_trimmed, 4) + + # Can't trim fixed-size KV cache after processing + # more than max_kv_size tokens + for c in cache: + x = mx.random.uniform(shape=(1, 8, 10, 4)) + c.update_and_fetch(x, x) + + num_trimmed = trim_prompt_cache(cache, 4) + self.assertEqual(num_trimmed, 0) + + def test_trim_cache_with_generate(self): + model, tokenizer = load(HF_MODEL_PATH) + prompt = tokenizer.encode("this is a prompt", return_tensors="mlx")[0] + + prompt_cache = make_prompt_cache(model) + + # Generate one token so we process the full prompt + last_tok, _ = next(generate_step(prompt, model, prompt_cache=prompt_cache)) + last_tok = mx.array([last_tok]) + + # Generate two more tokens + results = zip( + range(2), generate_step(last_tok, model, prompt_cache=prompt_cache) + ) + toks, all_logits = zip(*(r[1] for r in results)) + + # To get back to the cache just after processing the prompt, + # trim by 3 tokens + trim_prompt_cache(prompt_cache, 3) + + # Generate the same thing again + results = zip( + range(2), generate_step(last_tok, model, prompt_cache=prompt_cache) + ) + second_toks, second_all_logits = zip(*(r[1] for r in results)) + self.assertEqual(toks, second_toks) + self.assertTrue( + all(mx.allclose(l, l2) for l, l2 in zip(all_logits, second_all_logits)) + ) + + +if __name__ == "__main__": + unittest.main() From b7373cb44f2728983ffb99a3d21d61f73230a41e Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Wed, 9 Oct 2024 11:09:36 -0700 Subject: [PATCH 044/188] fix long prompt generations (#1023) --- llms/mlx_lm/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index 8649fbe3..cfbcf29e 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -239,8 +239,8 @@ def generate_step( return y, logprobs.squeeze(0) while y.size > prefill_step_size: - model(y[:prefill_step_size][None], cache=cache) - mx.eval([c.state for c in cache]) + model(y[:prefill_step_size][None], cache=prompt_cache) + mx.eval([c.state for c in prompt_cache]) y = y[prefill_step_size:] y, logprobs = _step(y) From 4360e7ccec3d2dd1a2ac96509c74afcaa5e80a95 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Wed, 9 Oct 2024 16:48:32 -0700 Subject: [PATCH 045/188] clear cache during prompt processing (#1027) --- llms/mlx_lm/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index cfbcf29e..1e07546e 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -242,6 +242,7 @@ def generate_step( model(y[:prefill_step_size][None], cache=prompt_cache) mx.eval([c.state for c in prompt_cache]) y = y[prefill_step_size:] + mx.metal.clear_cache() y, logprobs = _step(y) From d72fdeb4eeb0cf65f146ad3c3fbc48a2cd23974b Mon Sep 17 00:00:00 2001 From: Alex Barron Date: Fri, 11 Oct 2024 10:16:20 -0700 Subject: [PATCH 046/188] MusicGen (#1020) * Add MusicGen model * add benchmarks * change to from_pretrained * symlinks * add readme and requirements * fix readme * readme --- encodec/README.md | 7 +- encodec/benchmarks/bench_mx.py | 5 +- encodec/convert.py | 1 - encodec/encodec.py | 72 ++++++- encodec/example.py | 6 +- encodec/test.py | 11 +- encodec/utils.py | 77 ------- musicgen/README.md | 31 +++ musicgen/benchmarks/bench_mx.py | 28 +++ musicgen/benchmarks/bench_pt.py | 31 +++ musicgen/encodec.py | 1 + musicgen/generate.py | 23 ++ musicgen/musicgen.py | 358 ++++++++++++++++++++++++++++++++ musicgen/requirements.txt | 6 + musicgen/t5.py | 1 + musicgen/utils.py | 15 ++ t5/README.md | 40 ++-- t5/convert.py | 75 ------- t5/t5.py | 179 +++++++++++----- 19 files changed, 722 insertions(+), 245 deletions(-) create mode 100644 musicgen/README.md create mode 100644 musicgen/benchmarks/bench_mx.py create mode 100644 musicgen/benchmarks/bench_pt.py create mode 120000 musicgen/encodec.py create mode 100644 musicgen/generate.py create mode 100644 musicgen/musicgen.py create mode 100644 musicgen/requirements.txt create mode 120000 musicgen/t5.py create mode 100644 musicgen/utils.py delete mode 100644 t5/convert.py diff --git a/encodec/README.md b/encodec/README.md index 3ab2793c..a3b948bf 100644 --- a/encodec/README.md +++ b/encodec/README.md @@ -33,13 +33,14 @@ An example using the model: ```python import mlx.core as mx -from utils import load, load_audio, save_audio +from encodec import EncodecModel +from utils import load_audio, save_audio # Load the 48 KHz model and preprocessor. -model, processor = load("mlx-community/encodec-48khz-float32") +model, processor = EncodecModel.from_pretrained("mlx-community/encodec-48khz-float32") # Load an audio file -audio = load_audio("path/to/aduio", model.sampling_rate, model.channels) +audio = load_audio("path/to/audio", model.sampling_rate, model.channels) # Preprocess the audio (this can also be a list of arrays for batched # processing). diff --git a/encodec/benchmarks/bench_mx.py b/encodec/benchmarks/bench_mx.py index 2acd4b75..61ddaae8 100644 --- a/encodec/benchmarks/bench_mx.py +++ b/encodec/benchmarks/bench_mx.py @@ -3,9 +3,10 @@ import time import mlx.core as mx -from utils import load -model, processor = load("mlx-community/encodec-48khz-float32") +from encodec import EncodecModel + +model, processor = EncodecModel.from_pretrained("mlx-community/encodec-48khz-float32") audio = mx.random.uniform(shape=(288000, 2)) feats, mask = processor(audio) diff --git a/encodec/convert.py b/encodec/convert.py index 13bd31a6..5c5f7e22 100644 --- a/encodec/convert.py +++ b/encodec/convert.py @@ -10,7 +10,6 @@ from typing import Any, Dict, Union import mlx.core as mx import mlx.nn as nn from huggingface_hub import snapshot_download -from mlx.utils import tree_flatten import encodec diff --git a/encodec/encodec.py b/encodec/encodec.py index 3ef47369..4b85dfdd 100644 --- a/encodec/encodec.py +++ b/encodec/encodec.py @@ -1,7 +1,10 @@ # Copyright © 2024 Apple Inc. +import functools +import json import math -from dataclasses import dataclass +from pathlib import Path +from types import SimpleNamespace from typing import List, Optional, Tuple, Union import mlx.core as mx @@ -669,3 +672,70 @@ class EncodecModel(nn.Module): if padding_mask is not None and padding_mask.shape[1] < audio_values.shape[1]: audio_values = audio_values[:, : padding_mask.shape[1]] return audio_values + + @classmethod + def from_pretrained(cls, path_or_repo: str): + from huggingface_hub import snapshot_download + + path = Path(path_or_repo) + if not path.exists(): + path = Path( + snapshot_download( + repo_id=path_or_repo, + allow_patterns=["*.json", "*.safetensors", "*.model"], + ) + ) + + with open(path / "config.json", "r") as f: + config = SimpleNamespace(**json.load(f)) + + model = EncodecModel(config) + model.load_weights(str(path / "model.safetensors")) + processor = functools.partial( + preprocess_audio, + sampling_rate=config.sampling_rate, + chunk_length=model.chunk_length, + chunk_stride=model.chunk_stride, + ) + mx.eval(model) + return model, processor + + +def preprocess_audio( + raw_audio: Union[mx.array, List[mx.array]], + sampling_rate: int = 24000, + chunk_length: Optional[int] = None, + chunk_stride: Optional[int] = None, +): + r""" + Prepare inputs for the EnCodec model. + + Args: + raw_audio (mx.array or List[mx.array]): The sequence or batch of + sequences to be processed. + sampling_rate (int): The sampling rate at which the audio waveform + should be digitalized. + chunk_length (int, optional): The model's chunk length. + chunk_stride (int, optional): The model's chunk stride. + """ + if not isinstance(raw_audio, list): + raw_audio = [raw_audio] + + raw_audio = [x[..., None] if x.ndim == 1 else x for x in raw_audio] + + max_length = max(array.shape[0] for array in raw_audio) + if chunk_length is not None: + max_length += chunk_length - (max_length % chunk_stride) + + inputs = [] + masks = [] + for x in raw_audio: + length = x.shape[0] + mask = mx.ones((length,), dtype=mx.bool_) + difference = max_length - length + if difference > 0: + mask = mx.pad(mask, (0, difference)) + x = mx.pad(x, ((0, difference), (0, 0))) + inputs.append(x) + masks.append(mask) + return mx.stack(inputs), mx.stack(masks) diff --git a/encodec/example.py b/encodec/example.py index 97b311a1..15ea476c 100644 --- a/encodec/example.py +++ b/encodec/example.py @@ -1,10 +1,12 @@ # Copyright © 2024 Apple Inc. import mlx.core as mx -from utils import load, load_audio, save_audio +from utils import load_audio, save_audio + +from encodec import EncodecModel # Load the 48 KHz model and preprocessor. -model, processor = load("mlx-community/encodec-48khz-float32") +model, processor = EncodecModel.from_pretrained("mlx-community/encodec-48khz-float32") # Load an audio file audio = load_audio("/path/to/audio", model.sampling_rate, model.channels) diff --git a/encodec/test.py b/encodec/test.py index ffc23505..ae565c29 100644 --- a/encodec/test.py +++ b/encodec/test.py @@ -3,9 +3,10 @@ import mlx.core as mx import numpy as np import torch -from datasets import Audio, load_dataset -from transformers import AutoProcessor, EncodecModel -from utils import load, load_audio, preprocess_audio +from transformers import AutoProcessor +from transformers import EncodecModel as PTEncodecModel + +from encodec import EncodecModel, preprocess_audio def compare_processors(): @@ -30,8 +31,8 @@ def compare_processors(): def compare_models(): - pt_model = EncodecModel.from_pretrained("facebook/encodec_48khz") - mx_model, _ = load("mlx-community/encodec-48khz-float32") + pt_model = PTEncodecModel.from_pretrained("facebook/encodec_48khz") + mx_model, _ = EncodecModel.from_pretrained("mlx-community/encodec-48khz-float32") np.random.seed(0) audio_length = 190560 diff --git a/encodec/utils.py b/encodec/utils.py index 18b3f063..b429ed83 100644 --- a/encodec/utils.py +++ b/encodec/utils.py @@ -1,16 +1,7 @@ # Copyright © 2024 Apple Inc. -import functools -import json -from pathlib import Path -from types import SimpleNamespace -from typing import List, Optional, Union - import mlx.core as mx import numpy as np -from huggingface_hub import snapshot_download - -import encodec def save_audio(file: str, audio: mx.array, sampling_rate: int): @@ -59,71 +50,3 @@ def load_audio(file: str, sampling_rate: int, channels: int): out = mx.array(np.frombuffer(out, np.int16)) return out.reshape(-1, channels).astype(mx.float32) / 32767.0 - - -def preprocess_audio( - raw_audio: Union[mx.array, List[mx.array]], - sampling_rate: int = 24000, - chunk_length: Optional[int] = None, - chunk_stride: Optional[int] = None, -): - r""" - Prepare inputs for the EnCodec model. - - Args: - raw_audio (mx.array or List[mx.array]): The sequence or batch of - sequences to be processed. - sampling_rate (int): The sampling rate at which the audio waveform - should be digitalized. - chunk_length (int, optional): The model's chunk length. - chunk_stride (int, optional): The model's chunk stride. - """ - if not isinstance(raw_audio, list): - raw_audio = [raw_audio] - - raw_audio = [x[..., None] if x.ndim == 1 else x for x in raw_audio] - - max_length = max(array.shape[0] for array in raw_audio) - if chunk_length is not None: - max_length += chunk_length - (max_length % chunk_stride) - - inputs = [] - masks = [] - for x in raw_audio: - length = x.shape[0] - mask = mx.ones((length,), dtype=mx.bool_) - difference = max_length - length - if difference > 0: - mask = mx.pad(mask, (0, difference)) - x = mx.pad(x, ((0, difference), (0, 0))) - inputs.append(x) - masks.append(mask) - return mx.stack(inputs), mx.stack(masks) - - -def load(path_or_repo): - """ - Load the model and audo preprocessor. - """ - path = Path(path_or_repo) - if not path.exists(): - path = Path( - snapshot_download( - repo_id=path_or_repo, - allow_patterns=["*.json", "*.safetensors", "*.model"], - ) - ) - - with open(path / "config.json", "r") as f: - config = SimpleNamespace(**json.load(f)) - - model = encodec.EncodecModel(config) - model.load_weights(str(path / "model.safetensors")) - processor = functools.partial( - preprocess_audio, - sampling_rate=config.sampling_rate, - chunk_length=model.chunk_length, - chunk_stride=model.chunk_stride, - ) - mx.eval(model) - return model, processor diff --git a/musicgen/README.md b/musicgen/README.md new file mode 100644 index 00000000..baef4fad --- /dev/null +++ b/musicgen/README.md @@ -0,0 +1,31 @@ +# MusicGen + +An example of Meta's MusicGen model in MLX.[^1] MusicGen is used to generate +music from text descriptions. + +### Setup + +Install the requirements: + +``` +pip install -r requirements.txt +``` + +### Example + +An example using the model: + +```python +import mlx.core as mx +from music_gen import MusicGen +from utils import save_audio + +model = MusicGen.from_pretrained("facebook/musicgen-medium") + +audio = model.generate("happy rock") + +save_audio("out.wav", audio, model.sampling_rate) +``` + +[^1]: Refer to the [arXiv paper](https://arxiv.org/abs/2306.05284) and + [code](https://github.com/facebookresearch/audiocraft/blob/main/docs/MUSICGEN.md) for more details. diff --git a/musicgen/benchmarks/bench_mx.py b/musicgen/benchmarks/bench_mx.py new file mode 100644 index 00000000..b669597b --- /dev/null +++ b/musicgen/benchmarks/bench_mx.py @@ -0,0 +1,28 @@ +# Copyright © 2024 Apple Inc. + +import sys +import time +from pathlib import Path + +import mlx.core as mx + +cur_path = Path(__file__).parents[1].resolve() +sys.path.append(str(cur_path)) + +from musicgen import MusicGen + +text = "folk ballad" +model = MusicGen.from_pretrained("facebook/musicgen-medium") + +max_steps = 100 + +audio = model.generate(text, max_steps=10) +mx.eval(audio) + +tic = time.time() +audio = model.generate(text, max_steps=max_steps) +mx.eval(audio) +toc = time.time() + +ms = 1000 * (toc - tic) / max_steps +print(f"Time (ms) per step: {ms:.3f}") diff --git a/musicgen/benchmarks/bench_pt.py b/musicgen/benchmarks/bench_pt.py new file mode 100644 index 00000000..de01aa66 --- /dev/null +++ b/musicgen/benchmarks/bench_pt.py @@ -0,0 +1,31 @@ +# Copyright © 2024 Apple Inc. + +import time + +import torch +from transformers import AutoProcessor, MusicgenForConditionalGeneration + +model_name = "facebook/musicgen-medium" +processor = AutoProcessor.from_pretrained(model_name) +model = MusicgenForConditionalGeneration.from_pretrained(model_name).to("mps") + +inputs = processor( + text=["folk ballad"], + padding=True, + return_tensors="pt", +) +inputs["input_ids"] = inputs["input_ids"].to("mps") +inputs["attention_mask"] = inputs["attention_mask"].to("mps") + +# warmup +audio_values = model.generate(**inputs, max_new_tokens=10) +torch.mps.synchronize() + +max_steps = 100 +tic = time.time() +audio_values = model.generate(**inputs, max_new_tokens=max_steps) +torch.mps.synchronize() +toc = time.time() + +ms = 1000 * (toc - tic) / max_steps +print(f"Time (ms) per step: {ms:.3f}") diff --git a/musicgen/encodec.py b/musicgen/encodec.py new file mode 120000 index 00000000..8eb278a7 --- /dev/null +++ b/musicgen/encodec.py @@ -0,0 +1 @@ +../encodec/encodec.py \ No newline at end of file diff --git a/musicgen/generate.py b/musicgen/generate.py new file mode 100644 index 00000000..5a6b7804 --- /dev/null +++ b/musicgen/generate.py @@ -0,0 +1,23 @@ +# Copyright © 2024 Apple Inc. + +import argparse + +from utils import save_audio + +from musicgen import MusicGen + + +def main(text: str, output_path: str, model_name: str, max_steps: int): + model = MusicGen.from_pretrained(model_name) + audio = model.generate(text, max_steps=max_steps) + save_audio(output_path, audio, model.sampling_rate) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--model", required=False, default="facebook/musicgen-medium") + parser.add_argument("--text", required=False, default="happy rock") + parser.add_argument("--output-path", required=False, default="0.wav") + parser.add_argument("--max-steps", required=False, default=500, type=int) + args = parser.parse_args() + main(args.text, args.output_path, args.model, args.max_steps) diff --git a/musicgen/musicgen.py b/musicgen/musicgen.py new file mode 100644 index 00000000..a2d021a5 --- /dev/null +++ b/musicgen/musicgen.py @@ -0,0 +1,358 @@ +# Copyright © 2024 Apple Inc. + +import json +from functools import partial +from pathlib import Path +from types import SimpleNamespace +from typing import Optional + +import mlx.core as mx +import mlx.nn as nn +from tqdm import tqdm + +from encodec import EncodecModel +from t5 import T5 + + +class TextConditioner(nn.Module): + def __init__(self, t5_name, input_dim, output_dim): + super().__init__() + self._t5, self.tokenizer = T5.from_pretrained(t5_name) + self.output_proj = nn.Linear(input_dim, output_dim) + + def __call__(self, text): + x = self.tokenizer.encode(text) + x = self._t5.encode(x) + return self.output_proj(x) + + +class KVCache: + def __init__(self, head_dim, n_kv_heads): + self.n_kv_heads = n_kv_heads + if isinstance(head_dim, int): + self.k_head_dim = self.v_head_dim = head_dim + elif isinstance(head_dim, tuple) and len(head_dim) == 2: + self.k_head_dim, self.v_head_dim = head_dim + else: + raise ValueError("head_dim must be an int or a tuple of two ints") + self.keys = None + self.values = None + self.offset = 0 + self.step = 256 + + def update_and_fetch(self, keys, values): + prev = self.offset + if self.keys is None or (prev + keys.shape[2]) > self.keys.shape[2]: + B = keys.shape[0] + n_steps = (self.step + keys.shape[2] - 1) // self.step + k_shape = (B, self.n_kv_heads, n_steps * self.step, self.k_head_dim) + v_shape = (B, self.n_kv_heads, n_steps * self.step, self.v_head_dim) + new_k = mx.zeros(k_shape, keys.dtype) + new_v = mx.zeros(v_shape, values.dtype) + if self.keys is not None: + if prev % self.step != 0: + self.keys = self.keys[..., :prev, :] + self.values = self.values[..., :prev, :] + self.keys = mx.concatenate([self.keys, new_k], axis=2) + self.values = mx.concatenate([self.values, new_v], axis=2) + else: + self.keys, self.values = new_k, new_v + + self.offset += keys.shape[2] + self.keys[..., prev : self.offset, :] = keys + self.values[..., prev : self.offset, :] = values + return self.keys[..., : self.offset, :], self.values[..., : self.offset, :] + + @property + def state(self): + return self.keys, self.values + + +class MultiHeadAttention(nn.Module): + def __init__(self, dim, n_heads): + super().__init__() + + self.n_heads = n_heads + + head_dim = dim // n_heads + + self.scale = head_dim**-0.5 + + self.q_proj = nn.Linear(dim, dim, bias=False) + self.k_proj = nn.Linear(dim, dim, bias=False) + self.v_proj = nn.Linear(dim, dim, bias=False) + self.out_proj = nn.Linear(dim, dim, bias=False) + + def __call__( + self, + queries: mx.array, + keys: mx.array, + values: mx.array, + mask: Optional[mx.array] = None, + cache: Optional[KVCache] = None, + ) -> mx.array: + B, L_q, D = queries.shape + L_k = keys.shape[1] + + queries, keys, values = ( + self.q_proj(queries), + self.k_proj(keys), + self.v_proj(values), + ) + + # Prepare the queries, keys and values for the attention computation + queries = queries.reshape(B, L_q, self.n_heads, -1).transpose(0, 2, 1, 3) + keys = keys.reshape(B, L_k, self.n_heads, -1).transpose(0, 2, 1, 3) + values = values.reshape(B, L_k, self.n_heads, -1).transpose(0, 2, 1, 3) + + if cache is not None: + keys, values = cache.update_and_fetch(keys, values) + + output = mx.fast.scaled_dot_product_attention( + queries, keys, values, scale=self.scale, mask=mask + ) + output = output.transpose(0, 2, 1, 3).reshape(B, L_q, -1) + return self.out_proj(output) + + +class TransformerBlock(nn.Module): + def __init__(self, config): + super().__init__() + self.num_attention_heads = config.decoder.num_attention_heads + self.hidden_size = config.decoder.hidden_size + self.self_attn = MultiHeadAttention(self.hidden_size, self.num_attention_heads) + self.cross_attn = MultiHeadAttention(self.hidden_size, self.num_attention_heads) + self.linear1 = nn.Linear(self.hidden_size, config.decoder.ffn_dim, bias=False) + self.linear2 = nn.Linear(config.decoder.ffn_dim, self.hidden_size, bias=False) + + self.norm1 = nn.LayerNorm(self.hidden_size, eps=1e-5) + self.norm_cross = nn.LayerNorm(self.hidden_size, eps=1e-5) + self.norm2 = nn.LayerNorm(self.hidden_size, eps=1e-5) + + def __call__( + self, + x: mx.array, + conditioning: mx.array, + mask: Optional[mx.array] = None, + cache: Optional[KVCache] = None, + ) -> mx.array: + xn = self.norm1(x) + x += self.self_attn(xn, xn, xn, mask, cache) + xn = self.norm_cross(x) + x += self.cross_attn(xn, conditioning, conditioning, mask) + xn = self.norm2(x) + x += self.linear2(nn.gelu(self.linear1(xn))) + return x + + +@partial(mx.compile, inputs=mx.random.state, outputs=mx.random.state) +def top_k_sampling( + logits: mx.array, top_k: float, temperature: float, axis: int = -1 +) -> mx.array: + """ + Apply top-k sampling to logits. + + Args: + logits: The logits from the model's output. + top_k: Sample from the top k logits. + temperature: Temperature parameter for softmax distribution reshaping. + axis: Axis along which to sample. + Returns: + token selected based on the top-k criterion. + """ + # referenced implementation from https://github.com/huggingface/transformers/blob/main/src/transformers/generation/logits_process.py#L449-L460 + probs = mx.softmax(logits * (1 / temperature), axis=axis) + + # sort probs in ascending order + sorted_indices = mx.argsort(probs, axis=axis) + sorted_probs = mx.take_along_axis(probs, sorted_indices, axis=axis) + prob_threshold = mx.take(sorted_probs, mx.array(-top_k), axis=axis) + + # select the top K tokens in probability + top_probs = mx.where( + sorted_probs > prob_threshold, + sorted_probs, + 0, + ) + + sorted_token = mx.random.categorical(mx.log(top_probs), axis=axis) + token = mx.take_along_axis( + sorted_indices, mx.expand_dims(sorted_token, axis), axis=axis + ) + + return token + + +def create_sin_embedding(positions: mx.array, dim: int, max_period: float = 10000): + assert dim % 2 == 0 + half_dim = dim // 2 + adim = mx.arange(half_dim).reshape(1, 1, -1) + phase = positions / (max_period ** (adim / (half_dim - 1))) + return mx.concatenate([mx.cos(phase), mx.sin(phase)], axis=-1) + + +class MusicGen(nn.Module): + def __init__(self, config): + self.num_codebooks = config.decoder.num_codebooks + self.codebook_size = config.audio_encoder.codebook_size + self.bos_token_id = config.decoder.bos_token_id + self.hidden_size = config.decoder.hidden_size + self.num_attention_heads = config.decoder.num_attention_heads + self.sampling_rate = config.audio_encoder.sampling_rate + + self.text_conditioner = TextConditioner( + config.text_encoder._name_or_path, + config.text_encoder.d_model, + self.hidden_size, + ) + self.emb = [ + nn.Embedding(self.codebook_size + 1, self.hidden_size) + for _ in range(self.num_codebooks) + ] + self.layers = [ + TransformerBlock(config) for _ in range(config.decoder.num_hidden_layers) + ] + self.out_norm = nn.LayerNorm(self.hidden_size, eps=1e-5) + self.linears = [ + nn.Linear(self.hidden_size, self.codebook_size, bias=False) + for _ in range(self.num_codebooks) + ] + encodec_name = config.audio_encoder._name_or_path.split("/")[-1] + encodec_name = encodec_name.replace("_", "-") + self._audio_decoder, _ = EncodecModel.from_pretrained( + f"mlx-community/{encodec_name}-float32" + ) + + def __call__( + self, + audio_tokens: mx.array, + conditioning: mx.array, + cache: list[KVCache] = None, + ): + + if cache is None: + cache = [None] * len(self.layers) + + x = sum([self.emb[k](audio_tokens[..., k]) for k in range(self.num_codebooks)]) + + offset = cache[0].offset if cache[0] is not None else 0 + pos_emb = create_sin_embedding(offset, self.hidden_size) + x += pos_emb.astype(x.dtype) + + for layer, c in zip(self.layers, cache): + x = layer(x, conditioning, cache=c) + + x = self.out_norm(x) + x = mx.stack([self.linears[k](x) for k in range(self.num_codebooks)], axis=-1) + return x + + def generate( + self, + text: str, + max_steps: int = 200, + top_k: int = 250, + temp: float = 1.0, + guidance_coef: float = 3.0, + ) -> mx.array: + """ + Generates a waveform conditioned on `text`. + + Args: + text (str): The text to condition generation on. + max_steps (int): Max steps to generate. + top_k (int): Top k used in sampling. + temp (float): Sampling softmax temperature. + guidance_coef (float): Classifier free guidance coefficent. + Used to combine conditional and unconditional logits. + + Returns: + An mx.array of audio samples of shape ``(num_samples,)``. + """ + # Assuming no audio prompt we start with all bos token for the codebooks + audio_shape = (1, max_steps + 1, self.num_codebooks) + audio_seq = mx.full(audio_shape, self.bos_token_id) + + text_tokens = self.text_conditioner(text) + # Compute conditional and unconditional logits in one batch + text_tokens = mx.concatenate([text_tokens, mx.zeros_like(text_tokens)], axis=0) + + head_dim = self.hidden_size // self.num_attention_heads + cache = [ + KVCache(head_dim, self.num_attention_heads) for _ in range(len(self.layers)) + ] + for offset in tqdm(range(max_steps)): + audio_input = mx.tile(audio_seq[:, offset : offset + 1], [2, 1, 1]) + audio_logits = self(audio_input, text_tokens, cache) + cond_logits, uncond_logits = audio_logits[:1], audio_logits[1:2] + audio_logits = uncond_logits + (cond_logits - uncond_logits) * guidance_coef + audio_tokens = top_k_sampling(audio_logits, top_k, temp, axis=-2) + # "delay" pattern + audio_tokens[..., offset + 1 :] = self.bos_token_id + audio_tokens[..., : -max_steps + offset] = self.bos_token_id + audio_seq[:, offset + 1 : offset + 2] = audio_tokens + mx.eval(audio_seq) + + # Undo delay + for i in range(self.num_codebooks): + audio_seq[:, : -self.num_codebooks, i] = audio_seq[ + :, i : -self.num_codebooks + i, i + ] + audio_seq = audio_seq[:, 1 : -self.num_codebooks + 1] + + audio_seq = mx.swapaxes(audio_seq, -1, -2)[:, mx.newaxis] + audio = self._audio_decoder.decode(audio_seq, audio_scales=[None]) + return audio[0] + + @classmethod + def sanitize(cls, weights): + out_weights = {} + for k, arr in weights.items(): + if k.startswith("transformer."): + k = k[len("transformer.") :] + + if "cross_attention" in k: + k = k.replace("cross_attention", "cross_attn") + + if "condition_provider" in k: + k = k.replace( + "condition_provider.conditioners.description", "text_conditioner" + ) + + if "in_proj_weight" in k: + dim = arr.shape[0] // 3 + name = "in_proj_weight" + out_weights[k.replace(name, "q_proj.weight")] = arr[:dim] + out_weights[k.replace(name, "k_proj.weight")] = arr[dim : dim * 2] + out_weights[k.replace(name, "v_proj.weight")] = arr[dim * 2 :] + continue + + out_weights[k] = arr + return out_weights + + @classmethod + def from_pretrained(cls, path_or_repo: str): + import torch + from huggingface_hub import snapshot_download + + path = Path(path_or_repo) + if not path.exists(): + path = Path( + snapshot_download( + repo_id=path_or_repo, + allow_patterns=["*.json", "state_dict.bin"], + ) + ) + + with open(path / "config.json", "r") as f: + config = SimpleNamespace(**json.load(f)) + config.text_encoder = SimpleNamespace(**config.text_encoder) + config.audio_encoder = SimpleNamespace(**config.audio_encoder) + config.decoder = SimpleNamespace(**config.decoder) + + weights = torch.load(path / "state_dict.bin", weights_only=True)["best_state"] + weights = {k: mx.array(v) for k, v in weights.items()} + weights = cls.sanitize(weights) + + model = MusicGen(config) + model.load_weights(list(weights.items())) + return model diff --git a/musicgen/requirements.txt b/musicgen/requirements.txt new file mode 100644 index 00000000..5c716fe3 --- /dev/null +++ b/musicgen/requirements.txt @@ -0,0 +1,6 @@ +mlx>=0.18 +numpy +huggingface_hub +torch +transformers +scipy diff --git a/musicgen/t5.py b/musicgen/t5.py new file mode 120000 index 00000000..f31e26f9 --- /dev/null +++ b/musicgen/t5.py @@ -0,0 +1 @@ +../t5/t5.py \ No newline at end of file diff --git a/musicgen/utils.py b/musicgen/utils.py new file mode 100644 index 00000000..78e92571 --- /dev/null +++ b/musicgen/utils.py @@ -0,0 +1,15 @@ +# Copyright © 2024 Apple Inc. + +import mlx.core as mx +import numpy as np + + +def save_audio(file: str, audio: mx.array, sampling_rate: int): + """ + Save audio to a wave (.wav) file. + """ + from scipy.io.wavfile import write + + audio = mx.clip(audio, -1, 1) + audio = (audio * 32767).astype(mx.int16) + write(file, sampling_rate, np.array(audio)) diff --git a/t5/README.md b/t5/README.md index a0cc861b..e5165f8f 100644 --- a/t5/README.md +++ b/t5/README.md @@ -7,31 +7,6 @@ tasks by prepending task-specific prefixes to the input, e.g.: This example also supports the FLAN-T5 models variants.[^2] -## Setup - -Download and convert the model: - -```sh -python convert.py --model -``` - -This will make the `.npz` file which MLX can read. - -The `` can be any of the following: - -| Model Name | Model Size | -| ---------- | ---------- -| t5-small | 60 million | -| t5-base | 220 million | -| t5-large | 770 million | -| t5-3b | 3 billion | -| t5-11b | 11 billion | - -The FLAN variants can be specified with `google/flan-t5-small`, -`google/flan-t5-base`, etc. See the [Hugging Face -page](https://huggingface.co/docs/transformers/model_doc/flan-t5) for a -complete list of models. - ## Generate Generate text with: @@ -48,6 +23,21 @@ To see a list of options run: python t5.py --help ``` +The `` can be any of the following: + +| Model Name | Model Size | +| ---------- | ---------- +| t5-small | 60 million | +| t5-base | 220 million | +| t5-large | 770 million | +| t5-3b | 3 billion | +| t5-11b | 11 billion | + +The FLAN variants can be specified with `google/flan-t5-small`, +`google/flan-t5-base`, etc. See the [Hugging Face +page](https://huggingface.co/docs/transformers/model_doc/flan-t5) for a +complete list of models. + [^1]: For more information on T5 see the [original paper](https://arxiv.org/abs/1910.10683) or the [Hugging Face page](https://huggingface.co/docs/transformers/model_doc/t5). [^2]: For more information on FLAN-T5 see the [original paper](https://arxiv.org/abs/2210.11416). diff --git a/t5/convert.py b/t5/convert.py deleted file mode 100644 index e2108a0c..00000000 --- a/t5/convert.py +++ /dev/null @@ -1,75 +0,0 @@ -import numpy as np -from transformers import T5ForConditionalGeneration - -SHARED_REPLACEMENT_PATTERNS = [ - (".block.", ".layers."), - (".k.", ".key_proj."), - (".o.", ".out_proj."), - (".q.", ".query_proj."), - (".v.", ".value_proj."), - ("shared.", "wte."), - ("lm_head.", "lm_head.linear."), - (".layer.0.layer_norm.", ".ln1."), - (".layer.1.layer_norm.", ".ln2."), - (".layer.2.layer_norm.", ".ln3."), - (".final_layer_norm.", ".ln."), - ( - "layers.0.layer.0.SelfAttention.relative_attention_bias.", - "relative_attention_bias.embeddings.", - ), -] - -ENCODER_REPLACEMENT_PATTERNS = [ - (".layer.0.SelfAttention.", ".attention."), - (".layer.1.DenseReluDense.", ".dense."), -] - -DECODER_REPLACEMENT_PATTERNS = [ - (".layer.0.SelfAttention.", ".self_attention."), - (".layer.1.EncDecAttention.", ".cross_attention."), - (".layer.2.DenseReluDense.", ".dense."), -] - - -def replace_key(key: str) -> str: - for old, new in SHARED_REPLACEMENT_PATTERNS: - key = key.replace(old, new) - if key.startswith("encoder."): - for old, new in ENCODER_REPLACEMENT_PATTERNS: - key = key.replace(old, new) - elif key.startswith("decoder."): - for old, new in DECODER_REPLACEMENT_PATTERNS: - key = key.replace(old, new) - return key - - -def convert(model_name, dtype): - dtype = getattr(np, dtype) - model = T5ForConditionalGeneration.from_pretrained(model_name, torch_dtype="auto") - weights = { - replace_key(k): v.numpy().astype(dtype) for k, v in model.state_dict().items() - } - file_name = model_name.replace("/", "-") - print(f"Saving weights to {file_name}.npz") - np.savez(f"{file_name}.npz", **weights) - - -if __name__ == "__main__": - import argparse - - parser = argparse.ArgumentParser(description="Convert T5 weights to MLX") - parser.add_argument( - "--model", - type=str, - help="Name of the T5 model.", - default="t5-small", - ) - parser.add_argument( - "--dtype", - help="The model data type.", - type=str, - choices=["float16", "float32"], - default="float32", - ) - args = parser.parse_args() - convert(args.model, args.dtype) diff --git a/t5/t5.py b/t5/t5.py index 89f2e486..04a0da8c 100644 --- a/t5/t5.py +++ b/t5/t5.py @@ -1,12 +1,45 @@ import argparse +import json +from pathlib import Path from time import perf_counter_ns +from types import SimpleNamespace from typing import List, Optional, Tuple import mlx.core as mx import mlx.nn as nn import numpy as np -from mlx.utils import tree_map, tree_unflatten -from transformers import AutoTokenizer, T5Config +from transformers import AutoTokenizer + + +class Tokenizer: + def __init__(self, config, model_name): + self._decoder_start_id = config.decoder_start_token_id + self._tokenizer = AutoTokenizer.from_pretrained( + model_name, + legacy=False, + model_max_length=getattr(config, "n_positions", 512), + ) + + @property + def eos_id(self) -> int: + return self._tokenizer.eos_token_id + + @property + def decoder_start_id(self) -> int: + return self._decoder_start_id + + def encode(self, s: str) -> mx.array: + return mx.array( + self._tokenizer( + s, + return_tensors="np", + return_attention_mask=False, + )["input_ids"] + ) + + def decode(self, t: List[int], with_sep: bool = True) -> str: + tokens = self._tokenizer.convert_ids_to_tokens(t) + return "".join(t.replace("▁", " " if with_sep else "") for t in tokens) def _relative_position_bucket( @@ -60,10 +93,10 @@ def _relative_position_bucket( class RelativePositionBias(nn.Module): - def __init__(self, config: T5Config, bidirectional: bool): + def __init__(self, config, bidirectional: bool): self.bidirectional = bidirectional self.num_buckets = config.relative_attention_num_buckets - self.max_distance = config.relative_attention_max_distance + self.max_distance = getattr(config, "relative_attention_max_distance", 128) self.n_heads = config.num_heads self.embeddings = nn.Embedding( config.relative_attention_num_buckets, config.num_heads @@ -91,7 +124,7 @@ class RelativePositionBias(nn.Module): class MultiHeadAttention(nn.Module): - def __init__(self, config: T5Config): + def __init__(self, config): super().__init__() inner_dim = config.d_kv * config.num_heads self.num_heads = config.num_heads @@ -135,17 +168,21 @@ class MultiHeadAttention(nn.Module): class DenseActivation(nn.Module): - def __init__(self, config: T5Config): + def __init__(self, config): super().__init__() mlp_dims = config.d_ff or config.d_model * 4 - self.gated = config.feed_forward_proj.startswith("gated") + self.gated = hasattr(config, "feed_forward_proj") + activation = ( + "relu" + if not self.gated + else config.feed_forward_proj.removeprefix("gated-") + ) if self.gated: self.wi_0 = nn.Linear(config.d_model, mlp_dims, bias=False) self.wi_1 = nn.Linear(config.d_model, mlp_dims, bias=False) else: self.wi = nn.Linear(config.d_model, mlp_dims, bias=False) self.wo = nn.Linear(mlp_dims, config.d_model, bias=False) - activation = config.feed_forward_proj.removeprefix("gated-") if activation == "relu": self.act = nn.relu elif activation == "gelu": @@ -166,7 +203,7 @@ class DenseActivation(nn.Module): class TransformerEncoderLayer(nn.Module): - def __init__(self, config: T5Config): + def __init__(self, config): super().__init__() self.attention = MultiHeadAttention(config) self.ln1 = nn.RMSNorm(config.d_model, eps=config.layer_norm_epsilon) @@ -184,7 +221,7 @@ class TransformerEncoderLayer(nn.Module): class TransformerEncoder(nn.Module): - def __init__(self, config: T5Config): + def __init__(self, config): super().__init__() self.layers = [ TransformerEncoderLayer(config) for i in range(config.num_layers) @@ -200,7 +237,7 @@ class TransformerEncoder(nn.Module): class TransformerDecoderLayer(nn.Module): - def __init__(self, config: T5Config): + def __init__(self, config): super().__init__() self.self_attention = MultiHeadAttention(config) self.cross_attention = MultiHeadAttention(config) @@ -233,7 +270,7 @@ class TransformerDecoderLayer(nn.Module): class TransformerDecoder(nn.Module): - def __init__(self, config: T5Config): + def __init__(self, config): super().__init__() n_layers = getattr(config, "num_decoder_layers", config.num_layers) self.layers = [TransformerDecoderLayer(config) for i in range(n_layers)] @@ -262,7 +299,7 @@ class TransformerDecoder(nn.Module): class OutputHead(nn.Module): - def __init__(self, config: T5Config): + def __init__(self, config): self.linear = nn.Linear(config.d_model, config.vocab_size, bias=False) def __call__(self, inputs): @@ -270,11 +307,11 @@ class OutputHead(nn.Module): class T5(nn.Module): - def __init__(self, config: T5Config): + def __init__(self, config): self.wte = nn.Embedding(config.vocab_size, config.d_model) self.encoder = TransformerEncoder(config) self.decoder = TransformerDecoder(config) - self.tie_word_embeddings = config.tie_word_embeddings + self.tie_word_embeddings = getattr(config, "tie_word_embeddings", True) if not self.tie_word_embeddings: self.lm_head = OutputHead(config) self.model_dim = config.d_model @@ -313,36 +350,82 @@ class T5(nn.Module): ): return self.decode(decoder_inputs, self.encode(inputs))[0] + @classmethod + def sanitize(cls, weights): + shared_replacement_patterns = [ + (".block.", ".layers."), + (".k.", ".key_proj."), + (".o.", ".out_proj."), + (".q.", ".query_proj."), + (".v.", ".value_proj."), + ("shared.", "wte."), + ("lm_head.", "lm_head.linear."), + (".layer.0.layer_norm.", ".ln1."), + (".layer.1.layer_norm.", ".ln2."), + (".layer.2.layer_norm.", ".ln3."), + (".final_layer_norm.", ".ln."), + ( + "layers.0.layer.0.SelfAttention.relative_attention_bias.", + "relative_attention_bias.embeddings.", + ), + ] -class Tokenizer: - def __init__(self, config: T5Config): - self._decoder_start_id = config.decoder_start_token_id - self._tokenizer = AutoTokenizer.from_pretrained( - args.model, - legacy=False, - model_max_length=getattr(config, "n_positions", 512), - ) + encoder_replacement_patterns = [ + (".layer.0.SelfAttention.", ".attention."), + (".layer.1.DenseReluDense.", ".dense."), + ] - @property - def eos_id(self) -> int: - return self._tokenizer.eos_token_id + decoder_replacement_patterns = [ + (".layer.0.SelfAttention.", ".self_attention."), + (".layer.1.EncDecAttention.", ".cross_attention."), + (".layer.2.DenseReluDense.", ".dense."), + ] - @property - def decoder_start_id(self) -> int: - return self._decoder_start_id + ignored_keys = [ + "decoder.layers.0.cross_attention.relative_attention_bias.weight" + ] - def encode(self, s: str) -> mx.array: - return mx.array( - self._tokenizer( - s, - return_tensors="np", - return_attention_mask=False, - )["input_ids"] - ) + def replace_key(key: str) -> str: + for old, new in shared_replacement_patterns: + key = key.replace(old, new) + if key.startswith("encoder."): + for old, new in encoder_replacement_patterns: + key = key.replace(old, new) + elif key.startswith("decoder."): + for old, new in decoder_replacement_patterns: + key = key.replace(old, new) + return key - def decode(self, t: List[int], with_sep: bool = True) -> str: - tokens = self._tokenizer.convert_ids_to_tokens(t) - return "".join(t.replace("▁", " " if with_sep else "") for t in tokens) + weights = {replace_key(k): v for k, v in weights.items()} + for key in ignored_keys: + if key in weights: + del weights[key] + return weights + + @classmethod + def from_pretrained( + cls, path_or_repo: str, dtype: mx.Dtype = mx.bfloat16 + ) -> tuple["T5", Tokenizer]: + from huggingface_hub import snapshot_download + + path = Path(path_or_repo) + if not path.exists(): + path = Path( + snapshot_download( + repo_id=path_or_repo, + allow_patterns=["*.json", "*.safetensors", "*.model"], + ) + ) + + with open(path / "config.json", "r") as f: + config = SimpleNamespace(**json.load(f)) + + model = T5(config) + weights = mx.load(str(path / "model.safetensors")) + weights = cls.sanitize(weights) + weights = {k: v.astype(dtype) for k, v in weights.items()} + model.load_weights(list(weights.items())) + return model, Tokenizer(config, "t5-base") def generate(prompt: str, model: T5, tokenizer: Tokenizer, temp: Optional[float] = 0.0): @@ -363,19 +446,6 @@ def generate(prompt: str, model: T5, tokenizer: Tokenizer, temp: Optional[float] yield y.squeeze() -def load_model(model_name: str, dtype: str = "float16"): - config = T5Config.from_pretrained(args.model) - dtype = getattr(mx, dtype) - model = T5(config) - file_name = model_name.replace("/", "-") - weights = mx.load(f"{file_name}.npz") - weights = tree_unflatten(list(weights.items())) - weights = tree_map(lambda p: p.astype(dtype), weights) - model.update(weights) - mx.eval(model.parameters()) - return model, Tokenizer(config) - - if __name__ == "__main__": parser = argparse.ArgumentParser(description="T5 Inference script") parser.add_argument( @@ -421,7 +491,8 @@ if __name__ == "__main__": mx.random.seed(args.seed) - model, tokenizer = load_model(args.model, args.dtype) + dtype = getattr(mx, args.dtype) + model, tokenizer = T5.from_pretrained(args.model, dtype) if args.encode_only: print("[INFO] Encoding with T5...", flush=True) From a5f2bab070c96d21905b5c6ae564c2fbd64990b2 Mon Sep 17 00:00:00 2001 From: Angelos Katharopoulos Date: Fri, 11 Oct 2024 21:17:41 -0700 Subject: [PATCH 047/188] Add FLUX finetuning (#1028) --- flux/README.md | 185 ++++++++++++ flux/dreambooth.py | 378 ++++++++++++++++++++++++ flux/flux/__init__.py | 248 ++++++++++++++++ flux/flux/autoencoder.py | 357 ++++++++++++++++++++++ flux/flux/clip.py | 154 ++++++++++ flux/flux/layers.py | 302 +++++++++++++++++++ flux/flux/lora.py | 76 +++++ flux/flux/model.py | 134 +++++++++ flux/flux/sampler.py | 56 ++++ flux/flux/t5.py | 244 +++++++++++++++ flux/flux/tokenizers.py | 185 ++++++++++++ flux/flux/utils.py | 209 +++++++++++++ flux/requirements.txt | 7 + flux/static/dog-r4-g8-1200-512x1024.png | Bin 0 -> 772523 bytes flux/static/dog-r4-g8-1200.png | Bin 0 -> 433610 bytes flux/static/dog6.png | Bin 0 -> 444359 bytes flux/static/generated-mlx.png | Bin 0 -> 156875 bytes flux/txt2image.py | 150 ++++++++++ 18 files changed, 2685 insertions(+) create mode 100644 flux/README.md create mode 100644 flux/dreambooth.py create mode 100644 flux/flux/__init__.py create mode 100644 flux/flux/autoencoder.py create mode 100644 flux/flux/clip.py create mode 100644 flux/flux/layers.py create mode 100644 flux/flux/lora.py create mode 100644 flux/flux/model.py create mode 100644 flux/flux/sampler.py create mode 100644 flux/flux/t5.py create mode 100644 flux/flux/tokenizers.py create mode 100644 flux/flux/utils.py create mode 100644 flux/requirements.txt create mode 100644 flux/static/dog-r4-g8-1200-512x1024.png create mode 100644 flux/static/dog-r4-g8-1200.png create mode 100644 flux/static/dog6.png create mode 100644 flux/static/generated-mlx.png create mode 100644 flux/txt2image.py diff --git a/flux/README.md b/flux/README.md new file mode 100644 index 00000000..33bebfd6 --- /dev/null +++ b/flux/README.md @@ -0,0 +1,185 @@ +FLUX +==== + +FLUX implementation in MLX. The implementation is ported directly from +[https://github.com/black-forest-labs/flux](https://github.com/black-forest-labs/flux) +and the model weights are downloaded directly from the Hugging Face Hub. + +The goal of this example is to be clean, educational and to allow for +experimentation with finetuning FLUX models as well as adding extra +functionality such as in-/outpainting, guidance with custom losses etc. + +![MLX image](static/generated-mlx.png) +*Image generated using FLUX-dev in MLX and the prompt 'An image in the style of +tron emanating futuristic technology with the word "MLX" in the center with +capital red letters.'* + +Installation +------------ + +The dependencies are minimal, namely: + +- `huggingface-hub` to download the checkpoints. +- `regex` for the tokenization +- `tqdm`, `PIL`, and `numpy` for the `txt2image.py` script +- `sentencepiece` for the T5 tokenizer + +You can install all of the above with the `requirements.txt` as follows: + + pip install -r requirements.txt + +Inference +--------- + +Inference in this example is similar to the stable diffusion example. The +classes to get you started are `FluxPipeline` from the `flux` module. + +```python +import mlx.core as mx +from flux import FluxPipeline + +# This will download all the weights from HF hub +flux = FluxPipeline("flux-schnell") + +# Make a generator that returns the latent variables from the reverse diffusion +# process +latent_generator = flux.generate_latents( + "A photo of an astronaut riding a horse on Mars", + num_steps=4, + latent_size=(32, 64), # 256x512 image +) + +# The first return value of the generator contains the conditioning and the +# random noise at the beginning of the diffusion process. +conditioning = next(latent_generator) +( + x_T, # The initial noise + x_positions, # The integer positions used for image positional encoding + t5_conditioning, # The T5 features from the text prompt + t5_positions, # Integer positions for text (normally all 0s) + clip_conditioning, # The clip text features from the text prompt +) = conditioning + +# Returning the conditioning as the first output from the generator allows us +# to unload T5 and clip before running the diffusion transformer. +mx.eval(conditioning) + +# Evaluate each diffusion step +for x_t in latent_generator: + mx.eval(x_t) + +# Note that we need to pass the latent size because it is collapsed and +# patchified in x_t and we need to unwrap it. +img = flux.decode(x_t, latent_size=(32, 64)) +``` + +The above are essentially the implementation of the `txt2image.py` script +except for some additional logic to quantize and/or load trained adapters. One +can use the script as follows: + +```shell +python txt2image.py --n-images 4 --n-rows 2 --image-size 256x512 'A photo of an astronaut riding a horse on Mars.' +``` + +### Experimental Options + +FLUX pads the prompt to a specific size of 512 tokens for the dev model and +256 for the schnell model. Not applying padding results in faster generation +but it is not clear how it may affect the generated images. To enable that +option in this example pass `--no-t5-padding` to the `txt2image.py` script or +instantiate the pipeline with `FluxPipeline("flux-schnell", t5_padding=False)`. + +Finetuning +---------- + +The `dreambooth.py` script supports LoRA finetuning of FLUX-dev (and schnell +but ymmv) on a provided image dataset. The dataset folder must have an +`index.json` file with the following format: + +```json +{ + "data": [ + {"image": "path-to-image-relative-to-dataset", "text": "Prompt to use with this image"}, + {"image": "path-to-image-relative-to-dataset", "text": "Prompt to use with this image"}, + {"image": "path-to-image-relative-to-dataset", "text": "Prompt to use with this image"}, + ... + ] +} +``` + +The training script by default trains for 600 iterations with a batch size of +1, gradient accumulation of 4 and LoRA rank of 8. Run `python dreambooth.py +--help` for the list of hyperparameters you can tune. + +> [!Note] +> FLUX finetuning requires approximately 50GB of RAM. QLoRA is coming soon and +> should reduce this number significantly. + +### Training Example + +This is a step-by-step finetuning example. We will be using the data from +[https://github.com/google/dreambooth](https://github.com/google/dreambooth). +In particular, we will use `dog6` which is a popular example for showcasing +dreambooth [^1]. + +The training images are the following 5 images [^2]: + +![dog6](static/dog6.png) + +We start by making the following `index.json` file and placing it in the same +folder as the images. + +```json +{ + "data": [ + {"image": "00.jpg", "text": "A photo of sks dog"}, + {"image": "01.jpg", "text": "A photo of sks dog"}, + {"image": "02.jpg", "text": "A photo of sks dog"}, + {"image": "03.jpg", "text": "A photo of sks dog"}, + {"image": "04.jpg", "text": "A photo of sks dog"} + ] +} +``` + +Subsequently we finetune FLUX using the following command: + +```shell +python dreambooth.py \ + --progress-prompt 'A photo of an sks dog lying on the sand at a beach in Greece' \ + --progress-every 600 --iterations 1200 --learning-rate 0.0001 \ + --lora-rank 4 --grad-accumulate 8 \ + path/to/dreambooth/dataset/dog6 +``` + +The training requires approximately 50GB of RAM and on an M2 Ultra it takes a +bit more than 1 hour. + +### Using the Adapter + +The adapters are saved in `mlx_output` and can be used directly by the +`txt2image.py` script. For instance, + +```shell +python txt2img.py --model dev --save-raw --image-size 512x512 --n-images 1 \ + --adapter mlx_output/mlx_output/0001200_adapters.safetensors \ + --fuse-adapter \ + --no-t5-padding \ + 'A photo of an sks dog lying on the sand at a beach in Greece' +``` + +generates an image that looks like the following, + +![dog image](static/dog-r4-g8-1200.png) + +and of course we can pass `--image-size 512x1024` to get larger images with +different aspect ratios, + +![wide dog image](static/dog-r4-g8-1200-512x1024.png) + +The arguments that are relevant to the adapters are of course `--adapter` and +`--fuse-adapter`. The first defines the path to an adapter to apply to the +model and the second fuses the adapter back into the model to get a bit more +speed during generation. + +[^1]: Refer to the [arXiv paper](https://arxiv.org/abs/2208.12242) for more details. +[^2]: The images are from unsplash by https://unsplash.com/@alvannee . diff --git a/flux/dreambooth.py b/flux/dreambooth.py new file mode 100644 index 00000000..4a4dbb08 --- /dev/null +++ b/flux/dreambooth.py @@ -0,0 +1,378 @@ +# Copyright © 2024 Apple Inc. + +import argparse +import json +import time +from functools import partial +from pathlib import Path + +import mlx.core as mx +import mlx.nn as nn +import mlx.optimizers as optim +import numpy as np +from mlx.nn.utils import average_gradients +from mlx.utils import tree_flatten, tree_map, tree_reduce +from PIL import Image +from tqdm import tqdm + +from flux import FluxPipeline + + +class FinetuningDataset: + def __init__(self, flux, args): + self.args = args + self.flux = flux + self.dataset_base = Path(args.dataset) + dataset_index = self.dataset_base / "index.json" + if not dataset_index.exists(): + raise ValueError(f"'{args.dataset}' is not a valid finetuning dataset") + with open(dataset_index, "r") as f: + self.index = json.load(f) + + self.latents = [] + self.t5_features = [] + self.clip_features = [] + + def _random_crop_resize(self, img): + resolution = self.args.resolution + width, height = img.size + + a, b, c, d = mx.random.uniform(shape=(4,), stream=mx.cpu).tolist() + + # Random crop the input image between 0.8 to 1.0 of its original dimensions + crop_size = ( + max((0.8 + 0.2 * a) * width, resolution[0]), + max((0.8 + 0.2 * a) * height, resolution[1]), + ) + pan = (width - crop_size[0], height - crop_size[1]) + img = img.crop( + ( + pan[0] * b, + pan[1] * c, + crop_size[0] + pan[0] * b, + crop_size[1] + pan[1] * c, + ) + ) + + # Fit the largest rectangle with the ratio of resolution in the image + # rectangle. + width, height = crop_size + ratio = resolution[0] / resolution[1] + r1 = (height * ratio, height) + r2 = (width, width / ratio) + r = r1 if r1[0] <= width else r2 + img = img.crop( + ( + (width - r[0]) / 2, + (height - r[1]) / 2, + (width + r[0]) / 2, + (height + r[1]) / 2, + ) + ) + + # Finally resize the image to resolution + img = img.resize(resolution, Image.LANCZOS) + + return mx.array(np.array(img)) + + def encode_images(self): + """Encode the images in the latent space to prepare for training.""" + self.flux.ae.eval() + for sample in tqdm(self.index["data"]): + input_img = Image.open(self.dataset_base / sample["image"]) + for i in range(self.args.num_augmentations): + img = self._random_crop_resize(input_img) + img = (img[:, :, :3].astype(self.flux.dtype) / 255) * 2 - 1 + x_0 = self.flux.ae.encode(img[None]) + x_0 = x_0.astype(self.flux.dtype) + mx.eval(x_0) + self.latents.append(x_0) + + def encode_prompts(self): + """Pre-encode the prompts so that we don't recompute them during + training (doesn't allow finetuning the text encoders).""" + for sample in tqdm(self.index["data"]): + t5_tok, clip_tok = self.flux.tokenize([sample["text"]]) + t5_feat = self.flux.t5(t5_tok) + clip_feat = self.flux.clip(clip_tok).pooled_output + mx.eval(t5_feat, clip_feat) + self.t5_features.append(t5_feat) + self.clip_features.append(clip_feat) + + def iterate(self, batch_size): + xs = mx.concatenate(self.latents) + t5 = mx.concatenate(self.t5_features) + clip = mx.concatenate(self.clip_features) + mx.eval(xs, t5, clip) + n_aug = self.args.num_augmentations + while True: + x_indices = mx.random.permutation(len(self.latents)) + c_indices = x_indices // n_aug + for i in range(0, len(self.latents), batch_size): + x_i = x_indices[i : i + batch_size] + c_i = c_indices[i : i + batch_size] + yield xs[x_i], t5[c_i], clip[c_i] + + +def generate_progress_images(iteration, flux, args): + """Generate images to monitor the progress of the finetuning.""" + out_dir = Path(args.output_dir) + out_dir.mkdir(parents=True, exist_ok=True) + out_file = out_dir / f"{iteration:07d}_progress.png" + print(f"Generating {str(out_file)}", flush=True) + + # Generate some images and arrange them in a grid + n_rows = 2 + n_images = 4 + x = flux.generate_images( + args.progress_prompt, + n_images, + args.progress_steps, + ) + x = mx.pad(x, [(0, 0), (4, 4), (4, 4), (0, 0)]) + B, H, W, C = x.shape + x = x.reshape(n_rows, B // n_rows, H, W, C).transpose(0, 2, 1, 3, 4) + x = x.reshape(n_rows * H, B // n_rows * W, C) + x = mx.pad(x, [(4, 4), (4, 4), (0, 0)]) + x = (x * 255).astype(mx.uint8) + + # Save them to disc + im = Image.fromarray(np.array(x)) + im.save(out_file) + + +def save_adapters(iteration, flux, args): + out_dir = Path(args.output_dir) + out_dir.mkdir(parents=True, exist_ok=True) + out_file = out_dir / f"{iteration:07d}_adapters.safetensors" + print(f"Saving {str(out_file)}") + + mx.save_safetensors( + str(out_file), + dict(tree_flatten(flux.flow.trainable_parameters())), + metadata={ + "lora_rank": str(args.lora_rank), + "lora_blocks": str(args.lora_blocks), + }, + ) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Finetune Flux to generate images with a specific subject" + ) + + parser.add_argument( + "--model", + default="dev", + choices=[ + "dev", + "schnell", + ], + help="Which flux model to train", + ) + parser.add_argument( + "--guidance", type=float, default=4.0, help="The guidance factor to use." + ) + parser.add_argument( + "--iterations", + type=int, + default=600, + help="How many iterations to train for", + ) + parser.add_argument( + "--batch-size", + type=int, + default=1, + help="The batch size to use when training the stable diffusion model", + ) + parser.add_argument( + "--resolution", + type=lambda x: tuple(map(int, x.split("x"))), + default=(512, 512), + help="The resolution of the training images", + ) + parser.add_argument( + "--num-augmentations", + type=int, + default=5, + help="Augment the images by random cropping and panning", + ) + parser.add_argument( + "--progress-prompt", + required=True, + help="Use this prompt when generating images for evaluation", + ) + parser.add_argument( + "--progress-steps", + type=int, + default=50, + help="Use this many steps when generating images for evaluation", + ) + parser.add_argument( + "--progress-every", + type=int, + default=50, + help="Generate images every PROGRESS_EVERY steps", + ) + parser.add_argument( + "--checkpoint-every", + type=int, + default=50, + help="Save the model every CHECKPOINT_EVERY steps", + ) + parser.add_argument( + "--lora-blocks", + type=int, + default=-1, + help="Train the last LORA_BLOCKS transformer blocks", + ) + parser.add_argument( + "--lora-rank", type=int, default=8, help="LoRA rank for finetuning" + ) + parser.add_argument( + "--warmup-steps", type=int, default=100, help="Learning rate warmup" + ) + parser.add_argument( + "--learning-rate", type=float, default="1e-4", help="Learning rate for training" + ) + parser.add_argument( + "--grad-accumulate", + type=int, + default=4, + help="Accumulate gradients for that many iterations before applying them", + ) + parser.add_argument( + "--output-dir", default="mlx_output", help="Folder to save the checkpoints in" + ) + + parser.add_argument("dataset") + + args = parser.parse_args() + + # Load the model and set it up for LoRA training. We use the same random + # state when creating the LoRA layers so all workers will have the same + # initial weights. + mx.random.seed(0x0F0F0F0F) + flux = FluxPipeline("flux-" + args.model) + flux.flow.freeze() + flux.linear_to_lora_layers(args.lora_rank, args.lora_blocks) + + # Reset the seed to a different seed per worker if we are in distributed + # mode so that each worker is working on different data, diffusion step and + # random noise. + mx.random.seed(0xF0F0F0F0 + mx.distributed.init().rank()) + + # Report how many parameters we are training + trainable_params = tree_reduce( + lambda acc, x: acc + x.size, flux.flow.trainable_parameters(), 0 + ) + print(f"Training {trainable_params / 1024**2:.3f}M parameters", flush=True) + + # Set up the optimizer and training steps. The steps are a bit verbose to + # support gradient accumulation together with compilation. + warmup = optim.linear_schedule(0, args.learning_rate, args.warmup_steps) + cosine = optim.cosine_decay( + args.learning_rate, args.iterations // args.grad_accumulate + ) + lr_schedule = optim.join_schedules([warmup, cosine], [args.warmup_steps]) + optimizer = optim.Adam(learning_rate=lr_schedule) + state = [flux.flow.state, optimizer.state, mx.random.state] + + @partial(mx.compile, inputs=state, outputs=state) + def single_step(x, t5_feat, clip_feat, guidance): + loss, grads = nn.value_and_grad(flux.flow, flux.training_loss)( + x, t5_feat, clip_feat, guidance + ) + grads = average_gradients(grads) + optimizer.update(flux.flow, grads) + + return loss + + @partial(mx.compile, inputs=state, outputs=state) + def compute_loss_and_grads(x, t5_feat, clip_feat, guidance): + return nn.value_and_grad(flux.flow, flux.training_loss)( + x, t5_feat, clip_feat, guidance + ) + + @partial(mx.compile, inputs=state, outputs=state) + def compute_loss_and_accumulate_grads(x, t5_feat, clip_feat, guidance, prev_grads): + loss, grads = nn.value_and_grad(flux.flow, flux.training_loss)( + x, t5_feat, clip_feat, guidance + ) + grads = tree_map(lambda a, b: a + b, prev_grads, grads) + return loss, grads + + @partial(mx.compile, inputs=state, outputs=state) + def grad_accumulate_and_step(x, t5_feat, clip_feat, guidance, prev_grads): + loss, grads = nn.value_and_grad(flux.flow, flux.training_loss)( + x, t5_feat, clip_feat, guidance + ) + grads = tree_map( + lambda a, b: (a + b) / args.grad_accumulate, + prev_grads, + grads, + ) + grads = average_gradients(grads) + optimizer.update(flux.flow, grads) + + return loss + + # We simply route to the appropriate step based on whether we have + # gradients from a previous step and whether we should be performing an + # update or simply computing and accumulating gradients in this step. + def step(x, t5_feat, clip_feat, guidance, prev_grads, perform_step): + if prev_grads is None: + if perform_step: + return single_step(x, t5_feat, clip_feat, guidance), None + else: + return compute_loss_and_grads(x, t5_feat, clip_feat, guidance) + else: + if perform_step: + return ( + grad_accumulate_and_step( + x, t5_feat, clip_feat, guidance, prev_grads + ), + None, + ) + else: + return compute_loss_and_accumulate_grads( + x, t5_feat, clip_feat, guidance, prev_grads + ) + + print("Create the training dataset.", flush=True) + dataset = FinetuningDataset(flux, args) + dataset.encode_images() + dataset.encode_prompts() + guidance = mx.full((args.batch_size,), args.guidance, dtype=flux.dtype) + + # An initial generation to compare + generate_progress_images(0, flux, args) + + grads = None + losses = [] + tic = time.time() + for i, batch in zip(range(args.iterations), dataset.iterate(args.batch_size)): + loss, grads = step(*batch, guidance, grads, (i + 1) % args.grad_accumulate == 0) + mx.eval(loss, grads, state) + losses.append(loss.item()) + + if (i + 1) % 10 == 0: + toc = time.time() + peak_mem = mx.metal.get_peak_memory() / 1024**3 + print( + f"Iter: {i+1} Loss: {sum(losses) / 10:.3f} " + f"It/s: {10 / (toc - tic):.3f} " + f"Peak mem: {peak_mem:.3f} GB", + flush=True, + ) + + if (i + 1) % args.progress_every == 0: + generate_progress_images(i + 1, flux, args) + + if (i + 1) % args.checkpoint_every == 0: + save_adapters(i + 1, flux, args) + + if (i + 1) % 10 == 0: + losses = [] + tic = time.time() diff --git a/flux/flux/__init__.py b/flux/flux/__init__.py new file mode 100644 index 00000000..8d39d605 --- /dev/null +++ b/flux/flux/__init__.py @@ -0,0 +1,248 @@ +# Copyright © 2024 Apple Inc. + +import math +import time +from typing import Tuple + +import mlx.core as mx +import mlx.nn as nn +from mlx.utils import tree_unflatten +from tqdm import tqdm + +from .lora import LoRALinear +from .sampler import FluxSampler +from .utils import ( + load_ae, + load_clip, + load_clip_tokenizer, + load_flow_model, + load_t5, + load_t5_tokenizer, +) + + +class FluxPipeline: + def __init__(self, name: str, t5_padding: bool = True): + self.dtype = mx.bfloat16 + self.name = name + self.t5_padding = t5_padding + + self.ae = load_ae(name) + self.flow = load_flow_model(name) + self.clip = load_clip(name) + self.clip_tokenizer = load_clip_tokenizer(name) + self.t5 = load_t5(name) + self.t5_tokenizer = load_t5_tokenizer(name) + self.sampler = FluxSampler(name) + + def ensure_models_are_loaded(self): + mx.eval( + self.ae.parameters(), + self.flow.parameters(), + self.clip.parameters(), + self.t5.parameters(), + ) + + def reload_text_encoders(self): + self.t5 = load_t5(self.name) + self.clip = load_clip(self.name) + + def tokenize(self, text): + t5_tokens = self.t5_tokenizer.encode(text, pad=self.t5_padding) + clip_tokens = self.clip_tokenizer.encode(text) + return t5_tokens, clip_tokens + + def _prepare_latent_images(self, x): + b, h, w, c = x.shape + + # Pack the latent image to 2x2 patches + x = x.reshape(b, h // 2, 2, w // 2, 2, c) + x = x.transpose(0, 1, 3, 5, 2, 4).reshape(b, h * w // 4, c * 4) + + # Create positions ids used to positionally encode each patch. Due to + # the way RoPE works, this results in an interesting positional + # encoding where parts of the feature are holding different positional + # information. Namely, the first part holds information independent of + # the spatial position (hence 0s), the 2nd part holds vertical spatial + # information and the last one horizontal. + i = mx.zeros((h // 2, w // 2), dtype=mx.int32) + j, k = mx.meshgrid(mx.arange(h // 2), mx.arange(w // 2), indexing="ij") + x_ids = mx.stack([i, j, k], axis=-1) + x_ids = mx.repeat(x_ids.reshape(1, h * w // 4, 3), b, 0) + + return x, x_ids + + def _prepare_conditioning(self, n_images, t5_tokens, clip_tokens): + # Prepare the text features + txt = self.t5(t5_tokens) + if len(txt) == 1 and n_images > 1: + txt = mx.broadcast_to(txt, (n_images, *txt.shape[1:])) + txt_ids = mx.zeros((n_images, txt.shape[1], 3), dtype=mx.int32) + + # Prepare the clip text features + vec = self.clip(clip_tokens).pooled_output + if len(vec) == 1 and n_images > 1: + vec = mx.broadcast_to(vec, (n_images, *vec.shape[1:])) + + return txt, txt_ids, vec + + def _denoising_loop( + self, + x_t, + x_ids, + txt, + txt_ids, + vec, + num_steps: int = 35, + guidance: float = 4.0, + start: float = 1, + stop: float = 0, + ): + B = len(x_t) + + def scalar(x): + return mx.full((B,), x, dtype=self.dtype) + + guidance = scalar(guidance) + timesteps = self.sampler.timesteps( + num_steps, + x_t.shape[1], + start=start, + stop=stop, + ) + for i in range(num_steps): + t = timesteps[i] + t_prev = timesteps[i + 1] + + pred = self.flow( + img=x_t, + img_ids=x_ids, + txt=txt, + txt_ids=txt_ids, + y=vec, + timesteps=scalar(t), + guidance=guidance, + ) + x_t = self.sampler.step(pred, x_t, t, t_prev) + + yield x_t + + def generate_latents( + self, + text: str, + n_images: int = 1, + num_steps: int = 35, + guidance: float = 4.0, + latent_size: Tuple[int, int] = (64, 64), + seed=None, + ): + # Set the PRNG state + if seed is not None: + mx.random.seed(seed) + + # Create the latent variables + x_T = self.sampler.sample_prior((n_images, *latent_size, 16), dtype=self.dtype) + x_T, x_ids = self._prepare_latent_images(x_T) + + # Get the conditioning + t5_tokens, clip_tokens = self.tokenize(text) + txt, txt_ids, vec = self._prepare_conditioning(n_images, t5_tokens, clip_tokens) + + # Yield the conditioning for controlled evaluation by the caller + yield (x_T, x_ids, txt, txt_ids, vec) + + # Yield the latent sequences from the denoising loop + yield from self._denoising_loop( + x_T, x_ids, txt, txt_ids, vec, num_steps=num_steps, guidance=guidance + ) + + def decode(self, x, latent_size: Tuple[int, int] = (64, 64)): + h, w = latent_size + x = x.reshape(len(x), h // 2, w // 2, -1, 2, 2) + x = x.transpose(0, 1, 4, 2, 5, 3).reshape(len(x), h, w, -1) + x = self.ae.decode(x) + return mx.clip(x + 1, 0, 2) * 0.5 + + def generate_images( + self, + text: str, + n_images: int = 1, + num_steps: int = 35, + guidance: float = 4.0, + latent_size: Tuple[int, int] = (64, 64), + seed=None, + reload_text_encoders: bool = True, + progress: bool = True, + ): + latents = self.generate_latents( + text, n_images, num_steps, guidance, latent_size, seed + ) + mx.eval(next(latents)) + + if reload_text_encoders: + self.reload_text_encoders() + + for x_t in tqdm(latents, total=num_steps, disable=not progress, leave=True): + mx.eval(x_t) + + images = [] + for i in tqdm(range(len(x_t)), disable=not progress): + images.append(self.decode(x_t[i : i + 1])) + mx.eval(images[-1]) + images = mx.concatenate(images, axis=0) + mx.eval(images) + + return images + + def training_loss( + self, + x_0: mx.array, + t5_features: mx.array, + clip_features: mx.array, + guidance: mx.array, + ): + # Get the text conditioning + txt = t5_features + txt_ids = mx.zeros(txt.shape[:-1] + (3,), dtype=mx.int32) + vec = clip_features + + # Prepare the latent input + x_0, x_ids = self._prepare_latent_images(x_0) + + # Forward process + t = self.sampler.random_timesteps(*x_0.shape[:2], dtype=self.dtype) + eps = mx.random.normal(x_0.shape, dtype=self.dtype) + x_t = self.sampler.add_noise(x_0, t, noise=eps) + x_t = mx.stop_gradient(x_t) + + # Do the denoising + pred = self.flow( + img=x_t, + img_ids=x_ids, + txt=txt, + txt_ids=txt_ids, + y=vec, + timesteps=t, + guidance=guidance, + ) + + return (pred + x_0 - eps).square().mean() + + def linear_to_lora_layers(self, rank: int = 8, num_blocks: int = -1): + """Swap the linear layers in the transformer blocks with LoRA layers.""" + all_blocks = self.flow.double_blocks + self.flow.single_blocks + all_blocks.reverse() + num_blocks = num_blocks if num_blocks > 0 else len(all_blocks) + for i, block in zip(range(num_blocks), all_blocks): + loras = [] + for name, module in block.named_modules(): + if isinstance(module, nn.Linear): + loras.append((name, LoRALinear.from_base(module, r=rank))) + block.update_modules(tree_unflatten(loras)) + + def fuse_lora_layers(self): + fused_layers = [] + for name, module in self.flow.named_modules(): + if isinstance(module, LoRALinear): + fused_layers.append((name, module.fuse())) + self.flow.update_modules(tree_unflatten(fused_layers)) diff --git a/flux/flux/autoencoder.py b/flux/flux/autoencoder.py new file mode 100644 index 00000000..6332bb57 --- /dev/null +++ b/flux/flux/autoencoder.py @@ -0,0 +1,357 @@ +# Copyright © 2024 Apple Inc. + +from dataclasses import dataclass +from typing import List + +import mlx.core as mx +import mlx.nn as nn +from mlx.nn.layers.upsample import upsample_nearest + + +@dataclass +class AutoEncoderParams: + resolution: int + in_channels: int + ch: int + out_ch: int + ch_mult: List[int] + num_res_blocks: int + z_channels: int + scale_factor: float + shift_factor: float + + +class AttnBlock(nn.Module): + def __init__(self, in_channels: int): + super().__init__() + self.in_channels = in_channels + + self.norm = nn.GroupNorm( + num_groups=32, + dims=in_channels, + eps=1e-6, + affine=True, + pytorch_compatible=True, + ) + self.q = nn.Linear(in_channels, in_channels) + self.k = nn.Linear(in_channels, in_channels) + self.v = nn.Linear(in_channels, in_channels) + self.proj_out = nn.Linear(in_channels, in_channels) + + def __call__(self, x: mx.array) -> mx.array: + B, H, W, C = x.shape + + y = x.reshape(B, 1, -1, C) + y = self.norm(y) + q = self.q(y) + k = self.k(y) + v = self.v(y) + y = mx.fast.scaled_dot_product_attention(q, k, v, scale=C ** (-0.5)) + y = self.proj_out(y) + + return x + y.reshape(B, H, W, C) + + +class ResnetBlock(nn.Module): + def __init__(self, in_channels: int, out_channels: int): + super().__init__() + self.in_channels = in_channels + out_channels = in_channels if out_channels is None else out_channels + self.out_channels = out_channels + + self.norm1 = nn.GroupNorm( + num_groups=32, + dims=in_channels, + eps=1e-6, + affine=True, + pytorch_compatible=True, + ) + self.conv1 = nn.Conv2d( + in_channels, out_channels, kernel_size=3, stride=1, padding=1 + ) + self.norm2 = nn.GroupNorm( + num_groups=32, + dims=out_channels, + eps=1e-6, + affine=True, + pytorch_compatible=True, + ) + self.conv2 = nn.Conv2d( + out_channels, out_channels, kernel_size=3, stride=1, padding=1 + ) + if self.in_channels != self.out_channels: + self.nin_shortcut = nn.Linear(in_channels, out_channels) + + def __call__(self, x): + h = x + h = self.norm1(h) + h = nn.silu(h) + h = self.conv1(h) + + h = self.norm2(h) + h = nn.silu(h) + h = self.conv2(h) + + if self.in_channels != self.out_channels: + x = self.nin_shortcut(x) + + return x + h + + +class Downsample(nn.Module): + def __init__(self, in_channels: int): + super().__init__() + self.conv = nn.Conv2d( + in_channels, in_channels, kernel_size=3, stride=2, padding=0 + ) + + def __call__(self, x: mx.array): + x = mx.pad(x, [(0, 0), (0, 1), (0, 1), (0, 0)]) + x = self.conv(x) + return x + + +class Upsample(nn.Module): + def __init__(self, in_channels: int): + super().__init__() + self.conv = nn.Conv2d( + in_channels, in_channels, kernel_size=3, stride=1, padding=1 + ) + + def __call__(self, x: mx.array): + x = upsample_nearest(x, (2, 2)) + x = self.conv(x) + return x + + +class Encoder(nn.Module): + def __init__( + self, + resolution: int, + in_channels: int, + ch: int, + ch_mult: list[int], + num_res_blocks: int, + z_channels: int, + ): + super().__init__() + self.ch = ch + self.num_resolutions = len(ch_mult) + self.num_res_blocks = num_res_blocks + self.resolution = resolution + self.in_channels = in_channels + # downsampling + self.conv_in = nn.Conv2d( + in_channels, self.ch, kernel_size=3, stride=1, padding=1 + ) + + curr_res = resolution + in_ch_mult = (1,) + tuple(ch_mult) + self.in_ch_mult = in_ch_mult + self.down = [] + block_in = self.ch + for i_level in range(self.num_resolutions): + block = [] + attn = [] # TODO: Remove the attn, nobody appends anything to it + block_in = ch * in_ch_mult[i_level] + block_out = ch * ch_mult[i_level] + for _ in range(self.num_res_blocks): + block.append(ResnetBlock(in_channels=block_in, out_channels=block_out)) + block_in = block_out + down = {} + down["block"] = block + down["attn"] = attn + if i_level != self.num_resolutions - 1: + down["downsample"] = Downsample(block_in) + curr_res = curr_res // 2 + self.down.append(down) + + # middle + self.mid = {} + self.mid["block_1"] = ResnetBlock(in_channels=block_in, out_channels=block_in) + self.mid["attn_1"] = AttnBlock(block_in) + self.mid["block_2"] = ResnetBlock(in_channels=block_in, out_channels=block_in) + + # end + self.norm_out = nn.GroupNorm( + num_groups=32, dims=block_in, eps=1e-6, affine=True, pytorch_compatible=True + ) + self.conv_out = nn.Conv2d( + block_in, 2 * z_channels, kernel_size=3, stride=1, padding=1 + ) + + def __call__(self, x: mx.array): + hs = [self.conv_in(x)] + for i_level in range(self.num_resolutions): + for i_block in range(self.num_res_blocks): + h = self.down[i_level]["block"][i_block](hs[-1]) + + # TODO: Remove the attn + if len(self.down[i_level]["attn"]) > 0: + h = self.down[i_level]["attn"][i_block](h) + + hs.append(h) + + if i_level != self.num_resolutions - 1: + hs.append(self.down[i_level]["downsample"](hs[-1])) + + # middle + h = hs[-1] + h = self.mid["block_1"](h) + h = self.mid["attn_1"](h) + h = self.mid["block_2"](h) + + # end + h = self.norm_out(h) + h = nn.silu(h) + h = self.conv_out(h) + + return h + + +class Decoder(nn.Module): + def __init__( + self, + ch: int, + out_ch: int, + ch_mult: list[int], + num_res_blocks: int, + in_channels: int, + resolution: int, + z_channels: int, + ): + super().__init__() + self.ch = ch + self.num_resolutions = len(ch_mult) + self.num_res_blocks = num_res_blocks + self.resolution = resolution + self.in_channels = in_channels + self.ffactor = 2 ** (self.num_resolutions - 1) + + # compute in_ch_mult, block_in and curr_res at lowest res + block_in = ch * ch_mult[self.num_resolutions - 1] + curr_res = resolution // 2 ** (self.num_resolutions - 1) + self.z_shape = (1, z_channels, curr_res, curr_res) + + # z to block_in + self.conv_in = nn.Conv2d( + z_channels, block_in, kernel_size=3, stride=1, padding=1 + ) + + # middle + self.mid = {} + self.mid["block_1"] = ResnetBlock(in_channels=block_in, out_channels=block_in) + self.mid["attn_1"] = AttnBlock(block_in) + self.mid["block_2"] = ResnetBlock(in_channels=block_in, out_channels=block_in) + + # upsampling + self.up = [] + for i_level in reversed(range(self.num_resolutions)): + block = [] + attn = [] # TODO: Remove the attn, nobody appends anything to it + + block_out = ch * ch_mult[i_level] + for _ in range(self.num_res_blocks + 1): + block.append(ResnetBlock(in_channels=block_in, out_channels=block_out)) + block_in = block_out + up = {} + up["block"] = block + up["attn"] = attn + if i_level != 0: + up["upsample"] = Upsample(block_in) + curr_res = curr_res * 2 + self.up.insert(0, up) # prepend to get consistent order + + # end + self.norm_out = nn.GroupNorm( + num_groups=32, dims=block_in, eps=1e-6, affine=True, pytorch_compatible=True + ) + self.conv_out = nn.Conv2d(block_in, out_ch, kernel_size=3, stride=1, padding=1) + + def __call__(self, z: mx.array): + # z to block_in + h = self.conv_in(z) + + # middle + h = self.mid["block_1"](h) + h = self.mid["attn_1"](h) + h = self.mid["block_2"](h) + + # upsampling + for i_level in reversed(range(self.num_resolutions)): + for i_block in range(self.num_res_blocks + 1): + h = self.up[i_level]["block"][i_block](h) + + # TODO: Remove the attn + if len(self.up[i_level]["attn"]) > 0: + h = self.up[i_level]["attn"][i_block](h) + + if i_level != 0: + h = self.up[i_level]["upsample"](h) + + # end + h = self.norm_out(h) + h = nn.silu(h) + h = self.conv_out(h) + + return h + + +class DiagonalGaussian(nn.Module): + def __call__(self, z: mx.array): + mean, logvar = mx.split(z, 2, axis=-1) + if self.training: + std = mx.exp(0.5 * logvar) + eps = mx.random.normal(shape=z.shape, dtype=z.dtype) + return mean + std * eps + else: + return mean + + +class AutoEncoder(nn.Module): + def __init__(self, params: AutoEncoderParams): + super().__init__() + self.encoder = Encoder( + resolution=params.resolution, + in_channels=params.in_channels, + ch=params.ch, + ch_mult=params.ch_mult, + num_res_blocks=params.num_res_blocks, + z_channels=params.z_channels, + ) + self.decoder = Decoder( + resolution=params.resolution, + in_channels=params.in_channels, + ch=params.ch, + out_ch=params.out_ch, + ch_mult=params.ch_mult, + num_res_blocks=params.num_res_blocks, + z_channels=params.z_channels, + ) + self.reg = DiagonalGaussian() + + self.scale_factor = params.scale_factor + self.shift_factor = params.shift_factor + + def sanitize(self, weights): + new_weights = {} + for k, w in weights.items(): + if w.ndim == 4: + w = w.transpose(0, 2, 3, 1) + w = w.reshape(-1).reshape(w.shape) + if w.shape[1:3] == (1, 1): + w = w.squeeze((1, 2)) + new_weights[k] = w + return new_weights + + def encode(self, x: mx.array): + z = self.reg(self.encoder(x)) + z = self.scale_factor * (z - self.shift_factor) + return z + + def decode(self, z: mx.array): + z = z / self.scale_factor + self.shift_factor + return self.decoder(z) + + def __call__(self, x: mx.array): + return self.decode(self.encode(x)) diff --git a/flux/flux/clip.py b/flux/flux/clip.py new file mode 100644 index 00000000..d5a30dbf --- /dev/null +++ b/flux/flux/clip.py @@ -0,0 +1,154 @@ +# Copyright © 2024 Apple Inc. + +from dataclasses import dataclass +from typing import List, Optional + +import mlx.core as mx +import mlx.nn as nn + +_ACTIVATIONS = {"quick_gelu": nn.gelu_fast_approx, "gelu": nn.gelu} + + +@dataclass +class CLIPTextModelConfig: + num_layers: int = 23 + model_dims: int = 1024 + num_heads: int = 16 + max_length: int = 77 + vocab_size: int = 49408 + hidden_act: str = "quick_gelu" + + @classmethod + def from_dict(cls, config): + return cls( + num_layers=config["num_hidden_layers"], + model_dims=config["hidden_size"], + num_heads=config["num_attention_heads"], + max_length=config["max_position_embeddings"], + vocab_size=config["vocab_size"], + hidden_act=config["hidden_act"], + ) + + +@dataclass +class CLIPOutput: + # The last_hidden_state indexed at the EOS token and possibly projected if + # the model has a projection layer + pooled_output: Optional[mx.array] = None + + # The full sequence output of the transformer after the final layernorm + last_hidden_state: Optional[mx.array] = None + + # A list of hidden states corresponding to the outputs of the transformer layers + hidden_states: Optional[List[mx.array]] = None + + +class CLIPEncoderLayer(nn.Module): + """The transformer encoder layer from CLIP.""" + + def __init__(self, model_dims: int, num_heads: int, activation: str): + super().__init__() + + self.layer_norm1 = nn.LayerNorm(model_dims) + self.layer_norm2 = nn.LayerNorm(model_dims) + + self.attention = nn.MultiHeadAttention(model_dims, num_heads, bias=True) + + self.linear1 = nn.Linear(model_dims, 4 * model_dims) + self.linear2 = nn.Linear(4 * model_dims, model_dims) + + self.act = _ACTIVATIONS[activation] + + def __call__(self, x, attn_mask=None): + y = self.layer_norm1(x) + y = self.attention(y, y, y, attn_mask) + x = y + x + + y = self.layer_norm2(x) + y = self.linear1(y) + y = self.act(y) + y = self.linear2(y) + x = y + x + + return x + + +class CLIPTextModel(nn.Module): + """Implements the text encoder transformer from CLIP.""" + + def __init__(self, config: CLIPTextModelConfig): + super().__init__() + + self.token_embedding = nn.Embedding(config.vocab_size, config.model_dims) + self.position_embedding = nn.Embedding(config.max_length, config.model_dims) + self.layers = [ + CLIPEncoderLayer(config.model_dims, config.num_heads, config.hidden_act) + for i in range(config.num_layers) + ] + self.final_layer_norm = nn.LayerNorm(config.model_dims) + + def _get_mask(self, N, dtype): + indices = mx.arange(N) + mask = indices[:, None] < indices[None] + mask = mask.astype(dtype) * (-6e4 if dtype == mx.float16 else -1e9) + return mask + + def sanitize(self, weights): + new_weights = {} + for key, w in weights.items(): + # Remove prefixes + if key.startswith("text_model."): + key = key[11:] + if key.startswith("embeddings."): + key = key[11:] + if key.startswith("encoder."): + key = key[8:] + + # Map attention layers + if "self_attn." in key: + key = key.replace("self_attn.", "attention.") + if "q_proj." in key: + key = key.replace("q_proj.", "query_proj.") + if "k_proj." in key: + key = key.replace("k_proj.", "key_proj.") + if "v_proj." in key: + key = key.replace("v_proj.", "value_proj.") + + # Map ffn layers + if "mlp.fc1" in key: + key = key.replace("mlp.fc1", "linear1") + if "mlp.fc2" in key: + key = key.replace("mlp.fc2", "linear2") + + new_weights[key] = w + + return new_weights + + def __call__(self, x): + # Extract some shapes + B, N = x.shape + eos_tokens = x.argmax(-1) + + # Compute the embeddings + x = self.token_embedding(x) + x = x + self.position_embedding.weight[:N] + + # Compute the features from the transformer + mask = self._get_mask(N, x.dtype) + hidden_states = [] + for l in self.layers: + x = l(x, mask) + hidden_states.append(x) + + # Apply the final layernorm and return + x = self.final_layer_norm(x) + last_hidden_state = x + + # Select the EOS token + pooled_output = x[mx.arange(len(x)), eos_tokens] + + return CLIPOutput( + pooled_output=pooled_output, + last_hidden_state=last_hidden_state, + hidden_states=hidden_states, + ) diff --git a/flux/flux/layers.py b/flux/flux/layers.py new file mode 100644 index 00000000..12397904 --- /dev/null +++ b/flux/flux/layers.py @@ -0,0 +1,302 @@ +# Copyright © 2024 Apple Inc. + +import math +from dataclasses import dataclass +from functools import partial +from typing import List, Optional, Tuple + +import mlx.core as mx +import mlx.nn as nn + + +def _rope(pos: mx.array, dim: int, theta: float): + scale = mx.arange(0, dim, 2, dtype=mx.float32) / dim + omega = 1.0 / (theta**scale) + x = pos[..., None] * omega + cosx = mx.cos(x) + sinx = mx.sin(x) + pe = mx.stack([cosx, -sinx, sinx, cosx], axis=-1) + pe = pe.reshape(*pe.shape[:-1], 2, 2) + + return pe + + +@partial(mx.compile, shapeless=True) +def _ab_plus_cd(a, b, c, d): + return a * b + c * d + + +def _apply_rope(x, pe): + s = x.shape + x = x.reshape(*s[:-1], -1, 1, 2) + x = _ab_plus_cd(x[..., 0], pe[..., 0], x[..., 1], pe[..., 1]) + return x.reshape(s) + + +def _attention(q: mx.array, k: mx.array, v: mx.array, pe: mx.array): + B, H, L, D = q.shape + + q = _apply_rope(q, pe) + k = _apply_rope(k, pe) + x = mx.fast.scaled_dot_product_attention(q, k, v, scale=D ** (-0.5)) + + return x.transpose(0, 2, 1, 3).reshape(B, L, -1) + + +def timestep_embedding( + t: mx.array, dim: int, max_period: int = 10000, time_factor: float = 1000.0 +): + half = dim // 2 + freqs = mx.arange(0, half, dtype=mx.float32) / half + freqs = freqs * (-math.log(max_period)) + freqs = mx.exp(freqs) + + x = (time_factor * t)[:, None] * freqs[None] + x = mx.concatenate([mx.cos(x), mx.sin(x)], axis=-1) + + return x.astype(t.dtype) + + +class EmbedND(nn.Module): + def __init__(self, dim: int, theta: int, axes_dim: List[int]): + super().__init__() + + self.dim = dim + self.theta = theta + self.axes_dim = axes_dim + + def __call__(self, ids: mx.array): + n_axes = ids.shape[-1] + pe = mx.concatenate( + [_rope(ids[..., i], self.axes_dim[i], self.theta) for i in range(n_axes)], + axis=-3, + ) + + return pe[:, None] + + +class MLPEmbedder(nn.Module): + def __init__(self, in_dim: int, hidden_dim: int): + super().__init__() + self.in_layer = nn.Linear(in_dim, hidden_dim, bias=True) + self.out_layer = nn.Linear(hidden_dim, hidden_dim, bias=True) + + def __call__(self, x: mx.array) -> mx.array: + return self.out_layer(nn.silu(self.in_layer(x))) + + +class QKNorm(nn.Module): + def __init__(self, dim: int): + super().__init__() + self.query_norm = nn.RMSNorm(dim) + self.key_norm = nn.RMSNorm(dim) + + def __call__(self, q: mx.array, k: mx.array) -> tuple[mx.array, mx.array]: + return self.query_norm(q), self.key_norm(k) + + +class SelfAttention(nn.Module): + def __init__(self, dim: int, num_heads: int = 8, qkv_bias: bool = False): + super().__init__() + self.num_heads = num_heads + head_dim = dim // num_heads + + self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias) + self.norm = QKNorm(head_dim) + self.proj = nn.Linear(dim, dim) + + def __call__(self, x: mx.array, pe: mx.array) -> mx.array: + H = self.num_heads + B, L, _ = x.shape + qkv = self.qkv(x) + q, k, v = mx.split(qkv, 3, axis=-1) + q = q.reshape(B, L, H, -1).transpose(0, 2, 1, 3) + k = k.reshape(B, L, H, -1).transpose(0, 2, 1, 3) + v = v.reshape(B, L, H, -1).transpose(0, 2, 1, 3) + q, k = self.norm(q, k) + x = _attention(q, k, v, pe) + x = self.proj(x) + return x + + +@dataclass +class ModulationOut: + shift: mx.array + scale: mx.array + gate: mx.array + + +class Modulation(nn.Module): + def __init__(self, dim: int, double: bool): + super().__init__() + self.is_double = double + self.multiplier = 6 if double else 3 + self.lin = nn.Linear(dim, self.multiplier * dim, bias=True) + + def __call__(self, x: mx.array) -> Tuple[ModulationOut, Optional[ModulationOut]]: + x = self.lin(nn.silu(x)) + xs = mx.split(x[:, None, :], self.multiplier, axis=-1) + + mod1 = ModulationOut(*xs[:3]) + mod2 = ModulationOut(*xs[3:]) if self.is_double else None + + return mod1, mod2 + + +class DoubleStreamBlock(nn.Module): + def __init__( + self, hidden_size: int, num_heads: int, mlp_ratio: float, qkv_bias: bool = False + ): + super().__init__() + + mlp_hidden_dim = int(hidden_size * mlp_ratio) + self.num_heads = num_heads + self.hidden_size = hidden_size + self.img_mod = Modulation(hidden_size, double=True) + self.img_norm1 = nn.LayerNorm(hidden_size, affine=False, eps=1e-6) + self.img_attn = SelfAttention( + dim=hidden_size, num_heads=num_heads, qkv_bias=qkv_bias + ) + + self.img_norm2 = nn.LayerNorm(hidden_size, affine=False, eps=1e-6) + self.img_mlp = nn.Sequential( + nn.Linear(hidden_size, mlp_hidden_dim, bias=True), + nn.GELU(approx="tanh"), + nn.Linear(mlp_hidden_dim, hidden_size, bias=True), + ) + + self.txt_mod = Modulation(hidden_size, double=True) + self.txt_norm1 = nn.LayerNorm(hidden_size, affine=False, eps=1e-6) + self.txt_attn = SelfAttention( + dim=hidden_size, num_heads=num_heads, qkv_bias=qkv_bias + ) + + self.txt_norm2 = nn.LayerNorm(hidden_size, affine=False, eps=1e-6) + self.txt_mlp = nn.Sequential( + nn.Linear(hidden_size, mlp_hidden_dim, bias=True), + nn.GELU(approx="tanh"), + nn.Linear(mlp_hidden_dim, hidden_size, bias=True), + ) + + def __call__( + self, img: mx.array, txt: mx.array, vec: mx.array, pe: mx.array + ) -> Tuple[mx.array, mx.array]: + B, L, _ = img.shape + _, S, _ = txt.shape + H = self.num_heads + + img_mod1, img_mod2 = self.img_mod(vec) + txt_mod1, txt_mod2 = self.txt_mod(vec) + + # prepare image for attention + img_modulated = self.img_norm1(img) + img_modulated = (1 + img_mod1.scale) * img_modulated + img_mod1.shift + img_qkv = self.img_attn.qkv(img_modulated) + img_q, img_k, img_v = mx.split(img_qkv, 3, axis=-1) + img_q = img_q.reshape(B, L, H, -1).transpose(0, 2, 1, 3) + img_k = img_k.reshape(B, L, H, -1).transpose(0, 2, 1, 3) + img_v = img_v.reshape(B, L, H, -1).transpose(0, 2, 1, 3) + img_q, img_k = self.img_attn.norm(img_q, img_k) + + # prepare txt for attention + txt_modulated = self.txt_norm1(txt) + txt_modulated = (1 + txt_mod1.scale) * txt_modulated + txt_mod1.shift + txt_qkv = self.txt_attn.qkv(txt_modulated) + txt_q, txt_k, txt_v = mx.split(txt_qkv, 3, axis=-1) + txt_q = txt_q.reshape(B, S, H, -1).transpose(0, 2, 1, 3) + txt_k = txt_k.reshape(B, S, H, -1).transpose(0, 2, 1, 3) + txt_v = txt_v.reshape(B, S, H, -1).transpose(0, 2, 1, 3) + txt_q, txt_k = self.txt_attn.norm(txt_q, txt_k) + + # run actual attention + q = mx.concatenate([txt_q, img_q], axis=2) + k = mx.concatenate([txt_k, img_k], axis=2) + v = mx.concatenate([txt_v, img_v], axis=2) + + attn = _attention(q, k, v, pe) + txt_attn, img_attn = mx.split(attn, [S], axis=1) + + # calculate the img bloks + img = img + img_mod1.gate * self.img_attn.proj(img_attn) + img = img + img_mod2.gate * self.img_mlp( + (1 + img_mod2.scale) * self.img_norm2(img) + img_mod2.shift + ) + + # calculate the txt bloks + txt = txt + txt_mod1.gate * self.txt_attn.proj(txt_attn) + txt = txt + txt_mod2.gate * self.txt_mlp( + (1 + txt_mod2.scale) * self.txt_norm2(txt) + txt_mod2.shift + ) + + return img, txt + + +class SingleStreamBlock(nn.Module): + def __init__( + self, + hidden_size: int, + num_heads: int, + mlp_ratio: float = 4.0, + qk_scale: Optional[float] = None, + ): + super().__init__() + self.hidden_dim = hidden_size + self.num_heads = num_heads + head_dim = hidden_size // num_heads + self.scale = qk_scale or head_dim**-0.5 + + self.mlp_hidden_dim = int(hidden_size * mlp_ratio) + # qkv and mlp_in + self.linear1 = nn.Linear(hidden_size, hidden_size * 3 + self.mlp_hidden_dim) + # proj and mlp_out + self.linear2 = nn.Linear(hidden_size + self.mlp_hidden_dim, hidden_size) + + self.norm = QKNorm(head_dim) + + self.hidden_size = hidden_size + self.pre_norm = nn.LayerNorm(hidden_size, affine=False, eps=1e-6) + + self.mlp_act = nn.GELU(approx="tanh") + self.modulation = Modulation(hidden_size, double=False) + + def __call__(self, x: mx.array, vec: mx.array, pe: mx.array): + B, L, _ = x.shape + H = self.num_heads + + mod, _ = self.modulation(vec) + x_mod = (1 + mod.scale) * self.pre_norm(x) + mod.shift + + q, k, v, mlp = mx.split( + self.linear1(x_mod), + [self.hidden_size, 2 * self.hidden_size, 3 * self.hidden_size], + axis=-1, + ) + q = q.reshape(B, L, H, -1).transpose(0, 2, 1, 3) + k = k.reshape(B, L, H, -1).transpose(0, 2, 1, 3) + v = v.reshape(B, L, H, -1).transpose(0, 2, 1, 3) + q, k = self.norm(q, k) + + # compute attention + y = _attention(q, k, v, pe) + + # compute activation in mlp stream, cat again and run second linear layer + y = self.linear2(mx.concatenate([y, self.mlp_act(mlp)], axis=2)) + return x + mod.gate * y + + +class LastLayer(nn.Module): + def __init__(self, hidden_size: int, patch_size: int, out_channels: int): + super().__init__() + self.norm_final = nn.LayerNorm(hidden_size, affine=False, eps=1e-6) + self.linear = nn.Linear( + hidden_size, patch_size * patch_size * out_channels, bias=True + ) + self.adaLN_modulation = nn.Sequential( + nn.SiLU(), nn.Linear(hidden_size, 2 * hidden_size, bias=True) + ) + + def __call__(self, x: mx.array, vec: mx.array): + shift, scale = mx.split(self.adaLN_modulation(vec), 2, axis=1) + x = (1 + scale[:, None, :]) * self.norm_final(x) + shift[:, None, :] + x = self.linear(x) + return x diff --git a/flux/flux/lora.py b/flux/flux/lora.py new file mode 100644 index 00000000..b0c8ae56 --- /dev/null +++ b/flux/flux/lora.py @@ -0,0 +1,76 @@ +# Copyright © 2024 Apple Inc. + +import math + +import mlx.core as mx +import mlx.nn as nn + + +class LoRALinear(nn.Module): + @staticmethod + def from_base( + linear: nn.Linear, + r: int = 8, + dropout: float = 0.0, + scale: float = 1.0, + ): + output_dims, input_dims = linear.weight.shape + lora_lin = LoRALinear( + input_dims=input_dims, + output_dims=output_dims, + r=r, + dropout=dropout, + scale=scale, + ) + lora_lin.linear = linear + return lora_lin + + def fuse(self): + linear = self.linear + bias = "bias" in linear + weight = linear.weight + dtype = weight.dtype + + output_dims, input_dims = weight.shape + fused_linear = nn.Linear(input_dims, output_dims, bias=bias) + + lora_b = self.scale * self.lora_b.T + lora_a = self.lora_a.T + fused_linear.weight = weight + (lora_b @ lora_a).astype(dtype) + if bias: + fused_linear.bias = linear.bias + + return fused_linear + + def __init__( + self, + input_dims: int, + output_dims: int, + r: int = 8, + dropout: float = 0.0, + scale: float = 1.0, + bias: bool = False, + ): + super().__init__() + + # Regular linear layer weights + self.linear = nn.Linear(input_dims, output_dims, bias=bias) + + self.dropout = nn.Dropout(p=dropout) + + # Scale for low-rank update + self.scale = scale + + # Low rank lora weights + scale = 1 / math.sqrt(input_dims) + self.lora_a = mx.random.uniform( + low=-scale, + high=scale, + shape=(input_dims, r), + ) + self.lora_b = mx.zeros(shape=(r, output_dims)) + + def __call__(self, x): + y = self.linear(x) + z = (self.dropout(x) @ self.lora_a) @ self.lora_b + return y + (self.scale * z).astype(x.dtype) diff --git a/flux/flux/model.py b/flux/flux/model.py new file mode 100644 index 00000000..18ea70b0 --- /dev/null +++ b/flux/flux/model.py @@ -0,0 +1,134 @@ +# Copyright © 2024 Apple Inc. + +from dataclasses import dataclass +from typing import Optional + +import mlx.core as mx +import mlx.nn as nn + +from .layers import ( + DoubleStreamBlock, + EmbedND, + LastLayer, + MLPEmbedder, + SingleStreamBlock, + timestep_embedding, +) + + +@dataclass +class FluxParams: + in_channels: int + vec_in_dim: int + context_in_dim: int + hidden_size: int + mlp_ratio: float + num_heads: int + depth: int + depth_single_blocks: int + axes_dim: list[int] + theta: int + qkv_bias: bool + guidance_embed: bool + + +class Flux(nn.Module): + def __init__(self, params: FluxParams): + super().__init__() + + self.params = params + self.in_channels = params.in_channels + self.out_channels = self.in_channels + if params.hidden_size % params.num_heads != 0: + raise ValueError( + f"Hidden size {params.hidden_size} must be divisible by num_heads {params.num_heads}" + ) + pe_dim = params.hidden_size // params.num_heads + if sum(params.axes_dim) != pe_dim: + raise ValueError( + f"Got {params.axes_dim} but expected positional dim {pe_dim}" + ) + self.hidden_size = params.hidden_size + self.num_heads = params.num_heads + self.pe_embedder = EmbedND( + dim=pe_dim, theta=params.theta, axes_dim=params.axes_dim + ) + self.img_in = nn.Linear(self.in_channels, self.hidden_size, bias=True) + self.time_in = MLPEmbedder(in_dim=256, hidden_dim=self.hidden_size) + self.vector_in = MLPEmbedder(params.vec_in_dim, self.hidden_size) + self.guidance_in = ( + MLPEmbedder(in_dim=256, hidden_dim=self.hidden_size) + if params.guidance_embed + else nn.Identity() + ) + self.txt_in = nn.Linear(params.context_in_dim, self.hidden_size) + + self.double_blocks = [ + DoubleStreamBlock( + self.hidden_size, + self.num_heads, + mlp_ratio=params.mlp_ratio, + qkv_bias=params.qkv_bias, + ) + for _ in range(params.depth) + ] + + self.single_blocks = [ + SingleStreamBlock( + self.hidden_size, self.num_heads, mlp_ratio=params.mlp_ratio + ) + for _ in range(params.depth_single_blocks) + ] + + self.final_layer = LastLayer(self.hidden_size, 1, self.out_channels) + + def sanitize(self, weights): + new_weights = {} + for k, w in weights.items(): + if k.endswith(".scale"): + k = k[:-6] + ".weight" + for seq in ["img_mlp", "txt_mlp", "adaLN_modulation"]: + if f".{seq}." in k: + k = k.replace(f".{seq}.", f".{seq}.layers.") + break + new_weights[k] = w + return new_weights + + def __call__( + self, + img: mx.array, + img_ids: mx.array, + txt: mx.array, + txt_ids: mx.array, + timesteps: mx.array, + y: mx.array, + guidance: Optional[mx.array] = None, + ) -> mx.array: + if img.ndim != 3 or txt.ndim != 3: + raise ValueError("Input img and txt tensors must have 3 dimensions.") + + img = self.img_in(img) + vec = self.time_in(timestep_embedding(timesteps, 256)) + if self.params.guidance_embed: + if guidance is None: + raise ValueError( + "Didn't get guidance strength for guidance distilled model." + ) + vec = vec + self.guidance_in(timestep_embedding(guidance, 256)) + vec = vec + self.vector_in(y) + txt = self.txt_in(txt) + + ids = mx.concatenate([txt_ids, img_ids], axis=1) + pe = self.pe_embedder(ids).astype(img.dtype) + + for block in self.double_blocks: + img, txt = block(img=img, txt=txt, vec=vec, pe=pe) + + img = mx.concatenate([txt, img], axis=1) + for block in self.single_blocks: + img = block(img, vec=vec, pe=pe) + img = img[:, txt.shape[1] :, ...] + + img = self.final_layer(img, vec) + + return img diff --git a/flux/flux/sampler.py b/flux/flux/sampler.py new file mode 100644 index 00000000..3bff1ca2 --- /dev/null +++ b/flux/flux/sampler.py @@ -0,0 +1,56 @@ +# Copyright © 2024 Apple Inc. + +import math +from functools import lru_cache + +import mlx.core as mx + + +class FluxSampler: + def __init__(self, name: str, base_shift: float = 0.5, max_shift: float = 1.5): + self._base_shift = base_shift + self._max_shift = max_shift + self._schnell = "schnell" in name + + def _time_shift(self, x, t): + x1, x2 = 256, 4096 + t1, t2 = self._base_shift, self._max_shift + exp_mu = math.exp((x - x1) * (t2 - t1) / (x2 - x1) + t1) + t = exp_mu / (exp_mu + (1 / t - 1)) + return t + + @lru_cache + def timesteps( + self, num_steps, image_sequence_length, start: float = 1, stop: float = 0 + ): + t = mx.linspace(start, stop, num_steps + 1) + + if self._schnell: + t = self._time_shift(image_sequence_length, t) + + return t.tolist() + + def random_timesteps(self, B, L, dtype=mx.float32, key=None): + if self._schnell: + # TODO: Should we upweigh 1 and 0.75? + t = mx.random.randint(1, 5, shape=(B,), key=key) + t = t.astype(dtype) / 4 + else: + t = mx.random.uniform(shape=(B,), dtype=dtype, key=key) + t = self._time_shift(L, t) + + return t + + def sample_prior(self, shape, dtype=mx.float32, key=None): + return mx.random.normal(shape, dtype=dtype, key=key) + + def add_noise(self, x, t, noise=None, key=None): + noise = ( + noise + if noise is not None + else mx.random.normal(x.shape, dtype=x.dtype, key=key) + ) + return x * (1 - t) + t * noise + + def step(self, pred, x_t, t, t_prev): + return x_t + (t_prev - t) * pred diff --git a/flux/flux/t5.py b/flux/flux/t5.py new file mode 100644 index 00000000..cf0515cd --- /dev/null +++ b/flux/flux/t5.py @@ -0,0 +1,244 @@ +# Copyright © 2024 Apple Inc. + +import math +from dataclasses import dataclass +from typing import List, Optional, Tuple + +import mlx.core as mx +import mlx.nn as nn + +_SHARED_REPLACEMENT_PATTERNS = [ + (".block.", ".layers."), + (".k.", ".key_proj."), + (".o.", ".out_proj."), + (".q.", ".query_proj."), + (".v.", ".value_proj."), + ("shared.", "wte."), + ("lm_head.", "lm_head.linear."), + (".layer.0.layer_norm.", ".ln1."), + (".layer.1.layer_norm.", ".ln2."), + (".layer.2.layer_norm.", ".ln3."), + (".final_layer_norm.", ".ln."), + ( + "layers.0.layer.0.SelfAttention.relative_attention_bias.", + "relative_attention_bias.embeddings.", + ), +] + +_ENCODER_REPLACEMENT_PATTERNS = [ + (".layer.0.SelfAttention.", ".attention."), + (".layer.1.DenseReluDense.", ".dense."), +] + + +@dataclass +class T5Config: + vocab_size: int + num_layers: int + num_heads: int + relative_attention_num_buckets: int + d_kv: int + d_model: int + feed_forward_proj: str + tie_word_embeddings: bool + + d_ff: Optional[int] = None + num_decoder_layers: Optional[int] = None + relative_attention_max_distance: int = 128 + layer_norm_epsilon: float = 1e-6 + + @classmethod + def from_dict(cls, config): + return cls( + vocab_size=config["vocab_size"], + num_layers=config["num_layers"], + num_heads=config["num_heads"], + relative_attention_num_buckets=config["relative_attention_num_buckets"], + d_kv=config["d_kv"], + d_model=config["d_model"], + feed_forward_proj=config["feed_forward_proj"], + tie_word_embeddings=config["tie_word_embeddings"], + d_ff=config.get("d_ff", 4 * config["d_model"]), + num_decoder_layers=config.get("num_decoder_layers", config["num_layers"]), + relative_attention_max_distance=config.get( + "relative_attention_max_distance", 128 + ), + layer_norm_epsilon=config.get("layer_norm_epsilon", 1e-6), + ) + + +class RelativePositionBias(nn.Module): + def __init__(self, config: T5Config, bidirectional: bool): + self.bidirectional = bidirectional + self.num_buckets = config.relative_attention_num_buckets + self.max_distance = config.relative_attention_max_distance + self.n_heads = config.num_heads + self.embeddings = nn.Embedding(self.num_buckets, self.n_heads) + + @staticmethod + def _relative_position_bucket(rpos, bidirectional, num_buckets, max_distance): + num_buckets = num_buckets // 2 if bidirectional else num_buckets + max_exact = num_buckets // 2 + + abspos = rpos.abs() + is_small = abspos < max_exact + + scale = (num_buckets - max_exact) / math.log(max_distance / max_exact) + buckets_large = (mx.log(abspos / max_exact) * scale).astype(mx.int16) + buckets_large = mx.minimum(max_exact + buckets_large, num_buckets - 1) + + buckets = mx.where(is_small, abspos, buckets_large) + if bidirectional: + buckets = buckets + (rpos > 0) * num_buckets + else: + buckets = buckets * (rpos < 0) + + return buckets + + def __call__(self, query_length: int, key_length: int, offset: int = 0): + """Compute binned relative position bias""" + context_position = mx.arange(offset, query_length)[:, None] + memory_position = mx.arange(key_length)[None, :] + + # shape (query_length, key_length) + relative_position = memory_position - context_position + relative_position_bucket = self._relative_position_bucket( + relative_position, + bidirectional=self.bidirectional, + num_buckets=self.num_buckets, + max_distance=self.max_distance, + ) + + # shape (query_length, key_length, num_heads) + values = self.embeddings(relative_position_bucket) + + # shape (num_heads, query_length, key_length) + return values.transpose(2, 0, 1) + + +class MultiHeadAttention(nn.Module): + def __init__(self, config: T5Config): + super().__init__() + inner_dim = config.d_kv * config.num_heads + self.num_heads = config.num_heads + self.query_proj = nn.Linear(config.d_model, inner_dim, bias=False) + self.key_proj = nn.Linear(config.d_model, inner_dim, bias=False) + self.value_proj = nn.Linear(config.d_model, inner_dim, bias=False) + self.out_proj = nn.Linear(inner_dim, config.d_model, bias=False) + + def __call__( + self, + queries: mx.array, + keys: mx.array, + values: mx.array, + mask: Optional[mx.array], + cache: Optional[Tuple[mx.array, mx.array]] = None, + ) -> [mx.array, Tuple[mx.array, mx.array]]: + queries = self.query_proj(queries) + keys = self.key_proj(keys) + values = self.value_proj(values) + + num_heads = self.num_heads + B, L, _ = queries.shape + _, S, _ = keys.shape + queries = queries.reshape(B, L, num_heads, -1).transpose(0, 2, 1, 3) + keys = keys.reshape(B, S, num_heads, -1).transpose(0, 2, 1, 3) + values = values.reshape(B, S, num_heads, -1).transpose(0, 2, 1, 3) + + if cache is not None: + key_cache, value_cache = cache + keys = mx.concatenate([key_cache, keys], axis=3) + values = mx.concatenate([value_cache, values], axis=2) + + values_hat = mx.fast.scaled_dot_product_attention( + queries, keys, values, scale=1.0, mask=mask.astype(queries.dtype) + ) + values_hat = values_hat.transpose(0, 2, 1, 3).reshape(B, L, -1) + + return self.out_proj(values_hat), (keys, values) + + +class DenseActivation(nn.Module): + def __init__(self, config: T5Config): + super().__init__() + mlp_dims = config.d_ff or config.d_model * 4 + self.gated = config.feed_forward_proj.startswith("gated") + if self.gated: + self.wi_0 = nn.Linear(config.d_model, mlp_dims, bias=False) + self.wi_1 = nn.Linear(config.d_model, mlp_dims, bias=False) + else: + self.wi = nn.Linear(config.d_model, mlp_dims, bias=False) + self.wo = nn.Linear(mlp_dims, config.d_model, bias=False) + activation = config.feed_forward_proj.removeprefix("gated-") + if activation == "relu": + self.act = nn.relu + elif activation == "gelu": + self.act = nn.gelu + elif activation == "silu": + self.act = nn.silu + else: + raise ValueError(f"Unknown activation: {activation}") + + def __call__(self, x): + if self.gated: + hidden_act = self.act(self.wi_0(x)) + hidden_linear = self.wi_1(x) + x = hidden_act * hidden_linear + else: + x = self.act(self.wi(x)) + return self.wo(x) + + +class TransformerEncoderLayer(nn.Module): + def __init__(self, config: T5Config): + super().__init__() + self.attention = MultiHeadAttention(config) + self.ln1 = nn.RMSNorm(config.d_model, eps=config.layer_norm_epsilon) + self.ln2 = nn.RMSNorm(config.d_model, eps=config.layer_norm_epsilon) + self.dense = DenseActivation(config) + + def __call__(self, x, mask): + y = self.ln1(x) + y, _ = self.attention(y, y, y, mask=mask) + x = x + y + + y = self.ln2(x) + y = self.dense(y) + return x + y + + +class TransformerEncoder(nn.Module): + def __init__(self, config: T5Config): + super().__init__() + self.layers = [ + TransformerEncoderLayer(config) for i in range(config.num_layers) + ] + self.ln = nn.RMSNorm(config.d_model, eps=config.layer_norm_epsilon) + self.relative_attention_bias = RelativePositionBias(config, bidirectional=True) + + def __call__(self, x: mx.array): + pos_bias = self.relative_attention_bias(x.shape[1], x.shape[1]) + pos_bias = pos_bias.astype(x.dtype) + for layer in self.layers: + x = layer(x, mask=pos_bias) + return self.ln(x) + + +class T5Encoder(nn.Module): + def __init__(self, config: T5Config): + self.wte = nn.Embedding(config.vocab_size, config.d_model) + self.encoder = TransformerEncoder(config) + + def sanitize(self, weights): + new_weights = {} + for k, w in weights.items(): + for old, new in _SHARED_REPLACEMENT_PATTERNS: + k = k.replace(old, new) + if k.startswith("encoder."): + for old, new in _ENCODER_REPLACEMENT_PATTERNS: + k = k.replace(old, new) + new_weights[k] = w + return new_weights + + def __call__(self, inputs: mx.array): + return self.encoder(self.wte(inputs)) diff --git a/flux/flux/tokenizers.py b/flux/flux/tokenizers.py new file mode 100644 index 00000000..796ef389 --- /dev/null +++ b/flux/flux/tokenizers.py @@ -0,0 +1,185 @@ +# Copyright © 2024 Apple Inc. + +import mlx.core as mx +import regex +from sentencepiece import SentencePieceProcessor + + +class CLIPTokenizer: + """A simple port of CLIPTokenizer from https://github.com/huggingface/transformers/ .""" + + def __init__(self, bpe_ranks, vocab, max_length=77): + self.max_length = max_length + self.bpe_ranks = bpe_ranks + self.vocab = vocab + self.pat = regex.compile( + r"""<\|startoftext\|>|<\|endoftext\|>|'s|'t|'re|'ve|'m|'ll|'d|[\p{L}]+|[\p{N}]|[^\s\p{L}\p{N}]+""", + regex.IGNORECASE, + ) + + self._cache = {self.bos: self.bos, self.eos: self.eos} + + @property + def bos(self): + return "<|startoftext|>" + + @property + def bos_token(self): + return self.vocab[self.bos] + + @property + def eos(self): + return "<|endoftext|>" + + @property + def eos_token(self): + return self.vocab[self.eos] + + def bpe(self, text): + if text in self._cache: + return self._cache[text] + + unigrams = list(text[:-1]) + [text[-1] + ""] + unique_bigrams = set(zip(unigrams, unigrams[1:])) + + if not unique_bigrams: + return unigrams + + # In every iteration try to merge the two most likely bigrams. If none + # was merged we are done. + # + # Ported from https://github.com/huggingface/transformers/blob/main/src/transformers/models/clip/tokenization_clip.py + while unique_bigrams: + bigram = min( + unique_bigrams, key=lambda pair: self.bpe_ranks.get(pair, float("inf")) + ) + if bigram not in self.bpe_ranks: + break + + new_unigrams = [] + skip = False + for a, b in zip(unigrams, unigrams[1:]): + if skip: + skip = False + continue + + if (a, b) == bigram: + new_unigrams.append(a + b) + skip = True + + else: + new_unigrams.append(a) + + if not skip: + new_unigrams.append(b) + + unigrams = new_unigrams + unique_bigrams = set(zip(unigrams, unigrams[1:])) + + self._cache[text] = unigrams + + return unigrams + + def tokenize(self, text, prepend_bos=True, append_eos=True): + if isinstance(text, list): + return [self.tokenize(t, prepend_bos, append_eos) for t in text] + + # Lower case cleanup and split according to self.pat. Hugging Face does + # a much more thorough job here but this should suffice for 95% of + # cases. + clean_text = regex.sub(r"\s+", " ", text.lower()) + tokens = regex.findall(self.pat, clean_text) + + # Split the tokens according to the byte-pair merge file + bpe_tokens = [ti for t in tokens for ti in self.bpe(t)] + + # Map to token ids and return + tokens = [self.vocab[t] for t in bpe_tokens] + if prepend_bos: + tokens = [self.bos_token] + tokens + if append_eos: + tokens.append(self.eos_token) + + if len(tokens) > self.max_length: + tokens = tokens[: self.max_length] + if append_eos: + tokens[-1] = self.eos_token + + return tokens + + def encode(self, text): + if not isinstance(text, list): + return self.encode([text]) + + tokens = self.tokenize(text) + length = max(len(t) for t in tokens) + for t in tokens: + t.extend([self.eos_token] * (length - len(t))) + + return mx.array(tokens) + + +class T5Tokenizer: + def __init__(self, model_file, max_length=512): + self._tokenizer = SentencePieceProcessor(model_file) + self.max_length = max_length + + @property + def pad(self): + try: + return self._tokenizer.id_to_piece(self.pad_token) + except IndexError: + return None + + @property + def pad_token(self): + return self._tokenizer.pad_id() + + @property + def bos(self): + try: + return self._tokenizer.id_to_piece(self.bos_token) + except IndexError: + return None + + @property + def bos_token(self): + return self._tokenizer.bos_id() + + @property + def eos(self): + try: + return self._tokenizer.id_to_piece(self.eos_token) + except IndexError: + return None + + @property + def eos_token(self): + return self._tokenizer.eos_id() + + def tokenize(self, text, prepend_bos=True, append_eos=True, pad=True): + if isinstance(text, list): + return [self.tokenize(t, prepend_bos, append_eos, pad) for t in text] + + tokens = self._tokenizer.encode(text) + + if prepend_bos and self.bos_token >= 0: + tokens = [self.bos_token] + tokens + if append_eos and self.eos_token >= 0: + tokens.append(self.eos_token) + if pad and len(tokens) < self.max_length and self.pad_token >= 0: + tokens += [self.pad_token] * (self.max_length - len(tokens)) + + return tokens + + def encode(self, text, pad=True): + if not isinstance(text, list): + return self.encode([text], pad=pad) + + pad_token = self.pad_token if self.pad_token >= 0 else 0 + tokens = self.tokenize(text, pad=pad) + length = max(len(t) for t in tokens) + for t in tokens: + t.extend([pad_token] * (length - len(t))) + + return mx.array(tokens) diff --git a/flux/flux/utils.py b/flux/flux/utils.py new file mode 100644 index 00000000..21db17d3 --- /dev/null +++ b/flux/flux/utils.py @@ -0,0 +1,209 @@ +# Copyright © 2024 Apple Inc. + +import json +import os +from dataclasses import dataclass +from typing import Optional + +import mlx.core as mx +from huggingface_hub import hf_hub_download + +from .autoencoder import AutoEncoder, AutoEncoderParams +from .clip import CLIPTextModel, CLIPTextModelConfig +from .model import Flux, FluxParams +from .t5 import T5Config, T5Encoder +from .tokenizers import CLIPTokenizer, T5Tokenizer + + +@dataclass +class ModelSpec: + params: FluxParams + ae_params: AutoEncoderParams + ckpt_path: Optional[str] + ae_path: Optional[str] + repo_id: Optional[str] + repo_flow: Optional[str] + repo_ae: Optional[str] + + +configs = { + "flux-dev": ModelSpec( + repo_id="black-forest-labs/FLUX.1-dev", + repo_flow="flux1-dev.safetensors", + repo_ae="ae.safetensors", + ckpt_path=os.getenv("FLUX_DEV"), + params=FluxParams( + in_channels=64, + vec_in_dim=768, + context_in_dim=4096, + hidden_size=3072, + mlp_ratio=4.0, + num_heads=24, + depth=19, + depth_single_blocks=38, + axes_dim=[16, 56, 56], + theta=10_000, + qkv_bias=True, + guidance_embed=True, + ), + ae_path=os.getenv("AE"), + ae_params=AutoEncoderParams( + resolution=256, + in_channels=3, + ch=128, + out_ch=3, + ch_mult=[1, 2, 4, 4], + num_res_blocks=2, + z_channels=16, + scale_factor=0.3611, + shift_factor=0.1159, + ), + ), + "flux-schnell": ModelSpec( + repo_id="black-forest-labs/FLUX.1-schnell", + repo_flow="flux1-schnell.safetensors", + repo_ae="ae.safetensors", + ckpt_path=os.getenv("FLUX_SCHNELL"), + params=FluxParams( + in_channels=64, + vec_in_dim=768, + context_in_dim=4096, + hidden_size=3072, + mlp_ratio=4.0, + num_heads=24, + depth=19, + depth_single_blocks=38, + axes_dim=[16, 56, 56], + theta=10_000, + qkv_bias=True, + guidance_embed=False, + ), + ae_path=os.getenv("AE"), + ae_params=AutoEncoderParams( + resolution=256, + in_channels=3, + ch=128, + out_ch=3, + ch_mult=[1, 2, 4, 4], + num_res_blocks=2, + z_channels=16, + scale_factor=0.3611, + shift_factor=0.1159, + ), + ), +} + + +def load_flow_model(name: str, hf_download: bool = True): + # Get the safetensors file to load + ckpt_path = configs[name].ckpt_path + + # Download if needed + if ( + ckpt_path is None + and configs[name].repo_id is not None + and configs[name].repo_flow is not None + and hf_download + ): + ckpt_path = hf_hub_download(configs[name].repo_id, configs[name].repo_flow) + + # Make the model + model = Flux(configs[name].params) + + # Load the checkpoint if needed + if ckpt_path is not None: + weights = mx.load(ckpt_path) + weights = model.sanitize(weights) + model.load_weights(list(weights.items())) + + return model + + +def load_ae(name: str, hf_download: bool = True): + # Get the safetensors file to load + ckpt_path = configs[name].ae_path + + # Download if needed + if ( + ckpt_path is None + and configs[name].repo_id is not None + and configs[name].repo_ae is not None + and hf_download + ): + ckpt_path = hf_hub_download(configs[name].repo_id, configs[name].repo_ae) + + # Make the autoencoder + ae = AutoEncoder(configs[name].ae_params) + + # Load the checkpoint if needed + if ckpt_path is not None: + weights = mx.load(ckpt_path) + weights = ae.sanitize(weights) + ae.load_weights(list(weights.items())) + + return ae + + +def load_clip(name: str): + # Load the config + config_path = hf_hub_download(configs[name].repo_id, "text_encoder/config.json") + with open(config_path) as f: + config = CLIPTextModelConfig.from_dict(json.load(f)) + + # Make the clip text encoder + clip = CLIPTextModel(config) + + # Load the weights + ckpt_path = hf_hub_download(configs[name].repo_id, "text_encoder/model.safetensors") + weights = mx.load(ckpt_path) + weights = clip.sanitize(weights) + clip.load_weights(list(weights.items())) + + return clip + + +def load_t5(name: str): + # Load the config + config_path = hf_hub_download(configs[name].repo_id, "text_encoder_2/config.json") + with open(config_path) as f: + config = T5Config.from_dict(json.load(f)) + + # Make the T5 model + t5 = T5Encoder(config) + + # Load the weights + model_index = hf_hub_download( + configs[name].repo_id, "text_encoder_2/model.safetensors.index.json" + ) + weight_files = set() + with open(model_index) as f: + for _, w in json.load(f)["weight_map"].items(): + weight_files.add(w) + weights = {} + for w in weight_files: + w = f"text_encoder_2/{w}" + w = hf_hub_download(configs[name].repo_id, w) + weights.update(mx.load(w)) + weights = t5.sanitize(weights) + t5.load_weights(list(weights.items())) + + return t5 + + +def load_clip_tokenizer(name: str): + vocab_file = hf_hub_download(configs[name].repo_id, "tokenizer/vocab.json") + with open(vocab_file, encoding="utf-8") as f: + vocab = json.load(f) + + merges_file = hf_hub_download(configs[name].repo_id, "tokenizer/merges.txt") + with open(merges_file, encoding="utf-8") as f: + bpe_merges = f.read().strip().split("\n")[1 : 49152 - 256 - 2 + 1] + bpe_merges = [tuple(m.split()) for m in bpe_merges] + bpe_ranks = dict(map(reversed, enumerate(bpe_merges))) + + return CLIPTokenizer(bpe_ranks, vocab, max_length=77) + + +def load_t5_tokenizer(name: str, pad: bool = True): + model_file = hf_hub_download(configs[name].repo_id, "tokenizer_2/spiece.model") + return T5Tokenizer(model_file, 256 if "schnell" in name else 512) diff --git a/flux/requirements.txt b/flux/requirements.txt new file mode 100644 index 00000000..792205c9 --- /dev/null +++ b/flux/requirements.txt @@ -0,0 +1,7 @@ +mlx>=0.18.1 +huggingface-hub +regex +numpy +tqdm +Pillow +sentencepiece diff --git a/flux/static/dog-r4-g8-1200-512x1024.png b/flux/static/dog-r4-g8-1200-512x1024.png new file mode 100644 index 0000000000000000000000000000000000000000..7b1ca0e62ac424e0f91fc86feadf0fb47d1bad32 GIT binary patch literal 772523 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

q9Gz`OBl4 zGPmO6J&xS-(gu4_p2HIB(85?TdDvIpg)VBZ94YJS75D4oyS8wXDo#nTn^DCyVhgF}A;u(l$27saH19C{MfL#FsRc0qIF1W?(|51)JK zqFkAmy|+~P!ci@5xeyrrgg3f5cT91?y3naB_?))jkaEBE4U=p)#Eh!Fl3)l}0B)EL zr+Frdyj`}_*66Mt4wj$BFsIq>)%C^S6Txyhc(fEkq#zHF#^7Xc)gW8&xzE+-)W}?# z$1?&E=xG~R!I&KN)@=r*b~Sc-F8ZAFJVy@6iXNH=nVydRi~@a~ zc2;SrAb^^?(M!=P%woi}sVtAuQC*4%OaI0=?91I!{RPum?zD2WH5FI6h!@kUF^t&D zCdi9Hg~>L3cMVz-T$n^Zz=5(j6A$K)10f+V3_mGk*{ofHzD!^Ji6Ro&i*R|`CrtsE zJgbi6m%PsCYib0qn74xZ#>i}YDDGZpwm|g>fSn@gSI!?~Xi-68T#TV!+ zzl8sWsQ_C2<{j6AZ@@{vZU>>vanI0Ac+j%lT!@Ei<-6PLVHEbRShyhT0?<^4t});} zTC~V1_D;lmq7VlXb6T5$V2faBDI3z=kU6MW2u!;61Yx8Kz#abOMVt7DQ%4WGJDaT5 z!>4;u#>@Hzj1wA&8?uDsaC^R-U)mbe9jkI0mm_X?@0Z04Q}_S#%%HHr8E>hB(9_Ju z|76>I66XHd>iPs;cE>oEG|k=yZOZokv<&y{!EH#!j8i?GZ61^j@ESC{= zYR^cMX@?cx-T0uqw&+qqz7@((_@YZ4cV{`3hZs40jt}l}OY<_edAwjprYFu`3D@_4 zWVCDPa#5~&VI+(O4)Yr71Xi_KYB?Xm9)O^06#2%whP%NcVeU=b^USd@!*FvEQco?^ zmg?b|o_^ia<7tM{z^Js1joVVv1A4+1oyrg{rn#1?YcV#C?G`Cpqg?qu!$V!m=|GEj z>qSg-@l~t00o19r*WMCPdG~zB)C3$8zhNmOMDTY_*~+Dr2;6BEw%7``z8qz|0~jS{ zfW|w^6m$vI9cZM@E4D`vFgFjzF1^(9%xSB-3wM`&5E7p$=>^l|M4z*xloCUeJ-xXZ zfQcR=%mbA4VjAwWk<`kl8DMJ!6D{&!`dPP5&dRk^lM&v>{``DsZg}NND|Td1RXFmC z@Kjtp?41_LNYSd(F}>Pp5v8;esM>=xrW?Q`9?o#V<#c`6aXTS~1FIIOsklYv>t!;N zpQ$)yYg-dbJ3w$xX+UQWh~+q#BGyq*r$v|e!U4a?e84+T@JFE`^zt_X`^ z!Op_ir(q=BmcCs9_7HjPwQgH^YcX;Rfz1S?zlRKHV}NQv0uvY6=oqP7aV)l zQz*aNXuA%HOv zV~ql9ZrtM|jS1=gYUZ=kRCC;5lWp13d(YQCryu`@^ z^wB!06#6|mHQD65x#FEyXzYwp&l;uS$!IdoqUOXU*2 zlMwgm-rsx2$@k7inztSKp3j4++zKyMgT(T5Tk+uCjc`saqdYl=*Mdu#xnCyb$~;md zJ@o+9-KEY{OOA&0_nPicbB*){D4I^KFvEG);LeuS=a7K{5x8z8*Sme$h3 zlUsiaB;-wKuQWoJ1j1Bnw|#0oMK^ zDL&CS%Gi5>JkK*aYHuC$?oNlNNw(rVfae^7YW>=;mD_D)n3kj_pXYFNJf(GGCG7Sr zSGxYQ*52u^swsi$WZIIX=^lQy@F4_A$|lSH3qiB44hY0^p2^#9=IlIQ@#`hsUHhEUzl( z#yJIyAi39)%7Ow*ji<;v+p2j^5xF6Zx$h8Mdo>lWx*O2Ecdz@^X330m97?8%ppF#+ z?ktOqaLqI#qNB86EKU4vcDQpcLWSzQG_RSt!vO+ikL45PDb`+fYQ3}v){W_EM(nlw zxL4Bs_1*Nax|wsC5yXTs5cQzD>#4O@btO{NbF%l^oOg_IV-vBnitN%k*^^+*;+VXv zUfmgD7(JT7*F%fA4~69sF{4QYNfBVV_~6cc4i*n!`syOE=Rn34bF8isn<88!+B>FG z%2)*)v)9b|x&})TgUa!kqJ*5#&J`x1TH`D{z1#=Jk_FPHh&Ni3$jfpQ*rv;Kse2L8 zRUfYpDIe#gSO~9APNpQrp}b0bnY`vw7tqx&)wr9PxmT`?=NyF(>LrLs`7dB{yeYP) z8tPdYk!uARM4!q?fNSO;PI;F;JegwsiTr2Sci?J;n6dQYj#I;(B-(U$F={V$L}dxi zIXdtcmefBNkBpPVDq0#s!pw>X)hihpQs8ywGot2NP)&P)%M~tPhU63gGXMv6$xNM} znqA1j(lE`GIt)gQZ;mXl((UJ|YZcQmw097~B0+_yfT|G+aC8J? zyx&UFY#$n%Fo2JVuAs&!()qU7P4{>@xk7aDYClY~8|IAw*S<7wuA7unsyHWZ3>eA^ zoQ5$oKRODrUXTeH%oT`K~?X@LcWU}0KI zLBn3p$aY0cjlwyGrw3~3M!Hq8cqo!MU3WePOnwj??uI@4-_{g%1+>h_R>4wY2UF#~ z$!cg3p>e1BTI%ozZ+YpSdEN)O7#u0SkdH>+ibo)(dGDSX-(p0AD|F67sR&2j^upDy zWKD>7ty4EwW}RBoS|EWJWz>rJbFI7Z77D`c7 z3l@aPJ$WwxIv~Aqt=?Bs?;wIxRka0vPchOfIy8IFuQe*C3^O8jDh$};bFd{xP9mUe zaH(Vqg=$Vi`;-dGB4x-#EYHW)2G3+PL<_@Fpm6LB2_V^2&(;=1bQXyqKoPi~MHT zfMhiS*ty04gDfRU%5^bPY|lA+t@9iN6lV?`#hd=?=61)gFfqYgg$|=@cI&rWq7je+16OzBkKbSuZh);9l7YP*Uax|Kkrru`|ERGNVq+|$kdS|FZt_TI#MHEsySq4BY}_L5Dx zZH^gHT^VaC5*C4$|JG}&BEarKua&J4o}-~LMO8&+>+|!;ab~TFkMgqCkPc&xjFn4_ z^YgX~t&6AP8pvF@W?7F4a;<%#wzbwN%10^Ad%ZsJp5EfqrAt1tFs<}j%>2E?thK=^ z_M-Y6kO|42*6Bt`(D(IvsTXZ5&sBsFn~>-8bHBDXQH`p5A&|(flaa`L&a-k0DtPS` z#pn6?di@D2W^r5H$c zo{+3wV22B+&4|#TfQX5(F#t0#x(97Bu9W8d1UGaK zfcSpz0A0u{M2{ArIHUdeg{R&YV~$(lKXrl;qrYX5YB8a~n_I;6`dUj zT?9&YEFE=S4hi9KC)@=~iefj2{#sH^4XXUojsnw~k6&hKlR_?V<3bq)g(eZgeKbN| z&b3A}-uw5T4}d%Von(c4fu4b2(vlr=2Wq&2lh{zR6Uj_}rq2nfYj|mC=5wb8!dUTh z6l}B?k&%k^WgcO%l2Rrly_9O=qyGa}JwJ5$wJ9 z^E^9WYU<@M9h_%Ic8rbfM#kDHvWKkxsTS1!J|wikRJQ6|bj5|JUawb=7Pe;ZM0kJ< zXJR*|Dm3Pu@8jp*_j1-5bMUkQa`=+jSt;KwI3p4GB7!-|+d@dO5SIP0VKlB85qu~4 z(}C6ns`t-YvF#GNQ`kU4fUEn(V`15l?p0MIbvR}%0jjIBNLDcrO2%@k_YpG4`&3!x zM9GO#M7O}XHo5kCKA(gM_c_=v012GIf+Wt zQDe*{j%rhupiInopB_0cm6gt^{qYgX0#%Qg3QP7_aLrs)5of&t)Ft|wBqMUIlsJ8a zr@;bf{_^oH122`j&gqsexWfGrRUno`Ar$i5C{hb53bKZL+1%T=%j5~8O(FF5ipIxO zk=KWsp)M%V%93te_92^^Lrg!J%jjkL3daB4lwejFa{*+66?PB7#`7Z-2aCf8mmr+E zG7?U!%9Tw)9yvgZY#*mQs30J2_9+poc1LENnw5Ev)RAXWKSru4gwS|D1SSg|b3OXv z?Cw)KE6?+onl{s*a8)soP)-4wM@XQ#_u6}fv|`83O0H(loQl_LuhAWoCSA;Q1tA}*yf-baRp+dT z4A%23#B04!tj}V(4}J+nCc8=_0=HYcj4DT<>gcKV+P8EWBa%Cb7iS@tM3+bfoHRHD{fT0$I$_K}B5`uyYD24VEMO={FLlnwv{($+0v89BTevhE z03$c1dj}W;zki-CQ~JEU?ZDtTn)c<4G^}>t<{XZxlmywkWb`ia$$&`VIQI${Fp;8E zdIUA7nOU}Xj`?J%bK(Za!PT;Xx<_Hue3-sNK~Y$*d~dY>Y-?5SXwys#Sf3+RfSBo+ zZtm37RcefY{R+aWnzE5^rjX8s3u5Dgaf_3;HK=*Vm{?&<6Idmu7{I7zt(})ybfsr~ zCBtIXgXq1eDc?b+r%54Ro!o#5g;wUOb0RgKXd9PEte?{5SGOK(&{j`&n6vL z#DXP;p!}#@Je)yb<$@%bRJ3s?jB#0sG2AudVEjfvh`>zaf#d)pO?@tzVwi-p*L6$c zj$a1_cM2)91;)i6QIfDVla7#>dPLMYN>^?V{kvCtl%u*$Pq_uLn)#bruDPU2l{+*D zND{^oH$G9k)$XH`pgF5b{5nDEhMexvC|Ez+df2IhwN zmBu-Qrd{w40YW2wO~X>XLU-u~S-K!X$|d_|bs!@&&^cdDfiyKmI|@*eI699u@4*1p zTGk)yccpi|t;&Qn)4aQu?paQ$(`H83nYTaZN@-JtBs#_$?vPSM)1;*;#v6H4t(*J; z@#JW|z@>u(B8Nu+K%FA5*Vft|^YSK}FoM?=LqXmHH^SM88>afKn!`>U@d7g~GTH%rP5zq4oj5&22 z_j8)YVnF(q<)qVpYe385$ZU&=UP4)3`?c5JYw!JHXi7lYf;TxmwKv`&aAq2Lb;Anf zG2zAN0}v{FSWwb7l=4{0yu<21#Zl1fJGGwT=e6O`pd??_RM*qkDw8Eo91+e~Mx<6| zf*#844uPS>-9_`{nsnY1XvQMAq#qQ7Vfz$k48)UI-Nzn`x$pdiXds**!B%8=w zN%zTg)d^#ZcjJ>+`_UAKarLeNUw)EjiNu8?5E!ZeNvZqu9L%gCG3y$P&@^Zu0TurzOuWDLYp0!fOIR*rWjfOi zWJa!BK&;$jOJ%QOtyKTdok-x|8V-=Izg~i(GE)PB+`0-Yio(!5M=^p@jTZ@u-LzJl z1EgtDjkJ+G3Sn1@roquQc8J{MEy-GaKQ6+U%UMc9y!LwStxV!3$xh~HsGrh|Zt8`; zhH&b_tky6V`c}h5voFzn((!~_v((FL7t$2gE+45RR^{sDYQv!ZuDt^;B8@;3To}eRPy-`tTa1)XKdxCIraC~YbzS?8UwHWQuI^Ruh&b{ZBNxJ)|@lB8Fn+Z3f;6QYxjUK;kH&)N++H|&9i5V2R380&F;v<2~&>? z-%RuG+ecz>;)noNM7AX|Ta-RML|3PyHxFGGzL3LD(-BfcsQ4_ulrp*qXt15%wAx{4 zHzrC;@ZEu+Q%XQI|8}Eix^ibsLKtal;=Q6G#tg=8izH;m=jZc!?Tnzuq^xG|{Tf`K zWAs;z-%~UeGhs?rYoICQRTLqlbD;Oi$TzphEvjr56aHchN#hk0< z@`6s`EL=niQbKrSL<}+JYZj!mk`yT%Y1moV2gpWbonuSeRgCMcZ9gSz?ITO?v}3Oj54qzTBE6B2u`P_Vh#P|WfT&t)y_l5RwXGci`v5^> zfsojx!WTO$%?Zrhd&@;(yJV)&RHk^mVM^9B zVurO5nj0{5fJiQBXMFL!N?17nKL$Rx^fQ2c$3l(X0O%&7khU0`$4D!E1zon6h_ir4 zeN~+zX6W)r>1;%MC>h*RzI`AqPW8Mkf%Jg0K%)g;b2do2Oe6Pt0ZRIi0h-xe=s zRHWA0m=^qqnGUg1(gaHI@?F-(R>y))$TVS_#&c1C%XnA=4nFhnFy6Q+H=jYGQ zS>epc>eE%3YioU9uNP;_Wzixy-J%N!W0nyJO44y6h8Bz`nC$cOb7h|AaOTo|{`?F# zmxlz5D1rQ*PW)Ddp1~fg*N~ieZwHbM;+A6E4q9!?qf#Mcu%4uHg zFBIyGQ2Vy}lSsQ>-+y>;w<-^@3<0WtV_by4=lQJDkL#5Uf)-2ZCVTEMY=0ZEB^BBl zXIqxCPFnY{18!3Bwb796@)R4_7D^hlDfxW)R(C7r6_RW5B5sTozb) z6E<}B$H#|gxN9v$N^n{?Mb|^6%{Abh{gB7JXZ)wu*lFE{dWem{k=D_aZ&uG!x6lH@ zNxrHU??JhanrIwF=ZKo&lM#%X86D8P#z5T}eASVGcZV4QGgyE#yCTiW zk+au1F=#MxK&JbX00t6?G!USvaP8P3khxBs?M7$I?ej{2+uz7Zs2|Di%gZJ)L}b!D zj5&$eEjn4~8zZoCojOiJpc#DjFxgzQ@f zc+4b}oE1!@4uSq+;z zeFbJZUb9hd61`;{Th7~OFPw|y$oAXIN#J}Ea>v{Ut)9@(iIlr%jX(4`CoeQXhA~ zrNQ`oWgly1$m_`|tXz>(Xbe~5J3DkiW1^xrM7XxHS(kTc9@Ld+gga=bkc0Vk z54L_Q4#qt2x`K~4`>Ipe(VF}k84=I(e0+RsZA@gF-|J6y;D{B#b87GH{)cIn6Wh5y z6{1CtA{5owM%Q@dXWJbP(QM@&Do93$A+r_?p~&P}dt>S_nL#9*8VXL%Q6Kq}ZfQy? z+zb&&Fx@!a-D24V_u5@u?J-0gO$)^~V)N$SiulWu&VbPemyHr?pfi(kMJny5*z~Z= z@HS~mrh3;E8?2SlEGf&a078Yb8{YV1Jx3^54%WMSujQI8Kw_`aAnWexwP)}jm5Sh<;+&o`Dh{<)xAPymU41R#dUM{UfmQnZB|moR)iCY5m7vK_I~xq zqj3)O1l?)li8rvW7nGv^cKhYX=B_2gPDu1Q=W=ItuN~P=ui8yS)9uKqei*>B4mz9z zlX#w!L0!`wSJi>8P$(c$a0$03fg#P7E$g@T=8zM*YVTE$zSWMv^Z9&yd?*o=x9@7z zZ6zgZ%yMeuHJ z4tB4-Q|dHu1GLlPwYz)g9w+VED0Pu=;Wviso(51yzZva8Hc`(2ee&^cSKwNUam%mP zhh2N?R-ZcZLyDhtNwh3(gJzjr^%76OfKR3VsMAXk6-+saC50SrMeS|s=i^a;B@QSIf zDoL8=_J-v_Ik)ERH4O-<@ zlnxISTlB3n44;I~09Q;{6+n!buRCqJXOLmF`+$b5qzAXArgT?y2#50P-)c^XEQWJz zoo>ZgA>)oAuWMSxL}1YgX{%tEgfcaRg}0rR4-TZ?r$njfT1uKB+F@n_V!GB$0x=p2 z)vUwtymrasW&*;|srEjn1S#Rg^LoAB94e1*93s$kCh#&_Cb}&v)>+FkUH@I$+$M#Z zVTcGa8ZD69+)vNj6Cq{=O_$fhslO;QwD>jhPIBa5 z0=am{_e5LQE8e9-A@ti*S4qsN0S0l<)T7A2 zFmVS!yQw-1h`#Sp2$qr*vZ`AcPR?4ZhW)*_NGZM=p***{%FU7IIZ7wjj9+z2MOy`! z?Y?mR!?=UYhWIW<+$7}Tw8u%Y5NOxRv_?8l4w}K&-e-snd-U(mDYQoJ+D)o?{g!0K zA^WQ6KZng26J5>AZmO1cx2h+!S7};lvC7&v6>=XGjhD9HoDOP+eW8F$7+?K1!y@s9 z`T95a==*}mU|X5yoLV#TEWQw3+A6mWGJEQNwkJm1H7+fP9uBMLA`5!2X?=8o&$nGW z4wWee(Z=2ucVhEk*kv&E!0CL;%sM9{Vx=c*oyV#J9IY;$-UwKvh#+H4;G}adGTrs> z3Q&uajzSPdMj|~|H`T{G(Sl@Q_uijX=9CgphP?Lf=@i6BJraz_9m|Maw+hx8FdY$9 zo>tJ35e&qP5qP~`RV6GW%|B)^0JPR<(54I1hB;?BAPWSUhZ}BU$utWuA#cI!4qaa} z>QcLwD>8D9S40dcU&}iEaN-C}41p=A2HQ^2R1U|4AyrseST1aU^1+FobZh%i7eWe2*A0MYqLRYH>07lIejALoAA{kSKCkbobIk--9 zhuxTX2`6a4(&-8UmMplaL{0@s5eB{pEFeZv5ulYjMF_DVM(iz_FpxYHw%vvhfD0wc zJXz0SCS*Bx%eY(@j>g3;P`Z<@SrdW1v;V=yKA$Jn4mF$vNk$@8DCkrz8|Sm@cb*>~ z{WCv*J3s#V5odq?{_XYcw}1Qtbv}Q7*VYEH_P1vI;lKX;B!2njPrsdB{7cn2c)tDV zFQ4_D0Isbj5N|Qb zT#EXws$fid2q(IRoaXHCu7!9S=M=zO8B68K;s&@m>H>c7HR&VlMAMFG5=amAt0RCO z&~%W%a55k99t@J*ol9D!Mz926NU>zEgoU=I&faXkslZAe++QU#k!i356Z74&PiB=A zDwh)PC6darfRET0a{?zdb$c_Q zMR_oAXQZmTO6u5VU9L6LWLk+r)Cup6U3JtQycH8Hmg=< zp$-KUnCL3zz#xKw;k#xxje(8VxN!B_n;6*^ku=;`!MWEt*jg(?*lWuUh!jVRNUQz{ z%}J{&pa%poTQ58YQM0`;1p&9=av0J zL|#;Ykuy74xo-PR*huRA)@!lD{MY~jijVE`BG2=@DV@U!GV@|M!*LsMY(|J!(;vw_H{E;*j+cAKIA zwagk^y!&dW&wO$}?=J4!cA1PR}6fzLN+HSY1DytafYWpnZethly^XKR56_<#W zndkF~5x~{udY%WPD45o9VGvv85OJ{Fa%V`Z>GqV2c9KExwO-G8x=8d|yURHM(@~uY zxR#>na$LJ&4LDyTJ1wgvR-!;lVPHO;k(yQRyVP#_X}39p=CPyz0N7LkRPBrw2x<>M zQ+any;rLz>#gU4`Be6{LR6f?=Qfi>PDx%A&Q>-;u&Y(jIj1a>G47bVYF8BNpZe>ju zm}sHnR2U6qjXl-ZxDfjVT-VNfGv(1UXNYv0-_|XGONje>&M}v9xHqEWXy}eP{dCGF zT;@7vo!vUmTKf%1xVA`+tXTJvk75AS3!5f&uP`6@7mH`-|US_51ntue*PL zW%u*>+h6}}MLs`%kMsHb{E=8|tzP>y|Lxa*|MS26>-lxRe);pqw>-p-)Bkw({=m0i zKF@i5`=-*6o=UkH0u!M|gu-4qHrX^pC00uj9(YRkb< zv9->U3@*vi35j>DKxQ;Weo{FBaWv%EqYdv*+PNxuP|%4xPSuTxE+6ZDVIEgkmN((_ zFae4?H-ShfPp&F9>&jvp#}dt;Vp8;OXYXw8nYmL{B4|cLO8s5PH&X2)l46T7l^|Iv z$17uGckSHMDKrE*wZ{=;=i2gVCPvF5r6p)`ueeoT{of4ZTgPvwSjnCcABizom)m6FB%{t}biJnxNH`^2mSoB12`%Pj%w#*qIO>9OJrskOAEsjZv|VsRA$sCEC$+Q|iVdgp7`@qB^EK5B>bI9%TDQhjDA2#v6mnt7We992J5hd{A>q za)dtW8BJ+V$M}VT!37!HhRgg$osNjT++E_7QuY2OGO7) z(FMLtFJh)iT;08wH)O4?qq)K?qoK~j@x4-`i0-iPo-_uvEJB3W<^Vkv2UmfE(~)wm zp@NNEptyUU`}Eo`-R)CHqBJ4l2@-+V#}1$gmr_L%O9hYxfTy+l9Lu?^%s#>i!$cKj zpBzOy0ugK5P1G@Q!y)Kib9Zn(Q9Z{uCGstN7sHOBCuZvEK^0EdRFew`)1KvCBxs_s z-+QeH?$@4yk~ua6pKYM?^aPFFr%FyXBDznrU;Cv5Ob-S7U!xCduTt@ugKD{LT~g6b z`k-P;j;gM2j;V){Rwan|{QQ|K*Iv&#`oK&bO7#_o;)CTW7;<9(AQt%1d+seHTQo$1 z$LwK-R1{&Tp>6A3ULRB5=(jT_X#!l1!7&`>1Ywe=go*p^YGQS+>DMxR=0d9OHe~E- zdg;1zt<##hQ(=85rh^-oYmMPf<+)6HL#`z@j+mFa=RH}c%z;|bW!%%mA&w4}hRI2Q z;P!jEf?-KowX|klEgGedYpvJb6STQnEVZK{2?$oy_O&3KAOh~Mg@WH)&v}+;TDnv~ zIgLi!d8AmyGFAJv6*wA+ZcrI%Zncb)+d9C$4XYUP=oHLo(t%(XQ$3LgE?@k8N{WI02NL2T?-?5N58zl$|jP|8&z@siR z!>l6nP)rbM(OO9aBQsXa%ZjI)RrT7ha~`$gtgxE9kGk4jL{rX%N%AqNey&6b^mXvo z20l76_ewUG0#gRtm|2b}M@E21;HeIH*!dpPf0M7hg?DHNIq$dkR#{lcwgkfM2s5cs z8zmUNs8V$m;BnN0b+{onCUI5$P^>?K3A+QAV(!9f_?_lOcKN{11PSBrk~VfXr|=sB zRP@O@Dg_)`CH)pT<$VVDhiBIKBci)wrD=y6<~7>zJw@+~5MyST{9+|}Vt~oko`wN~ zdDG`ZlJu;VrsC$M$Vs7Rv|IT%e4#j;tm`UO>C zo1VXbIbbe~M?`u0c1-DJ<}`(Y20^e2`&LG*m?p#8duvLJ_}9v~G{@kUHJp*srj>@V zDrbJ83A$ws06ER(4d315AbU!;x81PBM4gzCTlYY!ZeP_L+n8&?Xt_Hg1m~>Gcm>Jl z^HE($&e|1Py256zjX`W3BHMqy36{PT0M4nc=O2q$GonEoq4EwIIYmoGteq`A!ep z3Aj(GPPJO2#1^LK$ho&*AP=F~d3J&#G`taB5L&aZ>JfM-X0jZY* zOGV+A!T{9=B&MvyHLN*4>HyFds5;Tr39i?wry@f{72m$_EC_K@LXBh%AkL|b2;@0u z?>#lKRh}-tp66(0LYkH5YCl#o6w_`dH&nP;?$~p_$)(G@_HHJupFno+z4Z_Z&c1#7 zc%HLg!k8^sw)YnHoYR9SPY>%Dr2L%RN#-zdILbCwlA&T&<(wG_%PICnd1WF);d~%7 zg+)0n3sWO*m8Yi*$2C7=f&PK2Biw&2q2sbOE26Ghj|ftIw}Fh;TF>*eRnJXSG;k<> zv0L41ttD&Xz%LA#&1Lo2os3X#ZVVd9wAc=Su1oU(n|m$K3qP*Q{@z}HI(z8#ZGDD+ndC`3=n(E6(&k|q|jd#_b}>UsEl z|JL90``>^3$N&DPKg9X*>yLl@y}tk4KtIptk00#w`{xr8pZW8tI(>fr_V-`D{oz0U z_V<7Ozx*#G{`$ZF{!jn%-#YS_KmX-NH-G-qFaPqtR6OhT=fbz=^k8%d>t?9*Axy3{ zcp-8TU~@&VOIYce(N|W_7+mefnwN-w$t}0sjR4UrU-Ao0uA!Nz6|CPU5YRpv4p-~B zXmxK@D~uvu=9M|Qq|z!r=J<&{k%-J#8CBuo#`mr@h4^+eknU7d5l@xUG!1g7eG)!L zn4bxuYik7DjCBeDu94eoT^oaWcahV40~tRjGjN6RBDGORj50?#;{GcC%=wqOGJ*np zgVO1`9%OX3hl;BlC#EkmVuuRN69^97PQiD~gRRKaos#2Iz!qbxb%}KFaK{zxRdeZJ z8D+OKjwTjg)E_)N{XF&g3AI9wRg3JmqIs2p$&GtEz5WS0~MJc?)haT8SR- zaH^{6wYL2*Q)YD*}nD4RjgrI_!Zh!LF*6-C+_Ykv3L}^jtv{eUx&w) zAs|Qk(%~cEAOmt2-aH9IZ{Hown?BQWK0~92idQcKqtWB z;9IWBCcGm$_HSrhtdKggq9wh!s%a>0Zqio(oeD70{X9 z+|67#=ivy^Dz(9UY<=sY$>=YdB53f4b;0trtG7H7MKqKcYnWR4~WyJ#M<2+NPC zx35uxVREffO-=ElW{9e0N95dj89U%zF{P@4{ieqVq*tw9)?`hkM(-4ZjtFko2MOYJ zjw>U}s8bf$0m7{$?um8-*6`zv1@aBQDOOH9t=p&|*h2x9e>_)mut}*nQ=FU(blwUV z5(_5EE0Z|S8Ein@(13dZN0}P}+dRekRLOgEGc+Gtz>LLAnqaI*1uKzXhD0dCiIoWC zRf!T})N_~=PpZ*G*FZB66b{;m)R^qnwU-c3(Nc*`f2rxS>G4b|8An8@rgn_#nZ3PM zYNAGUtLOoB7WQmUF8r|R=O zpPv;VfBgE7U;gyxfBgIZh|izvlBVBfooQo=ox<~1=veXYsQX?}M*{q)Nv3tK1P!N&kjDb^{hckBZ1cpUG z<8cBFY;5VsAf_a(v;=^G2YZ-M)x+PW+2IS!yV)6(}P}ywMSF6N5)8k)4LQB$w z+C9;#9FBA&Mvl{%y2xYR#|E;`g?=sgNNX2@GG9%_CAks?dLfvM1MWI|)Rp90xfK>e zWY;0x665d}P8inSYByA+Uh$Kk&{s8v*gutUr#hJS5|Ii$HJl^Zs)=4}E#a?CS1q(d z=3)t7S7&4xYnQcTQ?CDdWW{xTKwdb&uMv?wfNn158!C?oSGJ33uDz-?35QUxjt_sH z=aA!|Bdxh=h*wXL>>joW9C4UJS($=JQ}Bwy^gCpCC=fsk@wMi-W8z#Ioo3mI;a}+r zxMp#cSEP393>LxIZD*m+Fh+GMC1~{FTG#UFUkfNS)0bJGGLkG)q4tm;e zq;WEJBKk@Wln6j{$D)NX17T4wL?*h_vR^Vv78*lLt#9{^q5>6+u~a8u{Xx-B34d8$oD<|i$ z7a8)glKA%60F8QqO1if zUhs(V-oplMR$nPSDD6~u(bJtQiln>~<>4|@S|%a;1@jR{$U?u1P-$%x8rxlTGz<}g z+W`pA;}DkI&4}D9$x^w4RheWTKEL<(zy0|8|M*LNU!OmI`|E#vtPgx1_WAAmXIFim z(^cPp`~9`|dcFSkAOHTBfB7#ze*6}{^y>VNfBT>Rx7TZR{r2zw^N;`Z=imO;fBMT` z`uXwq-+$njzu>9-=l|>a(|KqJ}bj^X5l&g}rugEfrmmB)KY_92~1TRYDZqTR*YACq%rzaW3$)D&iv znCL(xHDz6G3p!cCRP=kS84G+Qfn%f7>F-8O^IN#C@GK_@QifY(hGb;_FE9G>*>F9j=BC3vJyV#C|+M^Y6 zo&ay7m)Wd2oT8G$VRenLgNe&ja4ariNF$+dPHyu%>M;=6rm7|6^4eBhh|HxNM`?z8 z-3)LkdTUVePy`fwE8cNOkhO8&=2Ymo=riy;1l*s{K_Edz9%e#DXcQ4uyc$$ER(R_{ zvQAA1j&4ScUOi$Wl{dLU-X-2@RJm46rb8OEQ(|3nR(ow<*tR?bp)tXo7fvMXGi7IT$A~Wx}T(?2@sa&Q9 z5fqZCctbR#cXiS%1`N_c@xjvhn?V6Pp`~Wi(>GJfm|jd`XiIMPB?IxCGxf2x)^gR$ zfe1398U#3XZ2APSRf^0z_I?bAGUiGCHG~d8WQ;n#Yj`3UczYq$9rvpMv3JtwQ@eah zRG&!whXT`9jW!ZEeG8iqc`s7nj?$90co9O<)qzN?wYpp&mFfT|G#XfQ1#mo**dynv z{ZIB12xgWbHIokLE~Aj{?NP~SF$wAj^=zff55}7^npdDH+=d6WYEbRSuh*8Fu2Uuw zuci84i}!EM_h@KLimDr$$`Rvcr5|&H>)Xdiv!8QbduJrlNPwIKHJv*h|LNQ*+_}JQPo}{kbws8J zv*BN35>-AHjZbJ(mlWmv$(BFDTpt4H)7z~2q`YB}T`Q`eU-SE4`ycgQ^ZNL@ll=))e?I;3=XkA%wZP6K3r%E7_cj8jTCKg!Rv0G-!$z)nGXaQhT6tA$ zU3wxi$pPD#nddxEu}%j}(lj%Q0q-GV>ah;N31b{|F)aj+z)lG>!>H}FXpdZ5G)$1N zqsI7@jBeZteoGz!5Kr5?))3cA23@6qwWk8uGJDHRiS9PPzi;Iy_eRx(c_wG$i$s;$ zgH|q(N-Ja{oa@3|gf$bK5;NuFt$F2j!*s7Ic#5>wuM)r$7+3+`&Os>2T&qv9iG`Sn zFiT}pky}ZO5L2!t3zpV9n-FPcjA%+%z*HIKx*=B2Y3;q-e|#Y=To)UmJrqgTo<@|y z>IRRz`$`3AIVm^Bwo@Bw?aE<0Olp8?b@Z~;nK}`G5F&Nv?c4&f1SSd!K|@iS)_BUo znr#x^4m90+r!+w!j%R7 zCEA8~qztPxMtOxnbQ@a@i`9LkZWrIpwT2|jT^jwA6HKU0FxNWu zsKBiPh1qNSr2)yVJ(mC1DR)?`IYZ(iT&kT0yvZ{$cbeHy&B(QvX@Nz}(@o6}Lqt_A z?51Y6906Mbnt?`O?yS&CJXc;I5-vV8#AJn;2Bw{;>z0?o7#{btrhzFV^6IkJ zW02NSYe%?i!sCqCAFmHVR;n3g%%B5prz^W*yhp6@@-_aFP?m;U_z@%;Gx*MI!^ zfBEspfBj$U@5C?v^zny3e7gL#SG-oHhAJq()nq+R|F{Bk_&huR$<#YmTaDNKFp2;o zv(6C?ExUQO(iCCnfH{^B67SzmPqr)khMbQRt(b&!0#Q=k6k_rvHOM)PKQWYlB0Znn z_*v)I3pLP_Gl$d+UErK&=W_q(>B$~@<%*?l5KaVRo8pgAOdpP)g#98=tnS_)`#cXO zKLH(Rsf1>Ii8b~DC0 zU!}>FQb01LSb<-?1s@TTRZ)WBMF4;h*X7MUP)r$)*I?FiZb5#6v3U!0&c2plMMv8j zo-i!3_f_pXztiNUCV1*Bw^B&l+WNd~usu{rAs`)gz4dR@vZPBb*=7aS>#cXrWkGka zH3u*<-5U|s$g=&9FU1+LSYnCQX50r#^Qz{^M6Q5J%6`tE$>Hp(*sGf?+pF>4BsK-h=mBCaHRjfyXtq*VJ#bFLYnU%_d!RjV zMrjb+5src(@7EA)AdfPVJEUbgbr^>JqO2p_xtrnEFJEc^9$~Jq6~u~cxUiY& zi@h|=3lPrYD?Bwlf}W%~{C7bq5>W_Pgs|$kB)go4x5g?p%xiW24kht*iY?ndl^+5T zU3CVb+qp>5|KA%VB6J{BS`1+~K#XXs;(zhl{ihKuQ+G0Mv%Bns196u60npVc0jpCk z?b8!oRxY&#MmpJ6x}|FfHAQIr5ku2+a|?|XCDv^%k-3qQUS+y)B;*97d*boN^2Zdo z79q%gg{UIzZq2rEzs-o16&r!kn-QwrHCkI%PayzR14J-zo~G-0Z6lQLiMhAdVT~d} z8qy~tAblQzI1%yr{P@U-NVS`;%v?*AY1eQQlJh59v_mHl!>^9vuxKgt%Nmk2J5jx$ zK}vsC&)n6yx5aXddPijJ2&|=vQCK@qm7Xjt(W5@sa}A1azO?zD=d9s{e|~UST#3*V))S* z)5JP1vVkxqqN)WwL|#iIzNin7YAq%5Ok_qdBt-M5{Z?4$jl7i!L|`E~gC|_dcTLmE zP(p4JS$zc8bjx0EK$UJZJ-b_#cCsZf4`N?+;k(H2UDXONKs_G{!Og6g*3z~2bIwY0 zH*;kd+oU?@GrAEybZaeNs_ge!<|3nK4d6sbIKnK#?`oVvtIyuSVMh1kDV*R{Z_!cGl|yxsd4R z2m15%r*FUh^&h|0`L^)m-}kqF{l7>5xBc-8Vk_xi`z6@BG@}%vYx_KlEO^clb4+yJ z$f!EXrc}O@FJF6gvFnK&#mkxrAp-{XwmFb%S!*j=YP0+i%X0%D)7!mPo+_}{-h(e% zRweL8Utmvq>y{Dbd@DznD1xzY$%y65a!-laS~J9nsIaWuIU`x^gx1PJ@3k{CtX(nV zFAxYwf!QpJz~1ZVc=zg;rHfd9iCb@4S8@ri=t_3mD2y0*|ef`R4u zL2;AX3WKt2`usW#FjqcLudR8M*b^zG*r8xrkwebQlfTMD*YqWoIu9AtF1J1pjXh*VWA<`}a(Mip1f9up8Sn6WYR zXo33bD^tollu=QAZc9jJZr#5Vr#W{9D2 zovFnB6_U$*sjF>xEM2-OUkZBilHT9ZSO+ZosMIJDsyo*ewlpW<!VTZ3fwfq<(i)mNupE|xRv_Q>M9XpP{i-f_sHYPt zPAH+EwRJC{|DIT#3apx+g+YcV%Lz+Od1Xl%k| zGltx73s%WUHVZ=I+Z~i6(k#}Nfy~TzQL@?efN_O%_14h!T=&kNUIj-g5?wt!^8AXs zl!krrLktV#xh{-QgHLEj6l4gCni5k-aG5F!G?V;8;`xvNBYyqs=dXVY^ykl?6-Z_` zpXZ!)R&G}BoqziCpPtX>)Y-4y{d}xf<>%-3-}kqV^Lg0$$3OhS?5w&v{&I zk)H+N8RipO*|^ZYU!eBTS14LPcpLXHMVJX~J3`0}*IuueN8XarZX65*qNb6ZoaKFU zhE)XuuKr-Ck8r3;Bq!5@*_?)s5;sq-YGIZH>$JF1u9cBpEuvapz>)($I|{k;q#bF?nK`napnyhB-4AA5$FON-uS)hxke2XT(^4W- zNV$=gk_uf(pZN&c9DD3J+%WmXtgX-x2@MGAas==A02b{-gtc78i)f*|8lFttqJ>3r zC{H$49t6?QN8Bk5a8RdEg=D%&JD7`)sOBY^M}&5RG0Cv!(^aGo1yE=}t=)z5#B!P$1n9O(&WD-HZFE%4XCNVuR2F=chDltGjDu<-|C<1pE6oONes@w=+ zbVj#Qog{`o!pBBguFO7o)%fSX!<*3EVdl)CN0;7A1o!NDELZge0{RT;xpuQ={+%0? z+9fm&(TgA$cRGPJ;Dw=xy!YB4Yx_3sm3`@pW$ADLn6@b8$r#|Eo9Q6sOJ)^5M`AU^ z%;r;0kD?RUq6^2uDZddxt{EG}sYMHg zvhxEd$+W33<|(U|fjsA!S_}=d)BNBq*-%8MI?ZAm?_s`!mMq#W2P*RzA zxRM?;TP^wc}~^y=YRUs??1lhUVr%WAM<0sJ{IdlJ%9e?4?EVc|M=Tl`Sbhl9}6Gx z*}wlAe*2G~|MvgE-~UJd{onZOU;FpJ$N9uL08iIip*z2JR@Yqtk!Li^?Do5>2)te^ zGWK4t*GnV^#lEs%G9m*1Tl1Kdi7mAlspV`{)v3ZH+jOZjBVz3>_pd4{C+rmZl5aLw zyfe(eBzTT{6g#e+2=ATBS)9tf42w|g{c=fH$_6AYNardrn}q;U(&Oq=N`Oxt3pM1M zW#Lq4q)X0u00crkXS&o7DOFAgOsW~|Wrzo%%J|B>eFYlv16raKlO?vZ{gTs>l~C$% z&e6SBz86lk9G5x(uyuXsFljEn89@Pz;63%Rtpl+=EEbk%xBkFA-lI}N_!@hNa!1>tg2N^^!-dl%h; z>~QABHSfg5wod!0?T~j`nL*Dr8%+wFbHomfp@NA#;L9@*qs{5eJd!7U&cV!L7$+N- zA7W9-N&Mu>rzp-b5t+m6nm{e4=H*{Qf{(El1dw}Ok0umqyo4z+0Vo|xVRdAeL$@Fj zxz~OddC2n|9l01*kKeUS^0 zJt9RBsc~8Y*1{G0$zJTxm5J0O`C8}9c%EnMxpE#?pM2AyXu-o4gkq2*bftdZDjjYe zzqj>Ls`I|W&Unc)`b$T7y!G*$W14eUgpz8xVL z)64WocK!L@>-l_K5O=7{$XGM5V@hW2E(b|WWJD&waw^{u$jG#$ohxD>G{d}3$z#P7 z0Ow6fa>t&sQC#05&#gCYTA^0!MaR!tCb42xuB<+6R38%20OPHL6hk0GTvO;$OgaMS zkxJLqnf5BdBlG=^NThv~nGGKEP&0Dji+WE?)jlKl+FWLvU;+u*sp{rs z9t#Y%y%O#?NY&J)RowQtq6?fv@ZDj9<~h&v5wV`{|KI=Z|NWQeSN`~3pFf^@Vxzl3 z)N{Ul{POMFx1XOsetdra<@N3J`2>3J`2PD3#Zv3Fa_7(I^Pm6AKmGRWZ@>Tk>&_Jf z&-2fJ`r~K+4Au%f-+yc37gm1s&%Zu@`s1J0^RsyRPyf0SjDCIm;gITk;T*z=kI)cK zY)+cCpbf8v3&MgWhULYnoV|T%vqP6xGM7A6d&(o)h3?h~(V653$xwYYuh$wh?yJSv z?z5NSkj;TvLZpss&sa(sF9C$`aFe&4w`EF*#tH`5hUnzc1A<9j>qU`JgWa->ib!$G zM6!%kT~(6kRmyG84K+xa6qnQ1tatB&`2TbDXG@YKOLid0i>R8pM?~IRg$BB5-sb;* z2}1({8jad+E)njws0cI9!#S+xm;_OwD$~QmOjQ=}P$z{_Jh1v%A2!a0> zRIC~4j_56os45~}I6|4T)XtFXhYumfIXaz@*fsbYo zMW(-6GEvbUfz<2?<>0C+Vx?lSCb%x;B-A=_{L>Br_9<6Dl+ho9caZe*s!rt0%9p0V zUUPc!vJ&zhRq`$EX7-Lx5n-8&1_u8Z+hIU019QYBI+`4`0GcZFt8|p22C7o1^Q)Z{ zQdP2YnKH&un}Jkz!erX;D$P;)N8<%zO2%L{4uWO|EqYd2OQD>}ho>_{w6GrReA>`Q zsGEta6v`@GivS?1${mClZ+TQN{lw~Ya=xbZ0cvRqqWXmaT>_)R6TanSMV-azhPOIL z;B~~4vLq{F;X&k^CYECQFffj@{rEcLCEHeR$n}TTsKNWH4v&GLKvYWRF~=BCrP9uk z>BAwc2MlK&XHrmWK$R*cR-)p{P9DX&nTI6Cwy3H&#ib(B0t$vELF}9v95$7)(1$13 zLW2t#7TAg1M0$&eun2|F16qG+Lca0qZQCLuV&Np=b&P{=3|K;0iFPBR40RI5@Z-6d zJ3kcQNoVJDJSP^7bRwH}pO)E}8#|>0+#5le6t;0zN4=gkM!+`CbcJ5xtZuZUGqG;Q za8;A8V9>qUS@$Qa1OTA$j{}|us4NYh^?IuF20H&G`SErsfz~iCO>YKSvaKgoW$Kj; zLMS_6Y(>0cG(6q*i1q*t98rQ9BI=d0j*t{+b#kFB_n6tBCQK)lJrx)=7CDE0TmZ(Z zBpQO?)K6E^Bb5+(L@ebwNrmgFTY#Wjo15h6!p5C$KP0*+hsJB34P7iw{#P*fXjsBOG@gGO)5NvyP`cZX@oWfBx(9 z{!=6>W33RkBaW!NKW~w1+qaL8U(e&VxmUzx49%EBCkR@1p0x6 z3XD3F?7|GvPi5(ow5f^8iq$tWAA*QblcVH7fL|mAu(abyO1RBcMae?oU@sKfK;@Ob z)vOARCG0+p6SCP0p7^DdV9W`IuVqjf4?@hkBscneG$OQWEntlqu@rN@rTPJqqZgK< zV8fFapP8scEPUpyM9_z-n!Bp5SlU-18V^)9c+6+phIbdiVbXe1Zz7eo+D+%3A&WEr ztoBd?aed$}a8*N$GHM;|in(4ZGrL6QbF0b}E&Q;u(I^U4Uk{l*xtzB7;baEgN%*kp z6_Hvvqa2Cj8nKY>AoT39TsxO8BBF(Pj^e|hy#ejeb!}iFCsj8BWQqr7{lCy%)E=hFv22^2`K5DySv%yeTp5BT~Np=?ijAh~X zpdlyn*adWPbhJ4G=JDMjw?O@Xe{e#UlnB>SFEgQ(APeDj_||3)xj3uN)2U_jJRra1 zHET$Xr4jJH$V}!#Vp-xlX}_ULibqgs6w1ZsT7_Cfes`znE|J-V`k`BhGVi7t)H;J? zOx2D#(O*TRo6or_3v~KgQ3)3nS~FKwAH#e|tDWe#jsN$0dF zzer}Rxv&UDv>7L!nOPZYv8_cz)k4#SF%&>*t--8J5F4Uef>L1sX2KIiWKxdd8P&W> z;eW7RFJ63Ag>{BX52b(hz@}=D1~3o8Rz}WP@DrA42N~&Qv63bTMh^1Kp1UB zNois9l9{@wl!#ixZ^+X$OaVl_pe=L@1sXSADM{QLs%ngRDkNZ(R42m-4L9b@$mixm zI<%7M6Sm}4Ku6?^GNjlwtEBUL@x0NCl&MGLfT~3JK)in^RFNpkyNQ>G7{f)y6`McD z%j{yP=S&Sjfm}i&(jje19dE~1HT8^o#KeFJv08{c8FMMdiL4c4&`hjqXr7fb(h*rO zhQr{PrsA&i*DrZ|jGR%=l)UVIo6ossWvGaaaonFD&yTma>$Z=Gm~)A3+wkk#RZSm{ zyO>7A+vWQC*Dv}0cG)(mj9AypHjhU{x#^1M*mlu;KA$DxlJovY-FB-rk7r!}X~60* z-uH3E`8K+1$1b%j5oB@;MnzVZ_t3YfzTAaSmM{SgvJ%E9sP?3Hm9zDitfDR!%{+@z z>qp)7F1yz3THy>FI1hIo&fv1T$lyzC7>^4!`^A!}KnY^3X}U5d0Ko!>GheDPRH08j zVNp99-R?(uA7IKv6%bz;z5(e$#aSX_46Kt%I>nWjG5;b}F+*B^09#G^S0z?dw@E%+ zyPj2wOhgJex|%8}e<4AE@eA2%9Kj)?k|j>ylWN)S%k;G=vV1e34+fyF?~};L>X;n^ zgNM;trFCY4iHK+)wkHCi=c6wvVHPv`uH>9PIT7Nj^TakaE)0A&9J6{(W57iSX821g zeDocuU*I}x7D6yq%seZp#tcnqomDd0LG{&_N-|ft7D}ymifR&$P_m%&{-nV zZW%)`2q_L^R6vR#UGBSz;P-&oR8y%!;+B~zL=j?8$3-j^KaXqR!SY4Rj`i*Cn8J?z zOMykPQ0)hs?!(XfpGa`aM3;Z(>- z^DY%iP7#{G@Yv4{^Ce44)j>j*A^kkg`?N|rF^NGJXxWouV|XeO6hYTQe7v~b6sWVi zs12Ni7-4IIM~a(0t73%>)dv16^sLVNJ`__es%v;MWQJibggH;|*HrJSE)b0lWAX+k zxtdvqNWGw5aS*=Ylf-V-qN>xxP83fmM+{bX?I)oY1=N%E^6PVt6$@?|F>T+=HU>8S z*|xQ!q_2KGBm5H*c{Jx3dWFggrY6LLa`C>79z0uREb-ar@m6423ycOWzKTF$6T&Dq zVz`{4C!24~d!meu`tv%OoPKM9EKP$Aw;Yg%vNi9nEV;9w>cm(9Gc}FK`8=95FbXvE zY~HFWwXZqIZK{$vwoO6f6oTNVibfwi8z?Cj4*MSMfx#@p(QPy6tqP7SVgnXymE_p= zwN}Hxq;I7CG$n;j2rI_avT{3vZlruTuv;~)1;)7bHi(grtU#J% z-v=5qP?u=$&9nEZEV0(`sfsGH1>ER)+s4R9(ZVF{SYZR1)?jyYY{XE(av)JVJBY2a z%8D)mGf?apHJ~ku;H9ew>I%G2gwG^nbgAx8(YEx!+5$6!2xAaaMYnxq1d+4m1WIb# zM~~+~(qGkD!z%7~k7var9v|>{-abFSzrBx==i|AKs4Nq`Ufx7x9>@0|-?x1{=W%;J z|Ms`Pnc3L(kb2x7fBW(Mx_zH9nJAtFEiQ)ZaFm-l6tppng%D>noFtGCgv%kA{;lt zN>Q{V0VJ!iX~yEgr@>hn(Wi__F|xNo=Cp z5T=?)2_o2=z*Mk{W4MaUIZrPg?n2fR6o^VE$cvg)UoqPJHlC!EbiiT(TEdg1!PhC? z#`;HPo1KwQ@d%n&VWNvL52N#2I$Odx#TN0PB@L;7FQSea&%%vHRVI8h-T+@p$jn$4 zqj&^9=F;r`6XOnw%LM-pns|_7X!a9%0GSy_pwxADRf|sZF)T&?v|-*t+Oqc9(1T8mAPiC zYeW=fB8q0T3E(Np5cq?86Su6Y^L_F7&25ax&@6<@4YxA}uo`$hr|AKVhsXzSw(Cd% zwkG2*Mst#k#gJL^1s%t0Y-h zkFAuB zMUi57V`<4S6@<>ALxfXA|i|P;ZDoH zyqZH7Gg`E& zR+&B+4!jQ^u<0 z=9M|)OdLxJ=Dq;=_>K(;5p2sS^)GVgs;+jvkP6Q5TM-qeR6JLfGNv3$9b;Ku z@D)}Mu4>#IA`%OmCr!PibtbRp!7jAX!BUZ^*xXA?*R=JB&p-3yFPX=2{~THG*Y}DT zZnJ71W3GUJsWQy$cs{@V_#x)^+x>pOZ~Mj6Dvo_0k?U_i{(isT<~qz(T_x)Ayl-QZ z%D3yT?vW8=tYhxGMrOqE{P}0uUdOzAzdl9E&ECx~*A$5~Mfsv?qE=>VViL`_fL6|g zG!9KE;2FFilVao!jx8w_6E}+tRYYBt`^b#5sRnG&*rZC)r-*yvZ0xm}w^KF5N)*>% zJB=}Kg@CgU2lIyGSgEc-!FU!}ClRb2E+HK<+w7m0uzUxCQ#9sEW@2K1HL?yM1RA)a zNJ_~*(sc=V7<2tfoyk9EDbpGu22iM4#9El*>AqsR4N@4=JZ2L9WL5!;fGp*^s>dV~ zWw$KkRYWj96<`=0gpjHd`BGE>2v?!ggN%RGsRHL=(3Jys6~o-|w_xA4WB5cffi|jG zo?D1~nrg|C1fnm*5N1IaAHPHtlMvDscN~uS~cd zAausJM*?+A2Gle=s{H@2j?aF8T+>-fz2wNYd>j5tRT zCeT|JC;N&>&k1c9w0A_mF*1iN8>$dC(|%k~XL9m^*5N{c8e=VY8^beCD2pEh|6uwM z4vi?&1zzCAS?Fwwf!mrF89<;W?k}P^~HiS70kZ##w>je{$ET zTHTS{P`$95s#E?uNP+35#2l{T))x~)8;*RXlXEdUCG0ZBSh1qVO`E+&!~xJEok|&~ zW`?9z^hh1Ylt;sjMLJA76s)PJLOi^w20OMSEWw8UG&w2HDdBez>1hZ>T8)4f2w#YZ zatx!tJqxfj8a}*coJx4Lktgky!aymPs+fzE8CoD8837ZpD9#+(q!cggb%qvWd@2p@ zf{<>L3{_gD0BSYmz;h+q0V({FDs%I}uWd$$uC};HSY=BJAva;U%>@yO--F}J(B}Z* zGc(|_hzv5(ROE~KiqJl+@zAljB4YT+N*qNI)Jv?0%xqbC`B}b*W=4?Ym+FxgSOeNK zQ?=!x{N-rFN~LthVQc~i+TYVQbEPA7i{d9>=d=pWnWH`%nMn+wJ!G>(|HI+y4FAH*|{s_~Z9|+b-A3 z<9>Iu_qTUZJ&xzTZ&HQg_S>-7aUAcLw|V=#yo;v({Gb2r+y7ZJ&FvDVdf83K zoZ(|rfv7-(_Z|1Yhzi(eEiJb49pGkXDVUwhDaG7arIjCQ6Po$@96t%@7HIMer8Eeo z1+i0Uh_UHx?I8?J%JnUUUM5}x`^L6G!jV$S_Dt1amv3R>7gx7Ps+Ri*`tmvW2Ag{= z!Wg}&+kpk%&m6roFRB)*VBcjc{c9La)XN0A8n~=0jnX4vnAiGo4Sh9lVKN;wkis> zzgC%YA|OU&0%OtfO3s2fv9N>^UW%8pM8jL>M9 zQ$+|h&@*A{lcuhw=u{xkhlCeagtPBe#w}=dfQgx5!~o>@klkY}AnFh4O+ktnlbm6g z4*}hZ6G)t!Xxlcd6W9!-`-RM4SO-jHM(-rFI$$#8yfEsldz6Q8N+f4H;<-_?TcpHM zh-~}FsK{7z87H+hM}h59kURGK!YHePFq+E;4Wufxd<}wTs(WQ=`*>0R83GVNNPuSK z%uMFqnwkz&2(OWFs@n2>*0#~ptksQ>j7Ve2*+4#tNu4}A%lPW5j~qTUMn-B+9;mFS0Ih*`2yC$r){em4 zmn@tW@N=UWR?)TcMRqBw2nFMcKVCSQ$?JN98VWTy&183|9nPHU<}1d0BU3kH8@pk(J76{QYbANW_)9Z>>EE1D{ZY@A9EWI+<@-iVqC zHcZAc_gM6kK`!3}MUKrY&;CTPcFNiafvXr90z4LwMxwa$2rMPbJ5*`bhGs(<$5)UZWSB6S#W3vlQa4{d3>-+8JAK!lbeLg>LqWeD|DVfiI-v9YO#dx!^ zCzi09a6&58EVE~dP^!X##OJxki6W3cE1Dl% zvE)LfSlD$wH+hZWQq&!xHZ?tmC5!evTFjjhJv$a*i7!@tB`A;DPW#ES-jghdU+5$L z<^L9P$|5pTJM$o0-$lKUcBGrUIw;_pxE-i=HO+viP*gd$H8Vxxr40dqb2dqQ>1-6M zAYAsfR`3aziGIn!tS=P;FC$geo>2jE*?5szRSthy74g@;knB=8gY{ehg@;d42ya#> zV3_8vCQ^-zCS`2eWrwSWhKZHrdCp4_g2iiEF&R~5L?_Ik@TMg=1E`vcd0}=@~T>+C`@J5Vh`WSsFK5SXmh6$U?cQfXk4| zQ$@tUnP?@5647(kp{VjY==D>4eOWEAKFtgqo`Cih)9y1VkT%t-P_u{yHWG++WlD(; zmlWnssECkkS7y$zNyh<+kSM4IvWp==T8wAq_V?FgWRH&Ic#pLb2|`>R3<>Qru`v!O zhn95oGTaGa?aE%X6ibLL4pEf^Z`sCr9iEv*CrFYLc2m(g7Y-F^k@~ic>d_E*St4TA z6p(4KwxPoie=&|7)6)cuZ^5;lc|xmQC{bo}9_Gs56QOOX+O~mXhP^IoXwyE)W37Ni z(}bhJz{qBnsrg2MbVPzJDTMGN2GHBcfg8lmpsK1)Y*PrZRkceLZXYG^jVpY0q;}s-VQP+69qS!ySUX%o-e#p{inLt0B3nV#usH z9<;f~iepVoB;+rLb}0dX6j&noXmB7(`>V&cjXpgsBTTKNSAS+^!7-dP7Vt@q$5x=!2())Gop^j^g5+Y9G8s@xI0B^bZUn@I(QJ460k9)DKSoW(AgPAHUEsq| z10k*|b1nQ%cg1z=9AQ{)27DY+0ac`kcuHz2)P!dNfj%Q5AhKX5Q%SzUv_#QmLO#e5 z6$Pe`8(z+MVC4e=;e*X)s+$iC#X*2wD3Y_ZvWAa5J=xU&m|8J^0s`OGGbry8G(AwP zW>0$^K3Jm=hl7dAfF;}TWSk;^Lo>4_G0aihGAx|KgQT)nCJtqDm(n$tj{%$~Vi~fb zBryP`Dl{XGXWf3f9M9Wl&13WJ_IcZeYn7X8Su&eqecm5P#TaAT_Wgdly}iBd!{5HW zef;_y!?w-$%jMUvpP#q;-~aZTm^~l&AHRK{$MZ3tW;$#<=gKO>79E-b9ni76?v}UT z|Cd>g%(~xAE8qU6I!Z_AfDWdQTh&&}h;VS&u(5y*_z>z(sKNweo-0JC5Rjq}mU79p zOszMyAfrtc4))j79z}4qU>+tpD^-mQSFaQwRvkkG&;f@yOn5@iz81w4*j8kd^vrOq zj?3XjtJn2X1y!|~A^;K|Eq20Z=4=&%cPKo}E?!^m1t~F8Ep)jkbTuj|)UAe}MSOTh z`WR?jjm;;QrI=bwXxB>ni%IlR5*WT0|LB+Pi=D>Y&lMZMbnxAP=(k%JpiMu{iWma#!AydSD)O2&-hc?aXCPZnAsiri5rJ}5 zTZ|EFx%tW*&^xr~3z`!^p=ynVB+ea@p(+ybf-+-;n`PuSE+Qgd=ZjS}YX?EKR9AJ{ z{+22s7}xR*W1dx;?lR2{4M`*h+f#9Onl6eH>#75FDZtB2>R$k{JFO0I+*g(jS3A!T zlxeS_a^sX;J~jkn7P~cV(rgi!+Ewg_;%h+zEoyHV1s%cDu&AP?G@lI=&T@1aTk)4 zA}Y|i;9KgK(S*bf2%njnO7mJNmU)A zqa$3$7;`R^h2y*(%^=#hKGplJH%=qcta>y`O|*$>-LVavCIC~clcZJgdZMvnf|^+c z%RIE6XbmMLwLll+wm{8?(_f^7id9{UL)W^C^?sCMD1U{W?ie0X9G`)&2j^;uH-~`F zTvZw2=A+A2^rj%c%uQ)DO}rocEjX`D++EjNu@=b~2qRk)Txy()WX=&AW`2g$YLCAL z>lC=>c+-f&-6x`wD^HmO*9(kRy@QK=W>h2?> zDpQ0L#WNH6iM>N!;td>XfJgEiQza@@_s9MFru)!k8MTILn%^TNa=3qdeq1k?%#|so zI`;iIj-B!WtLl#*Kh*S?hpZB-xA&`9#>%MlZDgdlZu`FC*xz<_IUdI_yIig_ z=GaE2)XM8+KW5xMJ~HyxUw`_()w~~n{<}ul+x>{=_%HwOS-Su2pT$tghKdazB7#5@ zH$zT?>rgd!4gwU8xivsL*1j&3?7;5_4DJH9kpP2y;Z>zEDhdNI=^c`WY@uZl4(SV+ z5w1l2$=EsoPE_{5lKIbBJ0e@njj7k2g3?HIN&iJvHQWh$V`2dAA@)Q}hv26$m1<8B zrrU6UG1sQ_`NJjKwC8K}@|;q5oItADaDhx5i#^VQUZh?ibjg_oeA4BVGvZ<35dqP| zi&HZ$B zOFHdD6gLY}8BmEcZjyuqQF9eWdG-})g!kMH#TIT15y6CDH&eA9uMB6GWJ+&(_UCEnu3J{7ha5yK~ev*nl3=lXAWfGrN8Nwtwyg#WF7=ub7!4ItA zzEY7K+8oQtbEs9N`v#qJBHo7b7(kq8p# ze_qEi-EFudKGoH6BAr+3IX{6UWWD&nE(6#YL`UHJa7)cfOO=*@BKsjLhi+rIl+2iJ ze$08ElQ`a?ctEq1iYAAmMmI}AVmLjbjYx7Xld8BIqpwsX84aIVZn{=TiuCXS5jBTM z1R!smV~Aohk{{QW2wuUN9s!VBd(3kZ5^eZEvQm*(#-9d5a?0xotMxvjTur5u3?*ZU z_6s-F!Z6ua{0x3ctS~j;tlSFPIONdP|WKH#KvF zvZ%_oZ8CtIP|7uii>Rb*1MYUtC&4Z*@>H~Ea6?BzqXM9Umq(tKARb4YcRRL$o5uQh z6_M!3SRx{sv}M%Uy%}hl1lGuG1Rz+gmf_&oWaF#V^NujbD&R4txi)jI1*x%o;eXwg zB`Jv&e4d~(=L`#LN-Hzwn$jLeRT{#^dC9eG8jk@DOd?;TQcXkw;e@%`5OaxKqN$Ey zqH^+Oijblb$-?19AqVa%){4xGSfZWV zjrHJ)33;r?x&Sf+*#-a(umpNq^iYdJ@j#KI3`Z zeto+7^?Fq;GugLI#J2rnZm~d#>?S6Wmu)xG>)S;|zJLENqL1S^Vyc_CiP`1ty6qbp z-S_X`?vJP1P}8w(Sz%@|m#dA(plz#Yij^E}2VR@oK0WRK-3OW!sE% zk`Ioy*sj4Y3d$FoU(TQt<@==%p<(vSJT^Chg{>FKeNo(1m40L`r#E2zZ zVqT+M2cQ%EC;JPwDGf&T5y(Ag>b^kx&ve9WO2Y&z@}u z-5>_UNcYw_Aoc9<+i0Q6Au38d`~>xdEtX<3s0j3OBv&LdFi(`5247b695Bot&nz>K zV`e0zsu5F~Q7Oz>%Z0ZwyvuUfjrz_2*by0`y3W}pio+zUt5pnhh>WUSbCxoWL{;Z; z04;{?uW{%T;^I5P-p7bp2yCR$^Jb5!9IBEzS3z-=c^-y1Y=rG0ZWB{e&`oPCAlUeY zG<`LG(WyRNmwvg_-02JkrGrThz^x$$ZOoP9mTfq!N}QG6A4X150Dtez?( zidwGzcQ)*`6O9rSa2;GNkfiptD@~89QdN7Zg0MpR4#y9xG6W*4I*AfWUMz9E^DbuD z&1MyDG?CFRh{Py5mK4I5t5StAx=|{VGag<=@(gH(k8JR1_*!s>&5-Dge1>ph7 zI6GGqtCi{IK$8pQA>#NVrNq^Sj}seRb7hc2&JH3ow?Pbzn<0on6cw7;2x2AF=?Mrq zIN{aD7zXW~vb(LUvaG6Y8$DkJkC6&BH&;S;Z zh84jZ^K3smnVP`B>h+e0C`P|*w~T;TYSYI}5FJXtCAx-@WZo_CIS zb&dfVc}9!$U?>7`Z$wrjYbQraArXP6_F)sQszF7!$dc+kAM8f=1-6H zd^{Ybs@XH*+xz!p&fD$&_WrgFAKP0+`jGei{d&26-fupqPV2?w~MK*IkRfNTryM8nN_+;EUpZ-tVRdkz=$K`UZELRI@c3$CqU!qDMi#V;oI%1SFiM3gObsY=9hA0mn5Tq?C7H>ygGR8v>O9eMs6+;nC-+!O8vg8ibxTNCA@ zS%WWKcAENHtE7BDa=GT5)*(75rdot8&7~;jqKpk;$|MAkzJXylhKxT^Q9(A5nMJ0S zgakWLQnO-avC@@AFwIUl7Z8T1w6U?oE=C(?B`~P-CPos5S}`Kl7)_Iz2y`yhs+V?= zSD~`HLd%L~H##jbGq>T@cDH6c7(QXtc{7L*1ZfhTarQJ~MJ3EgKAI{%jHQb@VsXlh9#R6x&3FEk_iG~n2aVd(0&?-}aaCYN#2uaai zC3>of*jK|=OL82j^nywjnMHd8e0>Z4I<@TCWFn#~pwX>JF&SeN zLC%T{{B_bah@S@0tg(&Agfe0q8{*5cz}yU!lvHioW@c+e-|THf&RjG0&AjiLjDqXZ z2PVKs!wN8e3riZ}_WYlI>Y3z~K~!{gtGG%rZAB;^F~9)q{JAOqcNdmw(;6lsYfUi0 zkUyB2Xl2-n<>qdsDjDT&89WC>)fJJOAtI(OmKl{1@HD2Rs$w45wheV_`-{X{BD#%n za&D@+*5sLEEun`UM@h1c;dCcU4VGNN)<;zeF%C`qjp4B<9j5FL_j*GUC!~i;0vDx- z%wr-Q0z>-bU9@DBtk+h(wJhBZJB8zn#?H7N&$})@qXRy+vWHF_Fu>S z=X&0J9gp>RyKKJsV?F`)9>>$i*!IobMQi0c=JED+O)aS!yStn3n^>9IzKw0$QA18y zAGiCqZRQT@^_a)?aw*Xg8~grz-ccUCzrSU~@iB8fFPDp%-EW_nWpRwK_4~irj1g0>KM#zod~&_RwWF12{H0d1?|Y!_V27~P@*836nn9g`{?6G1i{ zy?0g)8>vh=0FI9%X7i!AAS6_cj~20VgZ4Ysk2 zFk`|`qxUKxMyiSW>JM|CAzxI>B1qRU`YBQS^PlRX1l5>=Lg?w*Y7&UAlOCgyDOF=Q zUap#)@GlN0=wy0#NS}CGDXCOF2ZF^+>D|h}m*1UPaIqF>JtdkfYsR4Et)OX6CCibB zVpMJ113rhOPR1-FF_u+T+&m)sQdbdKYXSH?-&YA!_CYa8w~$@FlgL1iWi*!~*nku~ zPLgDjY!|YM31S5lT}>CFHrOe0H9}yBi80!ZsOagg8>kJQvNLm4BLb6~(9_{Eib(4_Q854&xrD=U47yne5F(CbPUf;#H>jrwiQt#tI2B479?4Bmf(t@bE8}Y_51CKvB@{5sHYF6>; zzA_7Jvv#m%Ng>+`-kX+ANo6gDoyZu2$_r67R0~>_5KoeysX`>0!pvpJqLHjbMCsTD zk+~{?cm~mq=V)-eJ5^!mH_B*RYi|3V8KR0ABP$n>WneLDt}$Fu<7Q?;3o3>{jeH%I z=AJ8x{4>=hh13dFw!5atEyPz#I^xjmY4?QWh9hDOOGN$Dd00XCaDrA)-(jb<8!f9! z)?CI|Atd8N%bg$!a8Pf`;SAgVBU zuQk*R@Kn2@jmWk)i^&-=+Z;N+7=r&N)L2u#W)Yc8CI95O{?cvG}r z=(AIGV}{<7;2uvd{F32l^0sY^;bRzpE+DDoN~F3PMq8H9{NYAX5tYJo8Dp5~aC3ww zXpNqPg?C=O)cIkwQBpHC@0rgC$svFxEe>jMk|yq4vG9Zd;^D~Hwt*816CVubR0Z<> zV#YJ!VX3o%0p4@|@{-hRgYzL}cJ?LYrh^Y1&ksp87U;ie5{y&fZ=fB2Z|4WZQ>iAg4N99~=u65*EwU$*`G5Dcx zXwgRdcn!emrKf@b_xkyN5iC4cF(_c{CPh9>1iVDv?+N-W407;OR}89!I&%;sYEqN<8U z_qDCelz$^}dR60fhkc!;QV1Wc1VE4x=Wr{|2s1Z4RJS2wB_^fdF3?Sa(nP>*<* z)nw4K$OHN1*w^FM{Sx(?elLkIo`;PO`8P*lNm2 zG9<}ScY;6M7@Um=g|-chwKk~C=@!Mw-#2LjO#pP=CntjYcoRsLK%1@_P_sZ7St*n) zK{sJ*uJb%!3;(;hxtXtHRRQU?CZM~cyWuj~5)@yi6G;22Q(y8p2l%zCplZiCG?%s&sIHkeO>X zSD+#qGuDLt(g`7D!75;8u@-H?%2UtX*IWW>bOmDfS!DKjZ|oh!;=!^HhCx*a?-UU; zr|zwn3(#T)_W>b;BZ;GMMIeAdWhi^q+qIwLLsoPRBjBVx@^ z)9QJv@BpA-jZDGVLUV*yVh2_tGJ;fTR(Ov!b&PH?%=b2Plkz#IbLtIGWFJFLIJSyZ zrfSg--Gy&2_9iV1evf}d_7)>T#2s&gf0)e-P zh&2&m%#cyDbxh1*#d(G%Bs1q++i)446mEgV#sUSx%OnCxF;g)NJmw5rusT>9+IX{w zLXs)#Nl1xQM@Vy){o^zrfd^+!HG;_;#DP?0gV;|R_-6uaLZ`(?lEW0*&~V+rLJXTUt} z1-e!sRyH#b>C&1pHAch;y_*Dw%v>n96@hj2mE+cYK8|o@S_{zGGnq8oHlSbjOLaOb zuuth*jdV(NDz+Ka%$D5@lp=RXL?U@uiJ6+j!dM?ICLG~3LyrN;xC(X<>!kwzc*55p z4_o@qfFu!BheRhc7(oq+MYS@dsAO3 zSQ3R-Pol(Jj8|e&?w#QVe3Tp{hq2-WVBST30ZmQ>B&q}w9ZG?#D%HFKl|eR*K=Ldr zpHl5U?>PSbho+Xz=yUt7|fT~#Vg zR5hx+1JfWSe{poC-swYz3kL9qP_<=dA_xv)T+|ftXKA|+(oM-zDQOfaGSQw#v)y)l>%Ls5s@)W0k%Sc4($z8{`hsK zhCcZ$V}z$D-Sa9;rOWo9p50F3Smz8+TrzhD~oTKS`y$3m9-{K(rX18-OV@rMZrXS?Cvw1Mpn@w$q!#*owkB7 zsPl@jvCOP{XvA(ZKWFm+nGOJ_6NqyRgcqq`##P3Wq=R0vDoe7gr+_&% zbEjPo_a;PAiAfv=PfyWDkJ}WS##$>f)>@|~0TngMN;F%;ZM4EqPEIoW{nP)EU567^ zw5I#rY|XXiQZI%;mL`{*k-?p{N)YZny_5H{9>*b1IW>u|-Ip<%${+RJ_$-IB){jaz9HUIHH z>d$}o$FKGA^V_!N@yIz%Bv)X~7iE+RH@*?>%!W3LLiaut0T}b(I;lUxKfHDao$uPR zawWQh)uMt0V=bHj$1`Zbr$WQqR9Bg?7J-lkR)yUHQF#{0eePiYWCu`^Me57G`5$RW zPtMpgCys&jw6NC|IS2o#u`FuMVh8=-GROG0L zGsrFoKlc@&tbGfJHGpU7-lH)}?YbE=89?ep-(O14Iq;o=7@gz~9yY5lBzMDP99dPI zjarojF%M-9t^DTo0UmD6m6@d#86EHp3}VU{{T6KpF`7E-ceiHaJNqwq`26e&;Wg0^iK4=f%^>MA|XR-m4elk`g_ zcChrPj`YknsFsuP`*wJLKR0|l#toh4UY8J>9Ul%dgqw{x3BeKW;WJ05UZ~BNGlc)} z-l+~_AY;S-ieq})HaMQiFp-ae%0rv@ZMF)xxsVO&3K@)_pCt~y;9S$sOQeICnslY6 z#?D7AoUMUqJ0EVtt7=5&jAo^liiwD*$*?i!6j5_4m`bZeij*RJX(z7ZB$_bxWpl5V zU{-SSxoT0Tg*2P~wpE3gW~z?_E-l=JL(VF>mbqu7J4LdH|$TYsq%2j46M(DJ0kUSZ!q6-a=7#q?;%BX^P+1s(u zw{3KW@t0{tsv@>#kT2^LYFDeOs4|htD2l~7iyi=Qu=S!eAFT{Nm2`w?RgJL?BdVcY zm$=!Ab#~+>6&Y}uTMv=Y4mPf;2ISy8OIX<9XF>p+dp+k7nJy&}>-iCn4_`;kIUk3Y zY}*zky6MkfzgEPy$u@Q^c|M<)v2Wv2A`!K1TT0C}FYj+_g-CAOUaFR^1;rQQ5Z(F*Xagp}XB~zrJ0scG*)crN-_T-7@R*ug@RvKh&fu=i?^7 z|LgVdI#cEQGxkRelgx=7q@&6+OVu;+I!(o#;(kP?dRCgcVUQEpGs?S4?d#y75^4?46>WWgG9k3GXu|0dybzza<}t=F0vw8{hGDYS%!uK;Xh>P+a#fL~rfYt_zrUHT zn$My8=jSiET;4C&if}Pg&5-j&j&00i!7E>)CJN{mpB5dXltgjHFH_duI@}T9q!A&d%!`b~zm%@#GRyTW#a<_vA_1@SCo+=~5%a7F zX!>Eqms%J))B+w6aY25IDBAu=gW$d`BE^0WAsNP)9ZZidDdlWwViIf!8s1l35!}uq zv|yN8nY-ocu?fO@7Y(W)|C9nZNlEvitQ!;KpTG~S4jwf#$%=Rl>N2(X;=`$|F*xiN z^%w46AEQ_evt$IpxM99ioOMXJxm2D~B2rjTS(m^ETvf(oGn+>b#8?bR30kYqxw(S$ zKGa5$AxJS?ICT?I9Q?IsBu)!^s=H7whAw;?U+n|dRKE_?!6#I(I$E5Vs^HIKg}G%) z-!GDdAE~wzpvt21=cM>VQIl3?jp0$Nvc~4eai|Ky!e6yNP8ac_hjfRqQoDKv+Ae2#5D%mU4u;sM;7q<1h`Y zLuE-khN>1RUNODh@59ISa*fC5<9Q!GzP-P#xnyWan7NqjmmP@%Yh{(LjBVfX%C_y2 zl9l3Pj4@P@M_Z_+&4zx_FdWD8a@oy9$9T@LZA-0Te*3&#E|*Lh+wnM_mh1Z6@4tSP z))>3Y_3s}_-d>cS5d9V?7``sD%(z` zWYTMOj7}zH_ZYlNvRLhGV+f$|R?v!U&YHv;J}@WGs3P*bWj!tDKtW&@v4Hqc)gUah zf#{t5PYFgJ7~)pVjFDtG+)kp5dIPE*p~OS$rCOK@tY*XA45tp=ji=WiNvSzw*R4@c*2-8a=*QY$gcgWa3TE~R z+B1f1Xk&EAx$#-4P-lrU41iBt&TA2kgrW#zZ(0maZC(g>Dz6yh@}cy+MHE8vK~J1Q z6)j)0;&>&*N7o{7+ezvoVy;K-ANk*wQ7A-T3OBvutfJN%2z%a`>WRyzh_fPr+=Rtw{A zs3>(+c+uq)nRAQb*+Z#=_cGxAf-|)eb3p*`^{gB0Ux*7F+?lGlc}1ycB7;7y~ z&qTgfH_tvC)7?Z%WF&e?>HrN&D(nh5vCiu7300i=k|KuS^)wztfPWdYI0jX4He8_I zft(}@ak_Lhd}*y?1(FGyMi3Q(uo2OOx1QXrh($2|1XNX@@pZ1hplg{z5VMO9YTaUAXgX5`@`o2AMK zcf_`6rLI^QVW9m!3KDkOv!KezOt_n+1j0=gtWqcU!q{w4*hW(thmJ`9B7Fm$tZbeI z+Y&N&h{<`;0{LF423G=oFepaNY_0WW9D@R^C21Ym%|nhc(3HD#Y%nKIN4R?#7AWJl zKz9HW#qA4JPw9CkC5PrgReg9y#A@TqNj*~_TPU)#P37mnS1ce{bIc6DnQ^LXWu}U; za|L}5Y+gH%6sVFiT^K9}kNLtRMa-1tc18U`9icI0LV6q{UK7?Jej7{AUi8AgA2kyW zbLN?J??gTOT+4bcBib;)rs!O?RUfJ-6li-O$WrNUI5oz$WgV4E)?<^}GJpN~A8)ZX zQ{7y%+;!!lrhohK+c7a10RvMtBDQ_=v2FVWR+ix-OKc2qyl>a*@i;=(+uK`K#gd5F zufvB;o$j`#&!3>A^>a)G*KjLpS|j$c3j9Pe+#8Os0T$B%g|AciWjo;S}UAHT|K zQ#s-omKo{$w{@3&{M{tfY~?idk^r)f*O=L{c`ejgZR1YP$?5vFLty)55%@=iZG%!Y zpf5a85neI$38;#SHQwIe zu%cwNI|v0aI%hYf!%q!RnY$_Kn?68_sSz+v;KXkU z7@3R@jy*#B@>O#JszjcZ>S-osC|PBe3r_$ritfPWg@z4kXXQ&*P^P-pbRQhT+wtmI zi9jCAq8VdEEYv7wrieBYn=8h)OU8^f+$Cbo`#wg_qt=S&)!8 zV*0T?$xW6*z|eUX`01&NvxQDVTEOe%{n32)g>Qx3N=W^Ys#V&Y7drC7s`sKWik z)Nos=cba5pR1idenFo02iArm1Aa$;wzz=aoawa&5R8O(Qsa06L6u$F!3LBm(5i_52 z4j8L>{@{V}WDa<@TK5lGL}iRKeVe0ch$Znc_dtNisI-inT+$^}+mSETdrMPd=*Ah%8Yp2jaU1~44EjaY#akZsGE&2+}!3Wr9a-2~w!#OG9vH@6Z^ zDR{>-vQpGatv4!VScCy8v$Axch;Qm&RpGN{CqfCkw0d5o)`C<3@Uoq7pt)sLW9KCG z7y`tCVPN2ZEXX{Ofu$1SROJUtbQ;dUBUBLk^FK4$IA0ryKAge zGas%LV~7xbqZuF~#_&o@$vy*wixq#nl$xdp#)|SL=yE(-Hg}3W%%O{?j$BlcF`AvN zR+P6wy{8+Fq!MM=IWr%l($xel6VzoqUvR6jUl&IctQo6HhRWmful4I6x{m8`S54<`^SW+-%>kkH=lg_WcrbjcwnqTUE}OD~`9z_5OI?{`%wn zx4*B?%w#P}8wP@P-(bHLElvrmY+}?O#B&Gs7DfM~|w? z-%wvP@%VVKE@&#h8S5iy9JCOLkUe>^sK=G!{Ar5_S1%z(3%Dg)nRaqhfXU6Q{hJu+ zO0CFQLiH^ASHKAF_2FLHpNw6B_UDmA^ljS!GRcyoV$rkFU7%HSR8*{bK64%#M@i=KFp*2H$6x=;GKR}kS+`&B zZC+Z0p(pN1-u3y2M zebS8GwUs~)uG*oue7{^nje10-D^K8zP*D}}9*37127s$L>Yj_7tuoV}P?KjS5~9$N z;)%6#q+KunzJOQx)rZ*cXG+uKmYJ~h9jCeV;>8g>^Bf`2n0<0$V zx#L7^^AkOzO5;1pXxK>Lq=agqO?}+0uG%ve*o0tvLV;z778DWX*-}~}jEpR#bJ79G z9HKE;9!7+zqo4<XPf%v zpw6*vNg0fdZHP!Q`1o}^%IOq8Cn4rbVF;|e!z!8RY-sES1kUo#iUphMZbA1VJEb^< zE%@#|Ul^Qjb@K+!)(6_H)5T;Wh1@x5Pk&X74YUA6)QYeWpyWwJA5e+7=}Li~9Hkv+ zP{8R%6*qSq2?%Hc>*SZLn+*>4#PfzALWI)|UW0=9V1ti1a&7xIXNbJYO}x=0_hiZZ zX6kIZqMErJOi-7z00=7!qmM`HCRL?GVq#jJSZQRs7)VUv z5HUaqf>U&YQkTln7^+Gv!mXgO7_u5Ti%attrH7fNv(70HUog3VN+ zH&t*F!s{E!hU%4-nlwoOJwaqAS1LMzv+5#$$1A4$D~hnYF5!)c!$|o{qT5!BSa{16 z5(D&gV<-oZ_Af>&5iLm{17_kGR9RWi#~~S&UKu~W ze|tWUV@4cv8@}(GO8wJ6{nM;ik>g@>uIu}onW_4U8p9*=m~%z=@VD#ReLmGyhgsVF zxTn;&@82>)w0!g9_E>TFKCav43Sli=Nq8pO4GeZxR-jyC z7b=ZM6zh9aLkCis#sA&vZ*_Ep~*7MQ1@CXMA(404bwC5qmW;Uin~cd4q;}B zxSmtg5efR+48aU;Y*1;8$&%8l6>~UZ$E+eFjuG+A>~Z_lb>#fG|N7l?sg>M+ZLSq_ z{`JT3k-4^ue|LTS_1w0RW)ivo_%WmM*JIu1o|Tem|b(!^{!( z&&&rSDeCTHj5(*OxEqXp%??SWR#$KC9_N9;%!$)01!g$S)D^H|1Nm7eV^FNi`8v8E zzzZr;6Ice;T|*WS%2(#3smM8thgSwSl&Uftr>B#4*QY{J7RMS0gpiUIolGr?#8vY( zS7&L!?j8BGf^WrD9U&xb!qtD9nk2oG6u< zsHvPWRzTJ1@{~@F($3C9otl!l%G^wKb<8DkT7WA|+7Mp5JbX$GE&^qFx^207WHp4r zYzp{JLL(>GJ|uL`koe~P64+b z!{=Pyy%t6w2LsTO6H6m8VGZ=$Tnj}lA$I&H_l4pZb8PA~ow_-FL|}dfR2UElk@Ewo zC6&1#0a6G`v@vvm0^QLxU69$*#8@QNoyjGUOL|-*0id-`^P8DPrMXttYNbZewn0fY z?ibEM(S%`I8!}G1oCOPA(19!S$4qrq_;92Svk77mM?)q$v?_@tcF-3)E3P=H=_*kj zxk%)3TvwirPoI|}l~qNSzlw2YUw&_awU;y7K}{87O6v@YE`;C;T7w#6L4WrFQel|k4^6{yb(QHX}( zQVOLx*FBN}vQh%@yk?f0tJ5jy?kf>IqX70!=R$G5i42ZYr5&)J8*YwQ*-gcTh+-yU zMn z^JG)@X^YyR9K-7oo7OsRwpPqn8tj~Nt#!NKhhMIj3+y;*I>wfomEzmjwo7`IXqH^| zJu}DVvLfdysj+P<18w9nXO(Q@BBhc##-0(GQ*B5Eu*$c$?`uupwt1}2+vnx&^7Hvz z8QbOh_`L6zZM%%JT}n;rx^KDS@$qr}_D!A-Gpk>J%H>O}!5JO?617K(Ylq zL!sawE*@1bX3p0^nuQafumHO4RTaiU`?)ej0dF{9equb()wJq{uCms%RbvvsgtWbq zRf5X^PZlrTa-&Ps(|F%cktig+SbRSp@o7x06qU)(Kl)DRQ^&*lnrwh*v}(nQ-feF8Xu z3b;o~PDTX?6)`N9E+TaXB6|t$XxxKv~Yo+@A_^|8t`RgB%nxB8Y{rx|s zjpv+t`LVy-7=9eLee5VMBooD@`uUVp&E}kXYB_j?@f=Qge}zv8Vq!V3fGh(49SYZa z57zD}n4<`bA*UCqa)Ma3C&QTNK*pz4_>6o})l>rYHH0;VAWNdVIn8cYoRm15|S z23_Vd+@tGk0Q2VxuMQMXHW-~pRCH|PAwaC`gDw#fG1oyWrQR6-UKNGea3< zv=-1xVQj)N6uxj(si|bG71rZlw7vK*fVK*mtqhcto?T!rDwZn4Oj#;Pr!a|>rj{`A zwnQ@mg&N!`)ptZ{4j-nL85lHC#Rn}&kuI=jd4f(TFZy279($~bfL#@e)J;`SpUFuO z4Umc;a7yG1t)e9txOKw|O-5EnT}j*CRXMK9GczMufmBrlJ{ph53gcw!=2%WNp|yz8 zt*eTDt61GP%J>QRh5u_yu4mi zAV*;R0FXU{GP6;C^Z-@0R=9b~EE9zgv@zP1VV~}jl}hPJ_tlVr6gW@*;J)uG);huf zuewPCqLPIXX=l0)ZJb59I6y=KtSD|YMHutSkh1f-;uQejl)Bkm3#GG}$|*W+nGKl0 zegLATn{R6^VIY{G|3D718dG%7^XfQ`%pAkr9rKJ!d39;zbsnb*%7$ zYdk^FMv|!(5oaZcw4N-HVeSBj0N3@!Q|B*5*zMU$R%T#~nOiNsWeD0p-S9+t<~}`6XEF)9O`DKmKB(32lZR^IE<>r(iR(A z_) zMI}M!58G7za@prxW@1v;%YHnL%{Ox|)#veacUf6$A!+0JxbNHczKxU^n)9!J=>C0t z|8^PI<8jQ}yIj7H%iCPb#*oU26}nvE7Pa$;kxmG~Lc;RHdqU2Sbu4k?5Q-gvTL@qP z#5>Yp7(njmMERl*|2(%zi=+8Qwx)pm5mptICB_pskyyuzfnzBXag9=#h>kv%=0Et% zh_|%>3=#_r5>lDR1Zn2ORS4>zI1`E(*oaxL39o%Kx~IX!0(izkMfH%7o9j8spc`sx zDpU2O5jyiRs}%Lq31?Dda&o8Ic7<$C*%d=Ga5xzmd7)kl_ZAMlta6b6;1sD|VgOld z=vXT^*G*H`L!LLkug^dJy^ha|O#kb5tIFrSGB%m}u=Up;wT-;r&16%Jd&ZiNN&LZL+CGM71CsC_{p>Lor(Wnz4J|$<}T_7P%)R1iU@~=m(Y8RPu zGD8(=jsomZPEQ^Xk*ut-4cMp${qCL_7!Mi9&13tjB4w`G)D2DaD%@M5QPIL3bKv687B0f`0L1UgN2Om+;fl#q#oJ1O$FF2A7Oo6E zWed3LQzEI}Hu35vkmm?KaljSGt(vWj6&NtGP86T&3#fJ>L5%X@ir%9q(g-9>4Q5$K zUz{6X5TcEA5U4_jXNH-Ijv@-(D99I$qQPEhG*TjlAgjjieFqv8!m#0q*%({oQjx5n z(Xk6_qu;MiNj8$^BMM0al1T5MWl@^{iOeyEo;OFsH)x@Gt(YL;2SU82MA}ax)sMoe zIKE1i>8>T^g(Afx!bBqrQS&8=h^mZq9}$ZefzjJWsgh|%s-1&%l@kLKwFewKgmFb` zt!VQ`l^`P@>1t<>rNOzkDk_7a`#`#7MfflE&nxoxA6@{VfUL{!dR z1r1hZ!rD@8I7S7qZChllIpGotI^`8wE_5l`Ar-vIihs2>(`ts~$|Ma9uYs9D&DLej z`P}fgBNJB^iU|rZl(u~Ys%ox8>4F{`f@bI%6)v$c=3LGHnz>1&oPS?2?oqf+TK3&N zG5p5>l5w;mPRGr=Yy!jkvP@ay7{E{qNn}OrEmP!W%$FwV8=f_-2>xW`*hVs=hSdTZ z`-(XGt0I4_D;7SsnVQt*dOsd+T9NxU*7JG4&CBKboJ-Z_T$Ykm*KytV%kwxy-ON?& z^Yiw%-~N`8&v{(0mlbQR&3Aec$)n?XzlL_U(Qg zbFJYv%zpg#<9@rZncseV&s@KLetzG+>(KRF=C)t#Ij7V*?%~(VsAJui&bfY9eg94E z&A$J3q(-D~Zz^Rf!`00jY}3r83ed4Y4dJFw49LsWYUr8GDKC3%0YH6`#(_G(dx}H{ zj(yY@C(V)6C>NB(s!_a;gYn_ovE<^aRDFqn5WpV990(R1@`CwY2_Ln2@x*h{^=s?D zVd`SF4tKIpz*dpTNz6Evx~oCWQ;k7DRu?PHUAy?eq0t>FE+H+E70c+3gTqC^!mG1z z*A8}&<^+@oq^PKxS0)OGVuUlNcXvvGNT*vC)4h0(Kxv9xobC^km#PoRh zoO;}Ce%$`?zt*pxyDpiZrg@pSm~-83+vaN?zn*tfi8Vaa#O6AVd-)g=@wN?(X)%56 zx7+9Yw{OSKzlQnGU%&RZ@5AdEhkwiM+ixD`x#p@U_5D&(u9O|;3N=NH?6!@}BF`=W zl0u630@x(Dw+o}eQKd;)0s7VRy^}JQDx=7|+Bbp%NLYV_kD(;Wfmy0`U^epIlbN9y zghF@{u2A`cL{zJFalk2QsP2$W`4ZJsB`Q@4Iw(^ywY3(eCZk}ltcWy%uqqJotCm7{ z?xiUru@)AM4>!@ZqNtj%zK;UMITnXfTw3TpaTBuXQo-tEkx>M^ajj$HQPgeXW|;X0 z%1a5cNo9FsFz90=0`A@_kT3%Hk8{3CMC(j(;SE(m&c8`NGwS|VO*virz~#SW?D7n8 zJ1I-x6v1)Mb+#e+B_x>$en=ICC1_((t9ojOQnhC-w-D5G+2MiK@O)OfT1lFqH0Lc@ z36qXug5kD#rHUZ$m`AF?O#vJYolmnIO2G^QOtp&%F*Wr8Zoz$27kpI+1lwNNO-FU3 zf7SteRS8Apz<_Y17Zu&fB1*C{0w7>jsl5C)D83_8mI%WnApQ|(T3BfIcE%q>Sxk^l zPxu_0y2cnKIQo)jB7w_bc@D>_%bKf*TOlvlb(op^XF zSUYR11pq70aZf5zveuL;8n~tAT&Q}pCp)bOaF}bY%yhMaLX-x5QI&zU;_iXowD&3! zEr5=|zq@V2%#S%$6*GZRmy3!QtwU#k8VCee6FUWk5ZZCX13FHxs*>Y5Q8;2slvJfL za29tq`k1vAI3e8E*z>Pbdqq~!52WnfagHGT978^sVrZ6`h-A(zsWNW`GE=~%8b=r5 zszpt-B5?i2F^%&Z%v_}u$21$(61?T}R;ZMHL&~+%_J`SG%HmiEqvGb{0>M_~a*Uo8 zFDfD!X-~4yI!VJ(?y{u!lPKt99#vq?W~LDkum}{HFvwTZ$ctS#HrJ9^$;dlVwC2_} zb`jCjq2C!{!klR{5$#c+=iPZ$>aPP?#m)1T5Ujb7m~ooq^}Kzh1cZe%Cm{>@1E?-m zz(cH3nL2#rBP(hh^Lp9lyv?-+=5!9P#L+zFTu^UDhOFb;x3@?+j$^-G-8^c!jcqeE zm#8s@nfn++ZDmTywvD-_ba<4Mi)vPE!{5KXf8Odj4|5msEZz5g-}l?cuV=*f_iveU z&l~2TZ2PVz?gNiRQ%qzY&nk`Mxk+Tj^D*-SX>zyipZ<>;mgYJ(cm_&Jhg7M;Yp$lE z$QEuxP%_Y|-va5oB4k-vN>q%pDp5sn$6BF?)@i3=GNPmw5*(rovk&P8Rw*Iw#c_eG zND$LO=2Qd+dStqZ`N+W3*d!1R=%BH7?)rx9ts~e_3S?v!huu2DyHM6p-^f*j1$k=H zs%Ue|o+bPOZbkW%&iodRF=}g&@b7)y9E2pQ80v3O_u($X;eL|!XsoKSQ5pD4+lst6 zj9oH=eNJIJFzrNDW=v&l3|eQ+74GV4vZB`WdH?lYp7($IAAkMJfB$XME$)}&e%b7} z|GGV&8OOGbI*zq;KBrWSeShBXW-{k8mB0P{Z)?t#B2tf!4=Jg!JwHAr=Kglg$W7~g z9~#H{`CQNSe!Zk#pP#p+DJH!-ej(K9o-pMV>AxF7wLtDiw zD7}lZHV}jQ>;B4R&+Aj1g4ArlJ2R6hu?m$Btb0I@ z$PuM5K7k|#zmIkZW>aL!ui5F1z@JrHY+NO#&*KSQ_{>zp|%n!IYV{ujQSe^C}r9n$|~$GWw5%rtGJ6K@HL2` zl_Yd|=i7p`A*W$>%vlJBFjEGLH!Yv3X6TA>sp07rsv^r1k~pzX;KOq2?W%luMrPI+ zmKZ6+@)Ub0)&0b$iWbLGB}1~{n964Avg$m#bP!tr0RR9=L_t&~ig<^ReB*^E9cYel zr_x$0s?y4;7?&5Z7;0$N`=3UX%3?`|<>1g$poj@=)e3-rV3#5?#?V&GH@Cynk1;7# zvBDkcDJ9GY2kt}6&df^^8c{UBnBoFthDRbQB$E8?D;0I_yLO9F`ao7BBPtmI>`cbw zjDm>3peHFH>ySOCNctFNmaEUw6Z#QZ5hy$|2=I^1V=ZWX@Q$o$Z5S{b(dV#NWWeC4 zecd(iqV%k~Y^w)G?~Os{dPQKp0@pxG0CA}lMtbpp2YevC8Uwm?PT)?!mzcCrB8!nh zfqAuf{$H>sJbOg0d$BT1Yf&z7E`@Aba2F4Z1QzhDxa(C^1{!MY$)vi*RFJ(T!q`#r z6d`NdHWkTAWZi6_O~006>K*9#J12oZ*D`&6rd_!7d1LtEK=*mn@s^(lU zUsjd-$VzlrEZC&=oyL*{zYR87J!^MT6Jm`&iNIl!PWq1odN;UzL%T_~yVPBTmsw?% zRYlNa`RuD%)lBUPsjBGg8u7t@HAE4WitgJc-5Hr$ z9BWg{l9)TsY4K*{_d`Og;U9Vfr={_E}$Mx;aU)= zIG+1u_!ucUpU-da-=4Q;m3;s9emr3ujOXpwF;jf}`u+YU5wguc{(c{~-~O-nh(p7y zWX6gt^Q|;yJu|b?Rz@Z~_aX(9ftv{byE;pQKrL^d03F>d2}pEma-x zol}XYYn5X{pNNS>#Bkt-e_ zw~V}A-)bIi{`1d&AVg|CkBG8u)O_Ax{f4PkR{HO&;OL{y( zRbiSWPHa(-md%H#o}3a8WTh^m=}1wmE>zwbXG2%SRLoqM%YN$txKL$>JCm+5~YF z;}+zZiyy`R(!&|0EAo=oaWq=176UGc|++z^i845Ru4?m2fEl<15vQ zR4Z1-ZcUi7*JWGgZw8QqIu1ffxXVTLR{`1Qx*I8fu7| z!%LSQHCR>1W#Oh4q)Ir3CxGKEvpHkaHdXe6GBshIZva;kXw@onPh*XKYs*G*ZlDU>D^OS*+qtnYw1OZ`F2W0fB>U7Y?T2+ZucRa|s!cJL~NJK;| z?Qz1{L?wh_W`^-dnF(?U-Qe(nuO^-3SCx3$k&JC*H9qvBLl%h;uN zUF>sC)XyT)TUrNhCNzk`M`UZQnrm!ZtffrEVxMr?FUOp6rdAU}WvRyOg;xZX&ACd- zWb_iO>SQnxP069>gw9SMbAw|E3se?99!b{jH0*50BP)xtcoD(zp(2rdkWgh|{c;_M zD!s)_^oOaRZADdP4tITJJ4lNpkZF@3*>PunIUEsRt!J@o9Pc8-ea=i8Mv;JKs$yc` zG|V*T$Vj z*Y|5_H&mXPb^Z3H>T>}Zui+4T#M|4ux*v1WE#h*yUbg-6oOg^8oD12{5}89?w{6Yo z+g@v`>T%4s>vjL}+s~hW?wh~=?c43=uUO$KZnjE3K0jQBs&4xxkv=RkndMBAU^v=v2oeCHVpXWC5%3mq_v86g z)60Glt!Q7bA)NzV7aIefKMkq!qg7FrjmW%h+p<#SR0|Ly?4|?U0(@`4i%nF@B{Mh~ zGzAPBix?58j8I;XQE%~ssaEenZTvz^v|#^Io=6FVgi&BxQD^$KWL3b?qzE$NmZ2j%exy=SF-*3MkyD=c_4&v8<3E1WIp)Xj|N7ta_WS#$^?YP3F^Rd>oY%{~kMVrm zp>JIC`8b|#_IBC#ZA3)QeBNeNT(7&zRL%WziThk@X4d=r+sfnZa{c)EM@anh|M7o4 zpC6Co8RNVER^PY({QUe&-u_l5Hnuqr8=Dl$J3mQIM7{A+6i_(n9X>?~P+{@QkdA@JH0FWyX3}h%jbPQ|4F_M=~%O+I+=irI{=?7+IY}Qka*l zR#9e}L5|eT(7c(tXk}&26cty)53E4Df+7$c9Xm!i^hyQbh^toB<^#EE6k-(zHkSx= z%Nga{He5s$!CBgFF6?ed>Q9Cl3-WYl0Vc+F&a8@w^V7^~QUAJaTQwnk7PQ27lw$9* zu}KC(7oZDw%101l))x@@DvYJW3^LQ++QGaa*hh!==(=KmZN_YRZgx1@OU{aYb?Roc zgxk$o9Gl_v!`F#S2{c9L=jSmvy0}$UNclmYSx9I*;h>`hr#a^&t=XHWNln(k7?yg$bRgAN>FO@eASWFmE z1{6SPBZ%%$5rv_Qq-{o(;5@}}2TJbFp4bc)C8AWKg5+&iqlU5#sjldZ)fC|p$2^9Q zQ^?rqVH{zd*+-ISEQ}{qpyTrDVo`8tFVNOAsH{mb5(ZhLxW`lCFF*Nj@Hpy*}* zEO7EV6;gQH7{JF=DU=2IY6uE}QyI8xk)X_664SLXfMmuoG9`c@)bsf{w%v#MHc~Yz z=R8)~O~?Z=NYzuz8}RSFZimdG~txt6GpZ7b2f&Y3(mhMB*)+gzbx!z|YN_3<(D za=BjB?YfW0LnL$fcs}me>uzJaKW^7;+b??_Yag3b9%aXTjNM#E<|3_WDi(7+j^pW? zb_@x%$6A549DM5LTL+Th_5*>mkTn{fe=M33}RVp4tLgY38`dU z_cu&+F+z`8B`P)CqN*fB8=A^cLXsw&O5@RSdywf@RY_C{@*K|FR8<7w49S>wQ_D(B zPb+8|P0m9Py_GlUpsEyR2+KezS-GrZg{%i7N@{U`X_dfE3Q8ce#u$jGZX`Gxv#~01 zF`Akrprr&gCyv+Chuc~^? z!$pUV%Nc5Rhd8k_#A*`d-~tj7B*>E9&_4UC=jqDr$t zI3C5_A~PcPZ3hO#^nDc{4z_m1+Q!yRDYN*_$+A3WyLu4hrzLWVXiF_l>yr{8JPd6r z(Q;BF{Tv93u3Rn-<{9@6HQ!Ag%W0t|D2BNlWc6{KY5&8@vng-o%r$RLfLatBMb=(| zDr}B4bt7F!Dl#*@V|h~Y?AA#|DCj^dDS@pY>K)-{z`Mgc1fp!sIafiLm5xttw$?Ov zs!1caF&b5E&jZW`c&Xh+qRKmaQYdF2I#WUFMr2e%!9Q4>G2Iuv zYb)Zq7X`8zot^0Bpa?Tk1@nR=GfuXRylcK9ZbiC)ma5DWXI0I)63i_uRb;01kYH#p zRr>!`W(FTcRaKx%#BnQ?slpbH;U%Ws)RCK5Yn7J?yB8Zc8cw>#Oif+RvkvlOB`e50 zBE#$JSk1UaSObi&#sBPOKQk^Y&HL|7pN`yOSWgf>wzj%IA{2be3087>w z&N-B+s)6+cv=m>2FsjliokcNq2~-T-AG)ea1R}pOBqPS=Yo)1-G1i>c*AvQAMJ$qZ z%0;I5Q0igeuz#iTs>m=UbOF8wIxRd0m?}z`wvOBY4gfBC5%8@*@UIm(bMVKFx-pYZ zDTX))U9d459yDTkuW0}?2xR0dSJtkK4-ekWl|;Wf&KVI#qN@1?w!v$Inx8!-lY?mh zz^w1Jri!}jN>Eyk2Frb*^F!dkX-4eh2T5C+6{comWMxFsS|K7|f-g;VMTUgCOJ!vG z7!k{@(?vUM6bB!$B!o-g!{eyMt4-qTd5)=3wJ7>{tVhs8%88<+9VC6H2>0086m?bu zWAY+GU3cH`JI}Ki%InjLfS+HPO2Y%jxzkgzP!vX}+qP}7LaY>W$)buZvjGx1_7Jo!9Q}THnl6l!LD?;&P#@H{H z_qXfHQBud8+wPakUf|PJ#x^4BnA69$yOrp1OgA6fSdmp?w%u;`ecKgjt#0lvmn{lv zTCEj!y<9KX$K#%rDK+Q(_U+p-#47FE+hI>DiTV8T{r%tm?PHo=zim?6ub-dT0>|_4 z{bH_CdE9>P`&-@qc;4^x?eAmv_sjpw!ar^w({^`H^RewQr`3px61i*_j&fk2GPDPw zOy*IA1jt27(7FPPtSsr!AQ8bBc}llQ@|K%vrHa7drKa>(si9C9W>L0QtT};eBTRqa z_lOkLT&r>+C(+zuO|u%qVx?4?4-vWD?xyzk{uZ$$!#BqPA}T{MR#sIla)B;T(Mn9a zbnfxtG~h~;ip9W^mJFOKgMyCJ4A^Cf%35K4Nn~T9{3H0jqJm*P{kwp0qjxT7r8ufW z;V=VMSs4YtoM}O3sjBML%FGt^=E@w9EF;%hq$>Aq%eedT$Y1~Z@jw1Q+w*7MerC;@ z&%5O3U;h}EK2*hBE7qEGuH*T3eNzqdG=LPt-!ax2@RG>bhWj|yl*;3nxz_vpyQ*#b ze*649jzg=)*j7o-b=;2i2vgtQ;g&ySu2T(U4*!D&=_`V~lMZ z0M4sL8A?W!*ErMdV7-7Nvw{UoYkdwa?_jP2G-Eh9GjlCqghi-=@E%{f zz7*0`Z6|9CNlmOJ-Jxc0BmK?xiD(-4($#YlGGiy~RM2j0z15aPcT=YFYsQJYt@FG z7;xY36fW|*RCc6zN5-H$CitP*Q}26@oeh%%iJ!tG17;HILxqz8MBw7XktTinvz#>s z2BN@4e!XCrbe~$P+M)`og=?X#GHg{;dUb|Ic7Y2r1nqLr`Aup|>fGgR*uW`T3K>X5 zj4u3CIV}{2u$h}VG@oefDF#qGL2mR~uZPKQK#blD{Zch?#2fh>EujA(cvF(1UUxL@3)&o$$SCrJ5&-CjzG%tm-KL&dhCB?`#~ zLIqG@LzN*%plnK{OliuF z%ymo~d=BnTDP+}2?iABV6aHg7jc%X~-U%m39nnKBB39dfOECJVkJnaiNHXz>d&WA; z7Na^+l1&$wVfU(xSV~DPAE-!=FD;2&gM;Dt{CqgZ8dh=Wa#WZ?H-&?|)YQd!@QI40 zNUcR$3hgSPpvnwvjLK-8fMhXT;?=UBm{3)%Okp^c#+r}MzmD6lp|KC2&&TdAh)(z6 z12l`Jif;cKQC6E9>zbmvYQTT#}(xxnHkyMrCRl9F!Of zP+jk$n4xB!vUVxj-GJ32E5ji|sbEeHcGTuutW;4>K0!^U8f%TQVfMv5!hM(pN*`65 zF*9R`ds$`|)Mb-{XA((VlWQ|&gAQ%DGd-z0CdMZd{G6cqa z&4nTIf#eW8c;%MMR}wC??+a_F`(w_@Xt=83<955> zZlC+UZDVN3<#PGF-9CPPT;DD(no-ZsdsO9d%jZp=ce&2z`phslQ%?IbRIBS)W(LOH!z8TCx>-q;B6fs$GvNnQDPg4zk+A6xwnK7>@VNs;HvQh2A2Qc%O@hPk|PX?aj zl&m<2<15-o>4t#dQkZ14O6HoZ=Fx76LXehK^dvCvp#?crvDi&XW5gLz73M}BPu1#7 z_f{H#+%&BV711+*M0rZMY4xH)@tuiTkbr~IlupPD_)jd;L_~Wu-Z-V3J?kAwm0%n8 zeXpuT%a-x$ba6%V33H{ID=;#G0&S=AmW|+=%1TjKIyn^s7cmVXLcOV*Kabc(=3tY> zJ4^7)$V!od#u|t6xex_+8$3zPsVUC_#tc_`T=2cf28yf*5F@yDd^nDCX=Nm%MfElL z2=^tFkt(ucF;%KLSW&?TCN)u^J)MVFDRB*X!8}!SF6=pW8X`X2Tp1qP7di07WWFSu zQILb~wvB<7k6kE$P)Z4t*6lOViuD%t?NY2?O|_zidqzAyf9B(Jcdb}eF}88NUXOV^ zpHDNpTrVq@o4Uyuo0P0MD^jKQecSeJjB(xHw*6AlVrw<{SrfBS+;5+^ShKHx@#gjX^?Tla&Cg$>4xKlhw@scFPhC&f^eS0Vu^@25&`T@^ zRK}GRQ7ck?oD)1yK4_EJS!r4sb2u_$L$ypf?wY5J)Law$PBD3O%~*%Za?6O75+!0N zs7*~}xUA!$6_?>TpU?Xpng*@<`1s)3jfrg)F-_%q*&|mZ-DwNZ7Zw8vqzFzJ?Gg>c z6h#5L3&Ff-;n7Zkjhtw)r=9RfHE^OOPw4U>MeY>jLJZ5BM5Bu+D|%$4rvf!m)^fl~FV{j6Uz zEN7XyVhTKp#rPL22FD`oWZr(FTG@0DJ>MWg2$mNse=)8bS#xIQiipUDMC&a23r688 zT-kVELY}Z97k(JS;Lca3rVdYPr*UA~LuO)clD49_OL(lY?WxKD8e83#R+S2ztUL+v zlQ7H(X&*_*LER0yufURqKUhV@hN;O~!8jd*vI7o>WLAm_&@nEXPL!@L-umk&#|EQ` zg5sbTYxUr^Gq8w@1cwsQE421JpK%nQbgr9aRII=P8^bwX$%u_&J%cpvzmYFmg~N5w zx#B~wwXB0%vfmV%tLh}KbMvBCgpgIFAX~zEz<YP65)a)Sx1M9OA45tiy4s(Z{C6Q&jq8mO*smfHhj5^yaRW;Fo zv!g^sP%9((%hZ%n)&?{H9*H`g5QbBpXbGO1@&p7iVA>l#B3vPKGk^@aBJexSP~iF) z*#ixXD}YTG4*~(yjAyjWY;4Z7nqm@}o*$zm*Ag?(6qRK@B9^LGgb3J4j}33baZl+s205r?&fRf5U58je03)iDYI?iYF`iIOwbh61Uc$D=A5apT!krq$ROQ;O<)MOsD|zjT2US>`1mpJ_0SG z_I;^lwqi+1FL6?DFMc))yZVdFu<#;AFTbinpe0IJl9=l4ub`j1feev&4ny>~o9grN zoX8vtJB!Nw^Rce$y0&1AxD8j6Zp7vG_B!?_ z33Dx&|AW2vfBu{P@qg_1KS_6VWmj^!ToyM$p#Aa002#ZuDHXh2uQGaTwbu50JV0^1 zT#(>$y^1KW1c2Lp8)LZpx3}N!&-)l10M@m+d6&K)&q56e(b2^FF>bdP9pk!QN~`zB z$Is`u{?FxwV3-iPm}%HOLx#am@DUJ^r_#*NPANMvnpZ;}Q}IWLB2f$`5?>~bFp(mh zm^UKXbQJY88joT$Pw>g*fB@CQMcyX43n7RWp5S-D`8M+)R@T`~w6vL%Stry_dpo}3KK z%Nr4}mJ-|yokG|Ed@u#^?&cJ_k?FLHgy13D4ehu<`i7%h*h7M~v z-xHMSOgeKCryA{i{0K=?!4n(Eb}6<805Z*PGdwYqsvHqL7N=9LlQa^D#T*Ml=0Fn8 zIv$hJ5KlNBev&o(92kD}(u4q0&*|=wS%|+71Y&>b8K10C{pCO(zdE@9pdBL7#ofbl zI^GQ+hR6iIHZ07>j1akC_@vPhJJRWa1OQHLY^^SN=^vR<#-mc=km@*C4Q#4x-u~nn`%(uEK)DrCS)9A=RD$) zARU9v^i9F@7v&O(6BYppqshq0048D=urOdiRU!c1S}jZtY42)grIaY7W%L;5#S)Mq zW+p=%AUO9hRi38N8kvKONFc{D(o)^cO4uz$jgs-Kd0~iouVaC~w1;TM&JdiY8a90X9fob07=FC_MBS5XCFoRp=xY)QhM#_H< z+(gt=Ayk9;lO4Tk1>Ms0m*<$Pp32|1vRI_e(G{yD#GIaX38oR(&9JT4l`?v42mj! zf0_&gbhT2r)jEdm#~xRLsQ`IdO7CN+F&7zHN~ueA^vm^HxDqpftGk-DrPbB|aa}L1 zEwxrrHSpS+1GKtaE|=@=wzP$bdLJ?b3G2e^r6Ixl$Iors;tp{4bzNRwUbcM$By-w_ zaA}BCh%U=TeE@-gaxMG51G#%7CmbB`*zi?fByY4?)dzvj}JTU1jBW6ee91v z_4w@1pP-xDFcDPqA+Ezs8Ic(X+*O7Qg&Y}~`_KUZZt7-*Xf0KagTq@x)J;uArGpGT zHqhbH!3N0CvFkX{&E1Y;GZg~Y(NPUdmWCvU>`zjWu@kC}VPg;=n9Fg1Nd;I4_WLKO zpqYvEeuS?QyE1^OxfocSp$2YabO4Kx2gqo1gitu3d-~54x)a29NP13i0}v6CBQ#HN z%W+#Iw8vzLDCNL-PA1*Mxh6L2S!~62?C-BoOy_BUAm-mnNVcOc|I65-9gQ#|q?YWhuw(H`AAJ2Q2js&fxktMjT zb$z+K$dK)Mca`h1n2cqqI^^@?ZlW;s`|Zj$P=y>yO!f^3I4%Vhi+n>B4T#RC!5Su8 zI!O+3q+p!Vlk<~jM+~XVd;;X2T%uGKnrTo}vgw@2>#Tkt{ z*{9hdAYv%j^Uj^^$E+=q_|7~BAMQgWDwd=`00zh7)C4m!Oye(4W4~!?F)LA+y+zWp zFiXa)3lXz73p2p1xGa)zAdG!dmT&^bS(|~MpMEy}S?;@;aiX?o0&(_ZW)WhO>m{LO z|9S{>WETVTuX3%_LWE($6Ah{lCkL%L_nkgr8II-K($i?zRivJozJe6PvKzv$?AWp>Cz`^V~@tQ1!^^2$z#a^tmb9bK%ix7l_mL?xN zLWztQjnSzQBF~m4M|K9LR%^)xBbi(&L(VS}wI4&WHYOQ?&ZQU>^g#I|$xBsDj1Xfw zPxz*A3Pznr)*}J}YV1CX9A7lkI4(TqeHPZR49P#vt~Q490rr>~7&sYy*^4Q7 zzMjIESSpyg))GzaXsu1=olbZw0&>x9Vq+9EjKHX%lruXiLtETE#9XzonNQCI4;xWX zm#j#PxnK-K0U)z)LRsP}Ld-L;VJTx_5E-K1|t}a8Eu{7?|#r*d6Qr8OtX=q@G)B%yr-A0(?u$aUtUs`qYW8bc~ zTTB?*(j2TXyScfwwaFNz6&Vr)$GQ|IuBAR74}7^^FPG<2%?uq1*N@N7*XvcrGWKn~ zE<<}fu48luFfv{U0CZ@V!`;TQW2t1L$o~B2|0!bivO1RW6YITF|%A+ocj{Bfr1@!A>CK@&5ku%`Y!6B4do+ z+H&k0sDcVQXdLmapes0nDFD@_ZTn8Gm{~-sW!aRpK{s`xlwFoqdLM+y$Z;%&Xnt1k zUJ6rCNF>01o|Cf7{|rittOvNFIpVG#0s;fv3(& zwJ#v78|@$aAOEn=AHM(8@!0wauQBq)b3w_9Cmw|%`{fByX0>e?<#C#bDVkJ%OXF9knVCW}2|Jj|6Elnt`WX&f{ z9O0}_61sNxDGqfr52!6}YE_wUz-J+tC2z1bXwo6FD+}|J=-I;UEsgsocl;NQD~27P z10ojYn6?$p@LJ5qg_s>8|1PLyMBxCdQ%XSzR8iEln2kW(wH`+4vk^;wQbq#I7d$Ke z%$$x(HYm`ug|YJ?#Ea&_QI*E!1Jf@xD)`V728Snl6+#rBKB`f)IY9crM5knKAePFC zK;?Z-7pD~_oX1g)KFrmuAgVi;G;>#ruN)=T97_T+7dY|w>9lU1o|Rz3mJIm1P1e}8D+WE^YE=2y>1#o$$PGOipX5x5!J%B{uCo?DXNK?ufe+4;O z#Dek=#@R#^Jr#lzvgnvwY?l5LH%E-YxSUk02qpu-{F0{U)r0gU*mZM}+~E0T;{kfO zqD8Bg1SDn(WqFJYdLI@toT_QHrGfoYEj={JSF}#4Cd! zp9O8hbIMGBYJLV91RXgehcVbe(>sr*V^#|D0pYYQa{xS-W~z%^nL|QA@QZzlh}K$T z8^=owVAa0dlK{|FH7wO}f>Obs$>Ae_d%j9yjQd4R%tC<90M3MB=H@Y^^St;8Pw>Z$ z2uRhBegF7TbSF?JUkdk)AjRddwARM~$gk@+)`DExx*);z^=03W{n$%gkf@K&988|N zA4gr<5OIob#cwY!A(n`}AYE%|>cibsRAiLO?g2$GA(UEMZ9uT^o7ugVGI}qB%jMGh zE;?Rr*Y{(uOFOm$+zSx+0jimr!my>3rQqVdzyE{lLS^?3UB)-YAqQW+ z)3AoMmGvQlq67Bjav7r|ps2Vjb8so36mmct9g&cMqaBqIE@fsq`ar@FcK&W{UBJ~v zh7U#_+oK?S-ajtaTPc;vTxGc~V;j5J?d3(r!Km!6$1(2jRaSH#pC1NfRB&wiwB_aH zd4H$A_paZre`^JF9AiV~3MRE)-hY0qOWmG#;zFe{lW7M4RhQn)ovFmR@k`oZ)Cm-> z8=<)*LPAs-02clmB7=y)!tt>93)ZG3c(zMJ z!@}iX{^f6~2IxaYhKBk~hxXA6m33K4t()v3vR<$5$i(EX(*1aLTxB_aYZb^vjtgId zhFYj0L?*N{m7qPCs#$^^mY&fRlW?6#>b%3wGS{bRhI3)02n3iA8$`X8>8dyf?qBru z#FXMFLluq6Ni|!NM4)Frz`XBgoj;M+SS=n_p*gXh)dj^J=kvCXaRefv3^@t%6AJx; z?#%U+zIj@)MTFTT6lLTk&9(aFw>YamFoVocBZ!)2;(|82sY#o}^h=%Odsnqmin=D@ zXBuHg(Q?A2n9P18rZC=)c`Or)N~_K0UkU#cyD1kI(G$RSSD;fTY7m5eb)X>0MBO7q z1t7fOj3RU#=F_mSWQ`vX=7|mfT*@SIK>&dfS(Bg}o+E~{@5@vyRV&QXB|NM;b2wve zD$1ON(~J;K5KJR)B#3-Wo;<^N&f##VXf%IWSp3bU7@IyoyNGfNigGBkg8)MqzuF?G z%iJ+27#z|At-18?`pcQXUO!BXsSfCFast&Ksl6) z-Xj4=!er9Zv^k0g8klKJmoQA9h+@k+gGm$>p-4<>Q8YGTRc9Ij!=R#o`bEGAe^u}( zl?l<^vUnSjdF3h!%u#9MG?LCgA23Kt3$p0XRhRu|bjzL`ifB#kmxgLyPXR>IoEijg zE){W(1v8R>5i;;O`gouj0});d3A)cnXSA&4;$X`83yDn!PL<&4WI}>NDHSK#j+5PC z6D-c}8z+>=Km^2aSB*JlWKA0a23y`NLm8suM&8y+t>!$jiU1P_a;KuFNS6gaB8%}|1M}(%%rAZ z9{5hK7&8wqXXe>9CowU0MhIr&*hEY32n*-9*`v=fV(LiFua^X;xEEt20@>at5EAsk zsVVmJF}o5m&igMW>QVWC=N&U0yENuyUxRFLNI%&iED>b3j8EEbmhGjKTtSn;3E&)o z66PBGxuB)xKsg|y%<%>SFvHlY=4iwBr##-P_Hk_2R>pAi*q62({YZyurpgTf07ofY zTLa9u34{O^w0LmX`|*0a++JUf#~qp5y7c{s7ek1x)%0URu1h<*^nSQ|X-#FIE6AaJ z!*x+AAP8p1kM>mf?O(tnnKi^&D?e%Tj_P5uYkE1^fWMT7ddkW)%zCS-yzJY7jR6N!?fr1T zS}V8eK%@7vECir4$21N^B62hY(6|B#2x$zhwIGqHn|dtN7;58ml)=Cz-3_vNo>f#F zLJ`Gch?#T|tm`yR#cW>*$rOMbQgp;ok0RlSku(6sJuCw;xgo>=s@8h}X@KOrzkl}6 zf5`iP^8R7l`~Lj6|M~YzVI2FVQ97Z(T-%2fYt=4TjwiPeWlBbcUKj}knoi0{r*o#AHah@j+0UdVFs3%=eoRD?l4YT0j6{^)qaokL$&rqy zxX9;fefg@vD2#6`AwJF4@ulEzRZXXg_O z954XsxG4}ETy;b*5vuZ8{oZ|s{WW{LWq0#MtMuwV0N@f7lr$48=VIXB8MhCEI zFi3&lJA{KaFq*qddY_o3MkPu1fJo#TnekudU4$6jrRBO+!93!o^nm_9snw!^O+T$FlVL;7&v&w5>q-V zB2p=sv8;$HLSH8cghT;DcOFt6ieChCt2k2l9pjwGSxoUL^865W!i3T3hR=EkT)+%W znF-Zoe^eg@%~e(OFuT+StL@u!X_rg63=tiIjJ4L~av3%r&-;3N0U{@)TKA)0*NdAE zRm3u6lv+j?l>vxrTSR4hJ{^phmZeIcz=>L0D-)>+5w?1p^jIp5RBeCm#E{lU3;C|c zejuPDKlYtUtqn}v%^68V&Ga~WDJ|T6bx3QAsg_pRkj$@Z9VYS|?%c{{|MTyL1Z%;4 zlwtoK`+6@p4un$=-YrCvwW8XIEg>F|=Ev2gLGK7hlk%=GAM=czd(v{2e z{%mOI2H@lIacN6Mc-lCgcR046zyI@p{$Kt#HMi&ekDq^he|y!(^NIbkE+3zNy#Mo` zw7r_z=l$8dmoXSrpTk7g+XcnG9xO^|6=6;}ElY zJfCmhe%rQZTb9G*#1*8<>>)K=w@P4sDPsK)5aZ) zvarZqCX*32%LT~G$5U$rr&lQk=_oYo;PVuYvM#2m2;{2h39sl7DukmKlcIakL+^h+ z|M{O~-2M57_UGg0?_+;7@}&?k6lUq$%XO`Vz;IcYZGUj7y?1jMYK`dn`mNOJU&v`>{Rxx4-^(V%CJ=!bztb8~P^idXa-MB&X6VsxTl zKWF)f2-E+2jx;?9VD9KAzX)Jkh0el?SV|0_^B@1uyqWS^-r;2B8~ zoP?x|&pQe8?}pDT#f=%O6fGT8qdGxCkh~uP6UsIteKIU6akD^JG4c<_7_-17A~M6w zK`R{m;rP{%o^@GwVl-HR*C%EMlU)~Y1MwGWzNb(5$B@~sYs0cB>cNh|z zTLg5Ty_1@er=mCBDS+keOc+&W+MecAfAW;3=4}k&iO0pZH+OI?rQ(>i15W4S^i7U* z2-f_Z=78au8_zmhN=RrA!a|)9Ei2SGg6tP!gr`APlEi#^2I8ESQeO^PZBL(`Sj z;}}BW2S!QzAtp4_Q?!}FFCawAR@Tl!H)>#0Q%Evc(5_Cv-Ga?Q*t^cQ0nVu(=Ar8|X2s{Y3UZ{1rtV}!4C$v~ z?lDxtxJ%|jP3{;c(+VB4sfrhjm|}34KFGoJMF4lJr2x2_4l=T-;qnRh3JLM;Ua5uX|?>*Q1-~$;L#Q z@K_qlCdd(d>u4s$)d0yd!Oc!*Jp{~vaf-1Lmx9c>jodv4Dd=;;7Xx>QFfw;H2Lp^v zsEQ(XY^Cq6avb}zG*H=&=e4a;#=h@;46Y?Kh>l)Lb!RL^Oo2$o!CZl;kI~99#<8w7 z92I3CZ>yE(K=8%IC3Bb>ELsUky}ms+I6;pt;PkkCPvW$pWu8=ETjy+3xO zs%!1f9md9&Z)jxtzy=7tUKSA;_veCdqPvL{v4V=7P{c+zvl3%CaV(8N<>;00*p6@C zzIEF_fB*RHw{KuZ$9-Lk?RVck_D$cG_WAc8{&-o}_1PcC$4`NSL_W8lN3HD2<6%!@ zuKN7(ELz&aqV$0{+hkCrS<)A0COv?EUj(Z27uS=_1KSX z-wN0Da&dJxc|P{b%Qq%A84U3IKmN1c{uT{C)*@n|r;0Ji4?vgD({f`on{@acH4 zaiW|RKg?7a_@blaIYopgtP#~uUXp21kmXbXxVVo3iN^q*;!jPwLNu`mV1aIe^DjJ;P$W|(eomH7 z+)7bJ=KmP2HPliaTt!_S=*z+!3=xr$eD*?91BvmKV^^Lm>iA$uF$poTCyC0V=twZGk`JW5Nmye~ckSCbW;mI6T0l1?_V7*W7UvNn@^h7c5N-~c} z51igAHjCR}UQA>>LsWcvmPM@F^-bFzQ<9jF;PVDBwUjk{CRXky7Qhd+|)Qsml8OfxCo`)4FCm= zu5dof!ZgPG<7OE?XVco!vco+Qs`zP%F68b~M4(_FrDVP$0}{xPIEzda(TOqzDJZI0 zHYez46R<*vp-rS4#-lNJ*Kn0}GXgLI1XM9M2clA9gl99K8VIQn8F-q)t0Q`%FS88^ z&`OiJn`Eu9+3~zxVgsZ|1JKWgs>H}0b1ZP87eTnf!Z5G8U?b5i0&-trwC|qtQgrZ0 ziFPJ(9U}X+K;J&lh0sBDUD~4`N8c~kTdUec99&dusp|H}&);va-wR<~8kY7rb~I;h z+qT1nYi**U>SpqCdpY*KkI|OmX$FecJ0fu-BA|lHvZ{>G)eu18(!Mn{?K|y_se=Az;W!gRX11B zR=n>!IG7ka`hGOG(RX=#unG@m#^PwQ$)E1q{rcNqwF}($A$xnhV(+d7wH=RlE=3IL zrHLqΜ}{2H5-FmUe89hxQfm2SE$K%g``~Upq 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ℑm(z}$0>$9IB-zEl z5yBQ?j$Q!)x#Z7@Mk0vvn3Cj4Y4G5D2UMSu14=mG=QR~M7=Z&iQ6>OP9&ftOxx?9( z1gL6p{zvseklJp@?42n4Fx16_7^!?iiBp{F(@BETW<%p+b148B>E-Dihe%OkdUj48 z`#zRp_Zbiv)aKgY>QAmvBrFe z$-fQ5J%GsY!j!g$YfUVMT;3GZjL>1GJ~(ubL3KOZIe;Yb$AWP;Dun4bI7d|3)CF31 zx^2!1`21H=tzwFnRZ5buJf{lLNJ>f?vYNt^1Qo-yV9BTSJXrw2GMJlg(vNHkZHYWI zF<)?g*w^HW3R*JF@tsX+G9nSrwKVJIAj&5InLTDY8ku>mC2?L%p^>YLCEbgDVRp?? z!t^7_BO+f}en>=}Z^Ya%>lDg)4iO;*HXm#>KuEBABK)LWa~@9iT9e zF}RlKUjv56P$C*?HdR5)P$zef29#lP`{pU?9unI*+!#Y@EpzOMc#^Tu)eQr2kJ_CO zCZmQ4#o*k}G4hMmYc>h(kRz@5vy|#9cXL&R1hBo2(OMyxav~1ZrZJF+Kp;tkrZ!|y zmfsUAGL3;CC%YycCoqV@j;c~4VG2N0$;ez|^brSfK=rX?ajxfcyGD;9^U?_EZT8cU z$Cc+45LBd;8jW?>G$9dk`daz)wV>%_I_)X4kH_iDKtwRRk@#%Ef(6V#cmMzjT)`nH z_>+KmPI(|FIX+{OhDe$DEHM$oGq^5t6L|&?t05wnn86X89ceK0rj3$=m0GD#axK*z zjG0x$Rf)KiQW>flx-5-k>%Hr`)OK4pa1)IQ6`?Ve+S(WgFqhg)4G1om>*a;sKkp8- zP+=~$)P38$F0C%_A3q8+8mO9#vG32{e*29J`@<2jmg3B!qt=qaE@sbdAF89v^>*P> zxYcD@@#Rfb_hVNbhaU1g*0OLRV8%kn^H32YETz=7aVbB4{K-H>B%{Z45)eu)+w-YI zT3KRdIdl+_if9$C&h)#C_09CT~_NGAipe^$M(G4UPl)fU0<(#bZ%U|+E5}w zA`v@|WB2E}E@K>ZX{HXU1h{Y8a#<8*X|1T48iSM57+nEGtuE`)pS9guL1ECom*NDl zKl?a#cOOI1p@1)y(1-vC>#=X{9;2ww$0s8!r2ye!68;BEYv@{lV05-oti$nOx3b@d zQc(ed&t0JKzx>bt?fpOhLqY%Y`bI#uZ`<~~z5aGV+P0^fF4vcsG^wZ|Sag_bXZ+j&yuRGHRIV%ZW4Si23zkxdxh&?^ z+7fkzIOq^WzFuFKbqRcwN)b_TyIya#*2iOmi5>PX>v}PhZF{_2UlB_w?H60zSw|4Xm#I3zHrJ2-W4;27Mzuo#Z=)@Q{QoSg|V;zZeV0u(oT z{6LQxgGGpeAsTujf&o8Lx)3G_;j5IKes=-J$MNf#PZ!EBgUrW_X?#Nd{$+C)4%d#9 zl~ed8hcAk%Ie3_inPq-$j9!@&M~}lf>{Ekm8O@G}0wfnFk*iEW^88=&YL2ly$4{6W zfy`|JuW_4v-Dq(WrExLN3EI4R;wP(`s%4xlA~>iDbD43FF2SZC0zh-qNP%UZXIgsw zB{9Q2z(hJ7Y$l|h=~__`5rMl%PTGSM;Rw@7{ltbnp)N$%nN!3b43L1OPQ79jUI1WX zAw`S->X_)7nIUpKii{f2gwP@Tge);_&RDYK#_Sm^i`u{$EY~(<*Mo=-6lq!6&m?@9 zKSs@BMvsUpl8qO7&i{iIHe)E_1%*esr$wzx%2!Snlh3*t0fJ^mxw&R#pOl#CM|rYe zLhNyV@%U8d&j&bq}kUv{j6^jesGWJZ0u5PJm0WV3ELjdqvil`Ex zM|2lJndZTa7{o`)IyFfd53gz#F*K0eTI5RTi`XNFf zNG#F}xDbIza1-+vd8C)mH*JLb!OIyeyD5V z0z@_hbX=EuyuV+pGg4cx{c*QpOjwwYJ`jN!+Oizd7erthGU~Eau3dFIwwIT;x3@Pn zAZAs$-Y)&zfT*YIAc&c7uICUSD2nEjqk!qqc%bNAHNPYRhFE(%qCR zZTl0D7Fby*MAfa;wp@;5KbU=}sxGC9?8r_?{W!qYfrx2cugBxbT-?32Dk>sMM3w3o z6687uU?N18q2mw)1cQDY6i^cYxPlp&u1h5ba1&86b5}4cM2GCh{%n`E)Y`kd4+I|4 zi3_5S(JL)RAjh^Wm&&x(s-omzE@QZ>lrYnEXti{aLypVkB4e2J(YvdTZc8iRu-0PQ z*Yzet#&Oi8xQtfIv2UUWQ!zDDJ)Tc=E0qCcbfFCMP}6Q>7`hH4GmyUbvD{u?0RQ>% zkJ{SvkI%Z^99sYU{rTA1^>+RKx<8-)`TzSrO05g}`{$#*yj?Hr{`tYn`rMvvy}A`F zY)B^+5X{i?FpT5bbhsz^A*7suZqn7LvfJ22(2wUBg0iCmZTCXz{c~(Tiac!SMGn}y zeEw)CKn`Pp-bb?!3+tS{jAAR@bdaG?!hcQN7 z*;QIwTWewlhRl_@I>52-4uDjGl(?>!w$%N(J)WBkG1J@2t@oqU+Sc{QpML<7nl5W2 zLKhpZwKXKz_RZAV(%gI;z3+pFBm$LzkKTJfxU_%|iR$(38@`GL*U!5JB6+_9=oZ`tIXswN#^BEC7OdImY_>KY2XOV*wi-)k&2(xS;0;IGRK|1-y`6Mmehwz+-$J9P|z%2XG!A%m9 zM}!dkR(AKL#wO zrlPeL#0-4yPG?dF@%|k zK-5$P0f^WvI?kCFP}tmEuwGpH-(KAz^1fOvusJo-Qg zAb|kp7zNQm%>x_(@bJAV*Z8<^lrrcuyYDf9`+)R2k;VOIkd_kkM)bn)1`w zDi)`%1g|jwjPq>DW;KYXAvRDAvT2NXavcx_{7=L~`cl_8!7bY&^ zlX1$hw_HG)IE?{vj&2b{QcTRC8iQK3FZ?4;u@qWps&R2zeyS=bz!O|Ij7fIxYJlv} zEb}agS*;V1nq>0(7k@5)vYAtMDTKc$ZvcKtO3%@#tDEa|jrirtpB%8f<6#bU11HOg zf3!80A;(-xLma)UDgi95Js%%qf4;r0{rxDeT*v{pEsZBHmjw`yAtJK0Wr#4Ax-MP& z?e!a~A47&nX-kC8?EA4aSeM0AkwPxw=Jxryy}rB@D$mE$h6ErKRt9m^m4W~;#)w3! z%VkBN$8-Df$DaW3*T4Sda=G+A1RNBX(tvCnF#14;R!gwV_w8whBBQm2>SOPScpOJS zy{=kYQ}I5AnJ?>Ns=fCX8r&F^c^YUOT@YbiS5XZIZ3I-+Wm(MN^Yiod^|jTC;L^MP z9Hq8Hj9OBbzrDWx`Nwu_8!{n~jKQTM0+ADf8a$tm>t$g=0#cDu$bkW&A6u(cdUw@+ zY%ed@n5R+Y<2cF~;olX!8gNs!(Fc>XR-)=14;furg~oL$ydQ^y_kFwFZlCw(Fd;5$ zTaIHxt?z&Qdn06X6V=a6v3?$(M9khF|9<@W$B$Oa-@d=z|NMv3GWI*xbyuzH`ttU@ zl6UFr`XcVG5!vpeAGI?*qMo z^!;784?aF%+>iZnZDjkt!ax;99x_Y?umE%}20m)#QpmXu?P~h^_8kiya!_GnHo!wf z0PAw)Wf667Bd(X_+SU~Sk0I}$pG?eLmdoX_Jq_&k`T_td+UjDekH>S{^Rd^Z?Z@`q z_x<_2Uaulrmn9evfW^UxsjW)~8^=ya0LDmI$i|>rKwprKgR-GLd7e@9QScRcND~uNG2TNGebE+ z@Fb~Z-;y^sscI>~GDk!V`_%qd5Y%QUf3a!I&Dtn6`;2WByYP;I3cSy+^qeq~@q!A)Kaz zfrgW5gMnVp*r?cTrErjPPbhsZM;Eb3$sHOc!Xa9BuMOkjtH0t0tn76f7Z{U#uQEuF|nb^?a$d=ZqHa( z?&)Wc5VMO25p&^0_7UAI$ckeO56K9ynjujMK9k6Nh%=r+O#$36RY}?7PQV`uV`#~v z#h;ngktTgosbX2gjDynZF)?_{Ia*G5&8<)|$N&_b7K(9F;8$sjh}g#{A+nxY*^u>c ziJ%0G0@^kE^3cH`hA7j_ta4G2NihVQ$OHg*23(`hQz=HTFnNdIplUYNu|7*%B+5BX z#z{ae3B_pYr@_>bGp9cHzsyLixybUUoF^!yGZMtvJ!i^M0iV(FoXE$SD#AH!V9Lb4 z=s?b9GFw(=spAIh+P+LvQHUw4191} zJcZu{oZ@Au2? zQZ78kD7A25H)~}nt#GM|=Ac8y^?LhnZ!c=U-S3a*b5pUlEHb1N>RtP>hsh%X_2ZB+ zYOU*fv0+CafKY2SRRBA79oyb&GXs^5Or*Gvqqc^~tuir+h^n-TrXuLAEv1mC?fcP| zMMTBA0o8SJx5`CnAo{X28Kdtz5ZwOus!iOjR#q7@kcqZ!Th`06G6!ToY+GUOQADpxKX6H;~QVr5YLSp+U+-;Y{r;d*Ra<@Pup z$V7wz)S<(1Y{cQRz;0$L1b%I+54pCA)|s8L^!;gFs8rQ%`sB7eAMa1%%4J<|pTGZq zr5l!uK5D(ZzP!9JV3Eh;=cjQaa&;7g>+R-h1crt(dI47vm9eht*mvdbGKko8l(Lxa z#ddoRzdr=P`$m1Mp#Aw=*XqYnb@Tqoc3A(cyl(qPDQGHPfe&J1s zetYf@0BB2T>$2;Updgc>n_6iNkm|ZBpgNXTUS8j*lzs1gh^Ybda#?FxRQ2c2pLJbs zx7VdDs;1W8-`^3jwS}>8^-{U_E@Dhbr4rZUIG9V#yiaBX)A4+KnkXR^CQ(yW=|fb} zT#r$ikg0b8a4tlM+uj8oFITP?Znu|j-_-$$BIFY=jAI~-?c9BOgG9L)^*kl1gDLB| zfF9H#;jt`D;;IPb*ncTu=M9>rHcSU$BE%RMBZZ6KdB5N(-$XhAet=)HTuf`NMC2B{ zuuO`JY9Q=Ef}yUnXbXe!sCwO<7|nbPAwuR7w{9G_0X;^85(+vEe1`xLnK2QfG9_nb z7DWaEPd5pk)8P2n7V!hKI7W(Ro@4qHfaWQVVI!DNG@CPz3C20^fC-kL5BU^Fp1r~O z79gLD&6~oLngL(0PbC!{*&ZO)R#YuuyzKgB=&y>ZYN-VQMO8$ZFf1-SMC1^@+8Q6p zPSeW^h$zf`8nPz-JTGg&bD)q6sQ5M>+5kW}&AV}yUdchG zFD{T~(Lb9g0-(62XAOYUr7-FgOBVFhi^t~)@c@ZQ6y@q)MocHs17>=9lp-|w!13Y` zFnVlvBTC9pc%o)c6%c1Oz*D3<4GdKk4Y-t;s)-5xGCYa;sucEVZyMaWNELwWUX0^C z_{=*ZwrOFE7;#U=B^&|KF|i4c4MP*xh+W|BlS-PkbPzNE5ivhN21I0-mrYH15J96+ z6jhLzLr&&QoT*V7LeTRxe7eFkd7{|!6(9gW3G`RZYnWL1mvjRh7HCVIg zsIKG$k;oGplL-M*-jGCUt)Q*HKNd7@zE=!%G9TDW*LxSpp;-p%0YXG?j( z$4??6uBAUW?BllH`tSc)xs<}Fx-5lpMX^%L5WPPix7+KoG-n6&!px<1k;)aq(9L1= zUfbI0(pj8<1C9h=*Y)x7xji0)eE<9m^3Qr%UG4sHzus<-`*XcCRqgvxDiiW@Tk53} z@Xw#`%UZ9u>-BoAwT;p5_s>F5nca;EX=%s)G|>Wp0ME}yYo&6jT)?4g7t^?1N~!yC z5K|XnqP8x>{PB2P)|ENneNvj*CDnpYajga@o~Mrw$_mN z^M3#Qcz=C;F_3LP3KvH$l|>NIS}T1BxGt?A7!$NsheAKLF?J%TmCZr=D8zMXu0mjy zp_IC`<>=4bHU{+i{V8yM7Dj(*6{WuaE;z8%NDU2oUOxO+bCx7&+~ zv{D>=St=5VjD6pY#SNBqt&C;@GS=l{J1`%iC7+Y{Ze zKduZeM*+3RL$=4IIvo!NDb{rpJ9ZT}we_-!9>JJHvLoBBsJ(`u6=dHSJ?O_kAf#;r9CS-2vSd0aSH# zc|Nz_e*10o5$5r@tSSSMuh(UaV}Crsl&M^Am)xbSBIs-SC7-(0G=lo=kps2VlnFBN0XN(bv>B$;~sCC{RzBrhe`Yl-8M1 z8b);z6pr8+1O-$hS>hx}L^FUPs7;D75+ZOYp`(*ga}!e)z%U`597{JdW1ty#5UdAK zRUi(E4$hn2Jws}<(v1x-8cc1-F$>3aAH`$3`KG6wO{0hawesyG`2hm*qbP<0HM2<| zo!=Kv*`=Ro4#X@aOinZkA?D!XqN%AFDxJ?9C#?uQrrCk=M!Ayis~%jbgrCytgOLCc zh@xx3skkL3goM?5ALq0jaoUb&4`c3=qyfn)k;tT<4U?M2I3U;{3MyyRgW3c!J->CZ zr%uo?iMV)L;>1@ocbaWRbPg&>SqZ8f<*m&G*=zBPg$4j{5R=SE0wBVeU_eX;X0I9a zdKZrxCN5St4?Z3M7{C}3^<;o|XH%C2lbL5yP&_Zgq!G=;bPt9SgS$^Lck~7)tv3bY zIev)p-MOMWnrSIKhQzbR>E~N!?lR0d63mP_3b>sRPS?~FKj2BdHV;HRCGRpd`qB1e z3L$1FM9}ghzN3`Xu*O@MO>)}LU^LH##~93v!4i*?XXbH*hr5Gw3`QUd<77SLqAe8l z9w8YjM3bKuDW1JUu2Pu)J&b*4B$s=d;7`p)Jc?3EtX>bU6k>6ARd+LSAjDuJY0d;O zJY+m|6FIap2Z!)&wHa$1A!6arl|qX!oSCWV7-mv5X={k-1I;l=Vsi6RI2KoMfTF*M zEgj_kIL(7?5@x-zM<=w5^VH^0mAL~Fgnj!7dovd|i=^n-&m12ca4vn!VOsV(lQNk8 zrTB%^pFC8HJ3zy#>hwE1-*+_YK3{f*WdMq5nWPqXICltP{3djB(?lAO$kZc#+s&N- zQ(NV8;6<^5V}H&@>9oFrBvfXn7~d{NKBpwZxMW{33k^EE#`utPs0y5W9Z&LQ=)7^zT~iP+J}5eRTj=WeHTcFGL)Qp&#dTPemR zQohINYpo^&*pF>5Tnp80d%k`9KD^Jf7z(qBESJWmc2P$hay+)@%k8cA(R&Ak(n{Yq zWEe6o?P6}*zAwu+=3+VsX?t#^6*M3tQ`?R$Uh8TXVp`imhsJp%PaIuk>wZjlWn0E&c%>_k+l^kWn* zt(Ll69DJCn$@Ovt3@cK*#=g{r>&2JxvF4 zv7?JwMW*iV;%2f}TCeNt_huK{9~Z>e!u$Kr=ly=WT=&N(jIlpIYb(6eaPocJKL}h@ zYDpkTO8pS2LS<6D?AVo@#sT_I5-k=t%l}+D$2x4DcHSUZiUNs-&$LG zANylR3@wp~8?3e6Ua#N2KZgJoBLG513?e!@P%Cyb`q8*Zs30ZvC=%AD z+otFL2MS&haSSD5Y8@rWX_{e%F}FW){dfptSgJDNixvwJmwH-G!@%#4b9wpu4#H41PsJ z|1vM4Ov_3X)!kF#LhjkZJ52jdbJJuH#cUv8C-@=;e~pQv)SgwPms(WKLb4AS#Emhe zl!8cz8eccwPRYWTT zCqx^EFw5C+-i$G5iLtGy1X(|}LLUb+6A}QyG=WYxSqBLdWdzmi3o(CYWMN1Q$Wo3R z%?tn)W(}sF7brLb&*PM_2oRZ5JmN6kL^Um?1`I|-VZeuT0wY7`nu|oe*kl;PPXlNjoS0y%I*kKubM;aoer{7~YTAOgnwsJVF zD?cZ>$=6KfK1Sw1sI+}@Msree{^bBOvor2^n$#dsP4K{BMh_Z9PX|!a8bW_rp@4*y z8J6|&=Q}grUS8IAF*gId-|yf5 z`h8hyf*FkFfK&=IGn?xe-4(|ejjIESL9I+o{n*Cd-Idc_7+tiMy4$lKy_7O+7?7Kf zG2G!edaI3@+p-|iF**{~QWIe%+WWCA%lB`;-M7c{`FMT#*4BEAzAVeKw4eI%cs^ck zFPC-Q_D5Z+*m1dD`p2;!do86cRg|{lc)8pn1x5^xeJ^EgwQ@+}7^RO=8Uh@q$FYC= z%iG?E6E3wJ+gO%{?zPr2R7C629IRB9aa`60PDk%uL}b5RUxFBV?7Me1+XV{}Bwb3z_PV^;P%hM;cQ7F{aJ-fU++`eN^p20Rw65-BJ7nMQ zpV+SJ+joD~{(1M~P~E_d33YpK@UnlrJNfl?{rUdSzy9_e9zX5=v0j$#<455x+v8Fh z`e1Z9o~1VFJE7_Gy?_-|J@&8+AOz|A^?GrWaU77oy~ec=6ET!h5SX|iBG+;ZrBbj| zDs>wI3Z>NR^@?1NG1NwDYptuPw}tv4YPRj~rM0)W?|}62d2g)|7f~zBt+uw5W8Zt% zQmND?U6-~3cq!#Lo?L40hncz>6O;RpA==5Il?q@oRC&(lS0MmsT@RT-M)XGdQVUkFukZ&TtiT zvyq4x27)*%ft*ky4Ic#=2*}XX96S!YIE~#bXzTQ4sN<(Vf;g&tn$-`g83d*iHW=VZ z@=5RJN$&&)$M7qPUIijqGDLCi=pHRoQV}qGZz6CCCbZ;{h>U5khvDuS9Ip6|5yVN+ zy|_h{L&Pe>r_)hBUPjSyyikB3Z=i}!RfY%1ij;u2o1JbvQSoy+jK_OCN6m1)&?wY= znyZ=_5oDyAe-VBX`$IfA8_|cvk2bZ_-0*9;dLqY@DTFFMc`0FV$I(#G%(0%$WfZ|* z+<{oTNSTclC1%tQsKA7PnF$RJFr&)zA`E!g+{q&r2~yZVfa=jgQ#LA$Y&pHj#S)DVDJ|PfO5Pg$U?eiY1-KEQQE3ZD6W+XOEWK*^|bR1FL9Q#}J|v z*LhB66ak_!@Yw_DOmgtB5QJ7HT?9R;Y2gf7Mkb!=W(5HOjF>7#UT-czn*q)-l8YwqoTUOlAu^MszvaY` zqr#qIBLR06X{{lKISFc<-Dsth@FWUkBD#&BJe5-NGq}ezVD|IV*oY#8SY6|zrU>j8 zVfcy%RR+V-QfH@+Mfl=%Pn@NN|+84e z{uu)vLNeCpac}Fz2@S0-Rm`9JR_e0UMa3{tyuQBv{Q1K~3UTEMfX99i!`iNWjGuq}T-H`wt(BK`!NM^Q5gG2VEEjSZ zwCR4JlRG}P4P1#zsb<5OiMecB?>5{VnN1Xdh`9GrN^ysMKSG40Du7Kad@3+h@B29F zvLK?mKaXcyS}E1R)P#tRzSq+J^4s_O{jMgZ@OpbO_hUay6djN4fW`VaN?pvQ)PhAT zH$>=Nz)(&4=&jbii>WPzWehj(+C|hdf2Vfr0kJN1aXW&Jz|3-tTB?)p`vE}9+OUTR z57fMm{`UQ~t!wYY(@Y+3udg3Jej?&}xe(Fqb`_Ian~ZMmKYsrCm*0M4F2^y@T=sp4 z)KVQ}xvtxD8^_Vsg$R9DFhejRx6w%tVE^&wKP6HHrH(N!%L^U%<ZmzQs5D&bdHI6xT$ zH1&}L{+=^WPht�l?7@HMcR$oT(5KfsJkJ{iu;AKOOFfBWu;+rFEo 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π6Z0qRc2|uQ@Bu0zmOm=#YoX-7*SN%vBp$YqyXU1 z;Ta8%;X#xGG?<;3xtT|06cy)CH8T+b%TSRY1qqWI*$;IG#bjQD(~!pOVS{UCQpDYR z?*Yk2yB>%Gj9i~Shh0Cdy?pj3Q5zynFUcbTd3gAA&8H}3%?^kL;E};PeXd|ikK92- zhK@;vGR~|vmVo4g!5NDKQ%3DUtxl~tl0zRu-A$O7QjRddRR>ap8X60cp_!ZiXd+7y zOy-sg7u6BZurLajDAvbNVs71sIxrRHu13SVp%K7R4dK4GwTil5Z##*Iq+XtchpLWI zN;j02mNc?qW{#j{;l#uu9vN#uwJi7hwU{h$ zDgvtLFu;nGsyZN*En13zV{Di(&2yLtCO2#JQA)}8f;=kBJqq+--Nh3#MrYv!t~@yR zsu>z}^SP;SpPyhK?kj_(dvA#& zmf{1CuIqBH>(lNKoQKhyvFqrJwBPRM=W}e-jK$4MEyvzk>*uEp5#8*W#3@_`G0(}4e6#&YzUN6_OiuNv* z!RgTB<8~dRBRC-iUx<(y3ZP;E2fyAfRq9d~0B>FQuYV9?DfH#bClP7wczb^bz~BD# zXBJfLfX;=NT9Leu0|;XKC8FLs5>;kEI(l2{2@sFgOh+xX*5%{<+qSIQc0ypLLJJe@ zw;P)Qcn^@02praR+fJKCVJ&5FIec+>!u1vF%k%SZor-$_BIX**+JN?hDrjb0(f}!SY&QBX~d3mM+tLh4|l%QM5U9m6pm_dOeYqM*R?eAM?#de zEssivn9a;IhM5pQk3ZPeKfJ~<`X^43NeB`itolHmVG=AHfSHMKsGB?V6dojIJd>V$ z+O9ij^hCkcwE6C#tx6jGqy3n$B~!4CFW{Ms_;*|RZD$jR$V@E;D=!P z(K+&znZ?03#+cLWQ(2fZ_d5=`tDdL++FEA49WfPGHjKBR`9X z#~2*$9XWFapGpa}ARxO#j2fa{1em@c(KjYo7tFFyk`Q1-ZCVPH-LusJ2cqoyJgMnf zW<>4i?hLWiog$6Pz`;bs%v$fv1c+m(2)p^Ty`sSWg2C$mBVv8$G|r+VNS0ItkLJXW zW*3pW#SdnbBccxVNpy+(Xlg@;XEYcPQUuG!N`QH75yIm?_3`Orp0Ax4z735T3lT-J zLj;b{`w-?}&zqU|Zf?Tj?jWf~$dH-Xqs$^23}*fyrda%E8V^uVA;L#>(xotJvb0;x zxm-}Jv#j#zKkxt`l6B%a`bZ7u2r>>anFSdh+!O(Y8Hx}wG9iNZc8CaYjdu*f5au8W zBlN!ngarZVLfmu)uS=7l@7zbwtkjr6mo^ zqQ%drF2~&= z>hWMdBA{hzbe^eZCX5^=IZiZvgr=D-fSZsE?P`V*ims+3L^feR3cdFj(8mphAGXjY|Yb+ zEd&EZ%tXu%8rf%wC0JO3gpkN6^l-}~=dS_xL@nB>kY};n8$aIXRuFHB_PUUnyx8B$FOd=>= z))fJKjQxIlI=}RNUqp`9WLZX6l5)G>m-G6npRONo`^Tk9t#!S8Tsn7P1f%QyW7}3P z(sk^&h@*$m3nt@wFsE@-bBjv zvKOIRYS%GTxh%9ieg5^|F8lHP<(GcDXzOEd&wu&+_WSPUeGF#V_uc(gYIQVX+SYAv zjgY#jEX&?^uBEko^kz+;pPp-7j^n7p`?0Gw5w_8+w<6?d`~9}mm|M}m4 z`{ma^GvYt~{*TjIKmGDlMMm%6zkRzNm-Tu3w7ot(zpCoxa=E@=nfP>i0`T*;AwaFm z*SB}q@#U9Kqrv^UfByWXkKRYS-R`w63$MZ9u4{2ODRerY0Kn9jbsaX2<1U4V4!bKd zFQwF{lOPeJ+8ARD9pv!k%P&R5L3{7DuBYvEy?sdKx~%3fw3Skjpe)kcv2CZ;$3hDi z8Lq7#r3x?(J)WLVHgw(Ab*XK?x3(X>AJ;3WRu(XALzkrjBC)7JS0kp=ayo#3&gJEc zpFi>WWiUIEu%HM@9qBNgQry5LMwn(qC5z!x5*16F=@v@;F#32vt$Auw5Pr}t%v>hD zj5A@t!kLG_J)ISzc2Cd)A@Ku*uz1KyIgZq9D?NXAi{L-o^y;o1`|UXaU|=8hztzoFgB{~^g#zou_naz1o&YatqHzS#=Hk^ zqNWoQCDoCPCkG2={EY7nS~<8o0uctqE`DH!vnK4MX5saL^E*2Qb0$mV2OA;YDRdu! z`~F;Zs97oE9J3S;1)cEp#U@Mo(?j79Wf~{9aXqYmQ+!0W;Su zDs6yaxk9q&PtCVSF&b?u{vj;oFdTbE@pso~T7tH=p-I#Mo1=G1C5QGpL z#Er{xFB)vOv^<$!Vey#EJ_KX6M;<~@8yGn_6rR>t7%ahfqzs_71_(5p-k9}xwtt}N z6#O8{yF)T_ibRKl zV6{{LU@qAxgZqqOip?l=#G{WfEDL3!3giGfR5eXG%{{u75#e?S2*AP&`A!}qUH37D zhj@Tt=%9h_;N&_~2%)fd5JGSm=0GsqwU5FS=ANzhbtwUss|}p6fd~yVaOhoyDTL?l z&{aA3;`2)RwEc@c!QF@{fo^cf=MbH&yQ3R2ffgMN9XbL+MWNmrnlcM8b1nO^6ES-P zm*DJKHE-|;c53LD@dpDCLa&`$ZO)7c-7+T&+yi0KF^HmJgN)5`Hyf!s2_PZeJpmMg zI+=SoCQHXtVHV*4#>AfJ8mOuvGLaM>Lqjx{Y9ZASLU}qZWH*xg!WhA4?Y-ApqL#&Z z3g?5J;yjC)lPCM>hhbcRIx!U(z2_d`nd0%-l>lOmq{!3r)3NUe+}c=A+i@H$#CUpre(6Uu#nbDl)(Q^C?WpG!5QjSM?YQ4h z+va_ITt5i$<@xz?xe&>^tjBQx_~~@IU+(w&zAQ^Sc6D!k6eJXua)MH&oOwBo>lGX> z?;rciy6^X)<7qp6yuUv^Kj|34rRmX!9(&{%;C?$+D%!{LQq+#qvi|=2@A^q=S;Ba} zH4T-;`}gkvww~(W{`WrxW!(Gyvfp?3ukYWSYd8E~|KI=5|LfoX0jzr?nnE_@BaYs*OyPVl>2o@bR%zj+Qp$tEWacI9EySV~^gY`aY zE#VYVN*Oxtt-B64z+hJ*ZOhqp0Knc_AL_2VSt(RXarM#1r!Sw*&!^tTurY=*!|l>* zSxYJV?M}j_ER@_|ElUN&p`$EIA8ojP`tljTkA1J}O5pc?0i>fH*UNRZqarM|0t-1g zn5juAKx9O!gQO7i$FcXa(Q;B;sGjx!`I$&a-E|B{QZpn%z~o&dX8{2$%iKpO(+~2bo3(r5!Zx>M zz_8E=f23%f=ENoT__%R-i8LT8Rq)`*#JG5jA(FyY1hVXJAPMf6z>toh%Ggh=7=X#b z|3RRS`Y!@a83<7c39=r2l#+2g5mCU^VVYM6brzYPS17R6Nl-~5O;l0r;1S~!Cs8bn z)T-ii_ntR5?f_8|xMP9EqBF=Kx~L;_bGD=jvvAVJavXtCxYG0p5lNRD^yvEH+a-G& z)9V(4{S}As7-{`70rjk(0W(sHv)-6|HXdtc~&K z=tt&SESPD%rJT1aj2O?q*1K7bZ;#5FCgZ zDcC?@sXEFpCn7o&+?QII#WVie8KHMAOi>7Rvyq7=3p8zJJj%aPQP_Rw9#D^ zLp+WiC$7afV5GL#H2SoVkGQ#;dNyPjEd;7+q8Bq{1jy|%7fbckk263uFKX<9IBlVc zIs83EB=cp0)|Odm5HqhZ4T!2J7DF;_!*&hH)pFC+VeYjQBp7|zgnZ(A1z}FrgJ&Dl zYM2E_jLBkU#$1#{z^(PpQnaBW9}1(5WvInokI{pPsowYdT9@FK#2gF)Eg^8L-!?#M&I%vkW_dRt0G zL3eMh{r>g4Fs-ZC^Rv`;{rk3RG`+PAOY|Mh?T%lpUoeeb82GXS*X z2$LB#tJ}68Hy;XSV{}kkYQ22Cy}x~&&*$D-+mE#@Z{NSW!u@{x^5xg)Y?);Y-OdZ2 z&Pq(B_SRc#M{80_m1?Tzr)LKhmN*gjcE8>)%eIiv(T-{>37L%|a{KtuF#tfvxV*ou z>*;d4+gNHTA8+4&`{mbuxsRg{Xe0!HqPNkR zxE*cVRxmVoM7mw?rb8$IPFU9Z_1o8VSvbK*=tn<4ZMBwCWxwwg3nK3O?br`9UDmA- zj?qE5?VU*&QG`Z608onb);^BQ>3l+VbYEnN)^|Of(f#uNW~!q#uu*EM%;?%~*ZY3A zVQxmtg2Gf70FRHhE=yOt4cJ~k`}y+?DmhUpU0V_9rbxvR%z;HPBiNl7Fq|k6O&>U4 z=yM|C%0djs!eb~>KChS&zxLp1AYl;+#zl|-A9Yxi8wtrKh1U`buqaYI-#Imffa$sZ zqeelB8F&n|qZo96$L{1L1Q4!Zz)#MDL%7C~!L*~9Qt9An#J33SJ20To`29c3 zAESUaHw#PU7~@QKL4Z}klt4m@2&ZuKM99W$o?dP-r%5q45vgUy+3YdqTxiP7h$xJO zaT4DXJ`LUl)93@hb$A4NW^aI`X=39rN&k-*v5Xf;ZVVF>lffMKPwq|hs1GonnTD!P z5gI0$HXut6yA_xXUSPETFv~=I__RQ9x?;){I&8{_IuUWFC==byhCj5p?&-=p`Y>}A zvXR7D&t>+bI|9Rac04VKJXc_hfH;DdH7LeWl|@|`_8t%F5pjIZ^tAERE}N%^m06hW zAZAt%I!tkSq+G=_A0q;@fyh5inI3~Vc+ARFmCPRF3w#JCqQg#Qd~}2H)wD>Rm0RAw zd9K6bSq@X$)_Wj>l#NKnpG{A#SeoBj+R{rbkm)=}?fIa@3a(%a2Rpjs}3JV+01B)c>E9*+>+dO#G3sI5`r+ zG*ymAJKzj+52cNo&cZAq3p%EiFC^8i(fWti+21*}&CqJy2GP$A)2TVSM2;8Nx81F}qr#F&c zx21+k)h$pNM2FG)vQ$Y5W9;=96a{ccL4!ygb_8Ie5upLGV-gCBo2d?Pa)4T@X*We3 z$X);mjFH$zc=(7x*lHRe5epa$ZQunl6ckRv#H=>FJA`Qz1V#|C4-t>&fEET+B$Pxv z4Qp*TbIvN)Feb&214^?&%ml&wF>p6cXpSS5+Fe*IQhSq{?+BqN!mi3RRVWS|aWm7K zHYVo5!4VR5K=5$?1y%fMjtLXuLe4YU%m9sY66Z&2Il5JiCm|-HK;Y()&ig-vB_pp~KVjXfBydI<#jth9Ye9z;v1y(5wsy4zA~t>@rms}9p4r6d`FTvh9-3iIe=KklRV z&tE<{dOLOiJD-+qJAMEDU8;`v zXve(}t?LSPiHwc=`}KOcAH5^XQc7J`Lsxg%s;a$~rMHd__4Kskx(|Z76e_O9rJ^Gs z18^Q34uHVO4`PRd!(_{3Q5EFI;P*2CrXOM)hv2TmqMC?FLV%QUMMyeE)Gi2Uo{%RZYS#DBfCZyF(-LOXQC{Y# zv&YaRAID0_Md738&>(7bGp|)bDTI}nXrvl45NHpnw3+KLLP91}LzwPENCA_3G&3^_ z`aw#8A5ut=FjN>e2L8&7ovNK zQj3UCEiHF2pS7P#d^0y^A*rGn4dpat$B(xB#~5c$kYa$7P8P-RGfAo}2kOBf*bAl(6|#XcZr9$dJ%W#}lv(Im9inK`t^05JN9cctv&!ox7; z0HJ|~2X!sjTr;)~5D6T^0W$mS7!J)|F1`n74v{FQc7RDv3qPwI;~*KpbWZ{hVRknu z!MJ7*RTiZ57$6nIaUI!#IM|11`s3jN%gxm1C?Fdb12dndFg_FD&`g7krak~Hd~ic? zBtm^CGO%Ne-f*~@FO@aKxJU?&MUZf*()%DT<_0K3)y#=GTnK8ZZZV>VWm~}A%xYmw zGP)5UhbIIgx-()L#se@$96V5-c@CfiGTJT#YP^36Ttd zSy0ei!O6qqNV54>GYTn*V*pffj)u=t&>|<34*^Lc)p&G5H0@PHhetci0H6aMBu*St z0K{O{A`r17j^0_sLr0QxqRI>M$|5!rX&mqK=Fl!3B?LF{R;GgjmP!n9vS9 zoWC8ALXZ%LTV>hgEay+3XxyPU)uDYLx)dR%aU7S+<^B7m*202dc-)WE`9y?u zDJVHZVZq*Y40rVF_2%Gy`B+cuvaFn(Xu{-i;kqrS^SQMKj=i`2IL2+DvzoX2u4=?Q zOqX>#jzfw>@v$xin51$aJ$)J+PN&V3wJQLhpFZEN`+xoIuh;!{KAmcPiVag z-fn+r-(_7oz<%2ad?}@}+~2Q0905SR>kyGXjF8UfXC!f9D(6NkFK47;!&=+Vr|qwQ zy=>3h)AQ4@f2bOmGBXz@24NQAB2+|<;~*lIdh9nQ*smAs>S(9!+>d*^@87=u^X2)u za?zvpdpoY}xE-~1yU(dC*y^5S(KGoCuetj>c{_@MO zWm!$ld@xIqMa0{25OQmWpg%po?E9UOTJKcJ0k60F{W#o!9T39Iz)C4md);nFEo9`q zwRK%f6+{BSrLH!VnBB)19SJU%3xF@{!btb~ZZ_6+B>-k_{RXauS0e0VjHZ2EeK zF7HR{udlDfyzjSYf4P*)_R1!4>V4@~Qu7@7v|8@2BPPV?|DM+ooy zC}~I6G(iq>t!5>e^>Nx+G9~|nN04*_;>|)WIRNqdv2BD0fcp=6?*yz;dHI06Fpxz= zo`%^&RahY9#8JeUnTO{X&UPX=7*fO^n+&3vmm)S@&tjH~^dMaRdkDXNk+|UMcEQKKsZ#!9N68n zWreT+iFynlLyYJ*rWVpPRTel#r%C={a3KVtWa8zE5ae(If;&P4#B8c&t{F~@VFmt>c^EjR zF5f7YO0s3*yvI1EV@;ZBPyhnT3rF4i*0w@Yx|V=gWeJTR^z<5V-d~Lc#Kg zxE`u8qw$m%n?+FIGz^8fLx2F8_va84F9@tTxKhypVwecSc4X+grJBpvt?KY5K@scG`4yi?xA21;XaHA9Y!gFOuY@NJk*3y!+J@z ztkn$E0K)tvlL`5kN$Q&}@*o z^)8iNI}m`8b#g*r)UF{oa$~mvj>rXFxD?P}k~)Bg{^#Msrm@h~iP*rS`wzt!5yxtX zvrL%5T~z^rFjMhjy2OMGpc>){ixPQGjfqjz%r$s;92PF_ro(~EftX3dJ2D(C;=VJb z>lRV48O>Zc?O(w(Fu6Py&1gE#GBY9eG0?$Kqi=RY1W-2#g`D~r%F*^j3yUEdOFAYI zx|6$y*LX|=Fntmtt@8o!AmW3bG!;uxo&;*52Ce|kB5oRqm*zMOm{@rkR9O}<9o7q1 zA7<7NQM)>zEcNxv>wepT+=e~9oOO6z7u5mXOBFC9VG`y-!xRx&3No|sQ1`t#qM4Rb zMat3k`>_);0I1phc6*5V2zlGK``*C)yqyu?<@LGLl2&eD_uIbV$?QkUx{fx+U@9LU z*Z=rmfB)M*{#lpvrv-}}%&KwqApt@kmGr}L^u>-+xc<@MO_=5XmBudiQj@9+C@C*oR! zMYIhBK!V%lc0N6oLO`VEYQENGZ+qe8zW0|ue{o#~&I$jz3u zUM|-X<#DMuUL+s7Zb0xsaa?Phx0_ibD6 z_xrZ2+j+aUz4d!*H$*YBfBm=Le*3a*r{#bA`~Udk>pwqz`MhmUUB|cI|9O6TaoU56czZ)8pjNbNq3T*^fWKSwjQ(Pd0^d7RII& zu824(G0a&)1tI?7U}GV2NP7ni&aRrq7qdx)n&n^ea4Z=G27o&B5vd{RE^0w*iF$p4 zvWkiX8!T7^MsiV~|dv z|N1YIyg-8vBNB06b zeR6j*a97HZ0o0LMPJsbudzqmPlT{J(7L6*O{{K|{NwXwbb|#2@=bU?0)y&@W=N@Bb z1`-LNfGRXc}ML%W~f5f6ue zN3wYUXqUkZWZjuK9p1V0?eBUZuw!-m9RX6_(Rsmi)u|LPMwCxpf3e#{B4X_JLr;}A zuSiiBxgh1f2ZEam3nGf7cw~sX@*2Z# zbjiLTN~Hty;Ok=FYR0xNV-4{PLqLu?M5H#T1KbJ_Q_)95RgtHYyJtOGF4g-MeGB0o z*6JI=uBJ>QI?~-)2GQoil9xr5l2ClgDm>FtW%PpOhcp6}L8Lyqwc(?_ERz<-H@hPf z345pT(TA#yhXiIoBuOV_fD~eCs!Zf=iNsiVElg2zoy-n-wGc1~p-=>w2eIT{laVzt zRUlk-Sq?ZyiY(G*fti_+X0%M zGgBoZkphT?vLt+HZXraqP8_Hum@Z_;Knv?-UQ9b!5HgN018;n40Q!_gp zm*eqn-Oe7qZfg*U@O3qFJUu;$)YILm)Y`Xxy_u=vJxxae-`O{PT^MC!z zcOPEPmo^=aW=8I>AMV~f+?UWF57*B>d$_wl1iTi!|M1)_4#(qicUTT{vvcoFyCI_W ztv@%JsIol2e_yBN!w)|?VmTZqSwj7Kc{!a<9?`ev>g&3`es~?t!egE$ny9Ing_P1- zv!G=@mc!lMt51Te14F48v|cxfChWsT%7sbRb)A=aIUf4De)#ZQr&4NJuT6q0mF;|K z*K0kTmT7)@|L8y=nCZ*=525z>?$OL%o?i-yZB7fAOBN7CVCye`_(y;6)2}``9b}s4 z%X&8VYgb0CJtB|mC8(5etf<>T`E7E=E>*d6ohUrQipB?o(Ivu#6Id` zax}_eE0|b@Bg=D8PE~Fom}>&k5e^!~!AVh$u{A-~T_B!gN(vIj@6A~!pBw{#9WX3n zcmPtlGbD|BzTZgv5TQLjJZJUPotirNfF2VXPsac?()(EhS4+)NM3~e5kzbj`ZY&-``EhmS*F8RmEcZw{X!yl>cMuH^jK)~z zBamf$wBOGo_FrWDz`eH5M=-w@83M)e!H(3#NM*m)0ki4w!#5PqC`Z1DR)KR zb5U>G`#45E{#PDTlun5h(MP4hFl5h(CXW+PQ+@=|2@)6Izj6sqbGQVK_q!pt}q zg8~7Ah+^!`$(+v1WNvno9tx8%qj+Q`0$5l`qJZF~NU62=40vKS>pkxYUK@%)&7`u5_ACa$71)nTOCdE;fK2nN zD5VAynABW*%NmqK&4{x1dJNXd*n~S1YuAk094mE#T+B>EN|=K{?D4mu8Uch2H84?l zhzJo~tl-0PY<&}!I!~d&5x%+gPC&b?wNwg{Ix#abt7}jY^R;cwd!6UHR3?&I z3y)t%Om&j^I5A7DRcZ-@nj)x96M;$<_p-I#wx;G0UZgI|gurz@t1E~PhlNC(;1L2$ zMUIC%DRQ}7d(*?=)sNqQ{r>&SJiorb^S}Jn*MIS!e*N}c=W<%RZ~ci9`~1y!r>VZV zJO17mU;gE?<`!V(%N>qJ2hROAD_Z~o~pa|?sclB?fmi~%un({HP)lc4h zc3s!&`SSTEUsRII^QA6x1g7cGtuNDIqN#_Pc`dcp!c#T7bk%T*u=#jU?bmH}H6rnd zwl%L#>B@@eszpjWpPBf0I81f&aCg66w^FzeJH_REZEY<@=4C#g&#i5UaPRZ7j8og< z)$51(xNPg%^SlcjjV_{P_$E7tacXE6<&Y#BN{&aWW4XVB@CkJ;|rku_**CmU+ck&Ix>4?8SB&6Cm z;l@-sCss{6(Hsu&@HNJ+D*%C!MKnCfWB?ekoB3E_V&A0^X~o>3(3|9CUr_dN#)xcq z%#fmw8s7vg#{`f=7!9R+{y+j5xvA!e-Fs)sw2Uz~Bnp@lk`zhZLrQe=6Z(7p-7$lb znT5yTCnLL z0*7&1)MB`9;a)I|xoKUc{nN6HQHv29n%~zLLowtWJHE3&BSv+@E^yidk+d6u+(w8q z?N5go)m@As+PSeSJ}k2|euA5)4kt6{C|hoBRgVz~-^T8eWa5yFl-iflz)hi^?{Jt9 z!eKYb*?xh!n|qOxNrrSw^dE@R?zP=lrpzzF09FCo(ce9;5t+iigT8Qf_q{jQQ`{fq z;lUW!#=yBThT6QS9l@N6^89yNKM3Q~AWqpj1yfe>jGDH5K1wMTp5J2%jS{fS-!-&M zHn3_0%!6_m9iOe6TWZjmX(Viq3m_w9^3~-KOBUXGzfF&zGOD;DR3mr481#;%qVOil zO2!}|d{xbjW#9=xutY0r?{sdNwj$&*D`a@)xb%7-%qYF3#eV3{j8cJfg?JZL>)k8u}ZfSxdVW+7!{^gYAHob^PX`Q3WxjXfXZeo_h2MV2GGc6PFYXfn#=@+ znVc{Jq!J&X)*6u@yv{W*tTbd~Ph>bpc%2tw3JVsQrjq{a46I=0Fy~s#b9>LDg@UNg zwVS7ig+*9Mm}}uGRGGu66s}bOGV^Hy;NCY73J1biq!h{m0g)2A3Qv4HsNAHKtog%$ z0Q0&_7J@P%hsRnqL~?Fnrc*f4gdv z2!fEzQjgZ3d75p#m}>M+;YF%|+-#cWzI9V|_aY)igrrVWsZ!?}h*IS4{(fsKk?6i* zQ{nlzsF|tPT0v~4)iS_>^iTx&(J8#VQLI@P;ZS8Y)WgFWD?RUoItL;_30_!_)is_xG=!*XQTQr{mAQ z2(@LNu7&%iu2FcNr+B%Zx2E^U>3Z3w;{Vw{`u)#7Iaa|uS6*f_Z{9=j35z|AU#ny1Un=>va>(hb(#c%lW0uLPCeTMK{$=&0<~-MA5cJ za2B4XWu9sZH`jF&<}wx4emor*v|ct4NoirUsM+;;;UW&K+g3!5$GOznd_5c&LWC;KrSMDq2rra?kxb4QYV00>oK}KZgWG#t{2UCY3!|Aw;;$A>80rm zfxM(|-lUZ9?&6WSsg`lWMzd%s=e#~Llxa^e1OSRWCu2yT$NG&MOOPEbh!}laL!dFd zfcX>)7n1ySIei9F`JeTI&O9=wb9!TD3y*LRdw9xScmSLrqKxVvPTYhKnF#DbnKBvf zHuB4n;-XTdwH_XxxH}?|M;ft0h=|^_mYQ=w4$6sW4q>q+(VPPu-S(o}{xHP-#%1Kyo2 z!c5G+n;sjHvm%8nkV!aJYLz0U*1JZ4HQBNB)YzXA(>u z@O`rn_whI91#j*cp=2No<#}GxLy!?eRF-Ei2!#geNOhS-s=Ad@gHVg44~YO~@wh3a zI9;>(33|lf28TN*)B~z!ISe@wv|A<&02mn3aG(<~0%wrL?}Y;76jLpo5rM5W9;dcX zMbw-HoZLmn+RzX~Q zJ3;#9GZ!N18rF2;)Y^eT7MN;Lf|~(cYgIQj%VaMp43d}g*~5uEIF5(Iv;fLLHNVVT z>)V#8{BdntBC^NB-MU@Zb>k8DMAvPtwXWM1;cCvz$I~f8NwjyjXlp0oAOyB+6;L5i z7(P6`d-v4J^!nfZ%nyXYh~Ldfo0$pKSelxIb!4%kdT0xdn>IdOcUz;dDBG-@3Nv z=SMK6I!BO)gUGbkNkF06m&4(5etG}nyISis%e>4_AD-H_3d`5O{`x0B`6&h5?ftv= zclW1mdfu*g_pk2m@816S9#nNvO)U zz3{}-GP~(^y}rlW2t2%g{ngKY@8$Y(y`Hze)lvdHO~Ui6VdiQPt@Yk@fVDso_VCl` z1dHAkLNDi+yVD&I+qx1{t+PncuEJdBsTAtHB}zsdft&%GR4kpSS{S=H~YP{rk4AraIMncXz^s z!gB4m8F!&AByjd!Vb{jB{7ksm>B@&iTF>_1d z>pleztxlHF-h|5uFy#|B2fLK!BOolAc^K}cmOQyruM)fBC$}Il~TCDULo;Jjg_g4BYfU0@1hMb(4blEgdK#8Fl88hVnfNpT-a#a8m@#RUo-V zxejc2$gF8ER2r%egn8PjW4MO_KzkqBw*At|0r6%G$Y2_O(DAW*gzUqAPPbCnkQ6?tYLVeqa06F>{Z7N9}67e_w#?Oa-Yb?;c1ZPQ-A?-h{0C zZb}|weXTWzw>($7YI>Txc45gp8&%y!eFM2quErDN&W>Ig3J4;ojt=Q`y7Enh#gc9m z+si1-HC02VZf2zxHQm`rgzU)4?r9u%1R~`W885~}YDP#)0Ec=|q;SbI*O-DyvWe?P z$_XV)Ht1PZOX};i(?cTAxY{A}1dW-h*(+LzRcrh)~Ob?^6*j zVro3INH&j1#ysa~opn4wm?{f%k!;^E^`vst!Vs-DiYUyJ$W+TN+{up*?p~M1B{6z8OPC(_L_(=(^3jC zYu7-Un?V@a{GeMxSeUzHz)%qH+1C3yP6@;0(QIZylv72Xnt7Imde?5cPGM5&G9Tl* zP4fa(FmdI=hju>q^%`nL*qhb4mRh0+lek-qwum5x!?)fJheOwOuJh%3@rcRIJ!+jm zF)vHbJx@;`3WxSyN(r;$;p84tG|b(NMNZ4Y#HOZDVoLUNo@a+^*D}pa<@G0@!0DT> z|L&XLe&2NZi~snSfBm;_U%EVA;&IipnPXy-W_GxL;8LbCixdVuet6%kt-Ag4`^TUD z(|_{yum0}Wzy0y6uRb&9~~U)hwm1*X#9qIh>ADnNO$Fye#MI zd0p4j>7F@bt=-J0sYXz(%k|P^nm#-8Lnl6@CI>v~-; z(;A)cKfHPUMj+dDd-duSkxkRAZu6n8 z+qsltPU=w^Eb`Nzecp9lU6+&CxyR)y^Wk)V+SZFm{p`yxEd2TDyq+^!)Xvw-^UKq6 zIJ|oOa5^4y7DL3g^=wMn*0yddNvYG^w@r$))()~NqUN>*bjyg{p!XqR=7(-Gi zMPWIrmLmJuHin6Okf-h<5$Fu*NkU*i_#P1-(*Oqq^*e*wm`Mjpy#w?yB*pt&Gv>cB ztKC%hzheg29SInvzh=zY^&K~100NE>5yX&3t6BC&rDkB%VC^V!V1y4wHsgxq#Fje% z5d}qWJ^!h6A7U?f$*_bmP9f3^k<-91Q!*!>onqi!c{|`>9?9+jkokC`!EW(8lx;3K z<) z6H`gq_fVL8T(NSx9wTE;@ywFXIt-HrWlc~@L3+23iSigG!<}g!ZM8CN%j1uOZXrU$ zuRuG)F}^$6i&S}Nu~L|ko^{T3G2f++W+i6Mu$sIoZV^M{;?J-k3Z~rj#@oSA*Q9wj za{Iy#Gt;O_$Q^0dEbX>=0@O4txG+bqaB%aa1$KLA=Bf|TTLLJ;qxjY>awi`z(#-b< zcLaekB5vGCBsV=ZOTAnvm9j@~pnx8k#|R=O*+F#P8NWp3Z#VML$?)zz4=~X*Ro|P} zQdAxunV*y1_gZRRt~5$8Vh{G%VRMkjPzU6uLBl)C<~B{!Xjd4}xbLLj=;Y4jmLVAT z$nSb}5HWIrV=!ld*i+`+^Tc-deSa8z;h@pwy#G%y%_0IOqEgDZr}i+dJi2Z@6{&Ab zb%?pa;qDAF+WYk~D@k~me@0mwnL8E49-^AxzVB`;+j|K?6y|%tOtx+4Z~>4JIDldJ z$m_|52?<45Rf&a&v@6OWNEl>$tt=A@O9a3{ITjH`xTygg4ohXy?I(9luC3I(;PR8E zy(~lXpf}fX?hk`aZuo=%Wto*@szSsf;&~|*nX8!gQg{csWv&7ObBUT4wO-xR46n^!XQ$#w^Aw*x|?c@#${m4+53ahO&LMqt~$~<%^1N5gi$zi z?z4(;E+xayq!cECt67Lt;iA@w#Cve594_Dh=Xwq107_zSG3{#pJf%6?TO+c*erWUwmYar#33CukGwa$u9%MUp3qUntKNqR{ zgLyAH$;d5GDK)$Iax$<;C6Y+3HAvf8ofnzHm?$$Gq_uV1T3APV-deO3V=m0+Y1X*O!Y3O?957A_!bm`}KN0&UbBVYNoxPPA3sj zH4Qa25~hK5ac^DCOO<)50eX3SdU|~M`kNo_j$i)s|Mt(n{o(rI(!YCpz8E&Jlp_UC zWx2aM-QT|gVVVxFA3lF|_j;b@zxd04{mu8kdAz>-#V>zd=U4B~=XXCoe(~lDDP^kY zz4dlIOcM|^Rheh)eO_jGTrcN3)zfnK{PZ-<_2ucMTf0BqJL2W?^zi1>?Yeo8cAJg| z5}B6e>HN&hwJuT*hNaAh%Vj-&@*o5j+WP9+ro!)k{PFPl>pIn!_F}pnme=`Y^1yap zr+L1duT!0bv0kssG;y#ZIPc%a&re`7l3xiu3tQK;b$~i%5C( z@UU$yTAvTo^W(M6^XdLL$@KQSALirS&zrP5EeAxszk6Lvd3riq)7P&by7k9*@7*vj zbDic|=RWlx-@PqTPN%zhK3py@rcKIlAD%DQd6}8byUz0*;Vj&?CPIh1Ij#ZVdc6W6 z%;p*nwXqo);2XGf@g|6b6fkiwbr(jflTvK%S0@jN7Q9w&f4x;r;UkAA>shs%`G5A zBm8lI^ta6D#Pf1$!JZoD?nzI73}oDMjT2qYGwoLGH#7!2%D%4(0cLWW2BFQh*HFy>V_2U zr{@ww$8S3_J;v7CC>BFbkijOr57thj5e&3z0}{oQ;hjo6E;*Ue?wN0pM~fweSqP0} zl^Am)B1nd&X?*DB#KX>!h|TYsU)_UQu)76HsrxS)STgTzYnes9W2hPa4kpfi`g}*8 z5qx}0fy|xIiHLW>Sn3fxJR^X(PDU}hd$>sUDQE3Kt~AP@@!Vz@tnK#p@kpxgw8qK1f&p`R2U7EZ*OZ<5nz zDy)*%&y8%1e1sVL`LJuI&c##*oF4En5zhCEJ+sKvm`G}Amn(D7a1J;DJb*a93Gl{8 zr?!74*g1cvh-|GQqJR@o&l`jpVIMa)58n00+BJsYDA$P$XrkU#yXFrcUsIa=KaRIK zi|3e15kX^=*fVn|Z&Htd?H@o2LGq88OUiZ=i3msUSuutP(&WA{7MOIcq)vWp|3LCc zmMoN7M4*fp6`7oyBL3US#@NmA)LKimgs-bD%xL zH|gVk#+)sT1K!x7p`kVt;ncwsqe$tkMgCZiRN>R;@^AvU@Kg_}Et%&`?M*K~L8O$D z-tk+;4A2n(W10%cgQ8aQ=7^Yzgwx=33Re+kuELTsI$;P=1T&YyEEJ%^Bl8vcNI;lt zp>l(zNiv(EOLjMwv>~W)Ay_1EV47c7xaOtigYfl0P{h6m1r6Rkgowil?yjcI;X){! z5p3DNNZ}fA63PGK)(IrcP61a2tTir0@~p@mnD>n{EWgrx!k1DqU`WOTcwl&A2Q_mW z(Y63aKJX4A#83^%u1_6bbUx{ahY4UAM?4c}toXPyM?@gq7WX0bq)gKz+Iu~SvNC43 zcBK?}1P|u29YSK(g~WoKE1a}jShThE>$TQeguL~>^{Gxs8xX<6Psd}^jhPR}1td~s zT4rJLhzt@A4^<`M!)ZC*El2gP{rT|);caVmnnie?mIyx_4j$fnBMMVXWpJIUBeeCy zamhriTClF``uuWT+sk=TFDjEcNJ_1TyZcvF7Qn&8Q3&$k z@o8D^fAe>L_4A+o-eI2q=5PP%?bFMvN!r?e`Stfd`^m#}IJS13%hLM#@bJm`@<>c- z5un~$Z;gqB$gH=m5tnJ2+?`Q++uZ29Uasf!>HeOGTGLX!X=kRx@mS`Av7k&*3YQW9 z!o6$1ocngIO!s$p(>$l-sn*KOZEIl^3=cQcu6vW9>f_VnB(r%Z#zk zXng#M`81&&(Z7vN^f7*M0NVp*#ZBTulmRuK_#LHMHuVN&q_C6ilV2Wm0BB6@36lqC zq@V4P$^b~n-3h$oWyDOeH?K$RO9&J1!|P6o?Dju`mS-W3Su zq}GQCvTLiQx_NgklICxv5fab)u(Hp5Df-V>o>SL82j#YtAbv{$9c^OfZl=P_ zB8-n(=ky^6bN127&2>hx4|)5fhCS(S=YNPe^Lc(g>LUj^j;x(^9I!=<#eQ6@`;bYT2ZHuayL2k&Z&f2;v_O~* z*K)wkGh{4mytQ16t@Yb*(zaG>*|~?TI2?1lo28aal_IRv>h68BaUJHs9ImRnnkazr zO96yNPJE@*R0<6!lqAOR<{&O(F(P<)7$t~4Sh*lzNGA~#w8tH!UTD`aVUKh*H7+c| z5j6bc9z{wD0-H7ocEDAo2q?G~X7-PMFHRKM+=86hb7z zwU(hKPn#P_J|KJVxtCOtTpS~Di$K6R*n6vWan+HSB-FbT6~H1wa`%X#aHk;02(u#u zk}=HZLl{hn!mW`jToFo0?@vC>EMs6e4LBMsLXmeqGex)!OI2=;K8_g#;9QtYdtOxG z&V@Zfdq;pvvEBicS|Twe9ruq&)=j4{F11RjY8@KQ4Cu`5UxsuK1P1Nav&{S+#5sV> zg|OU(=q6`OK~?Y|;D&}zcIjbS0K;K<-80zTg^8K8JF?q_#KBwBrPMOds;h0df4KMF zUY=HAo=+$5DutI?+qh7B1WMN-T!BURP7KwfD7)immj`uiw6Xx4wPu-#xCF zPXr;i0RQ}}KRBSwUfhj5t}ic#B{IPG zb#Clf?(PN}{Y3kAl*jfcWlPEhL~6#KTNb&e#Cbe$bLk2nEvEbT4(z!25Q0U9hI4>S z{U#v7{wr?|a!)f_K;4Fy)ZBS|0?4G!gm7$Bv&HyAKPH6!y-k&O1F>*MG!hYrBP`hf z5W1SA)vaqL(77k{o`V_2mdH7#55-i(h-W1c`v@;bxMioCaO%r#1V%=P6bv6n>TAgn zu3Bn|ai@i5IxQgpbEBYSttp1QEr5)%qM?!-UWoiE0zrg*dqKka!s1~7x~inx9$~}C zH$*i==%c=yh)7dtwCn3)5B#tQAw~p`c4s9*@KCCx8AS6p`tIS$pB`buRdTy{IpLuQ ztZgMC5!qTBYi&l!XHEc7&?xiD3p4`isfBjhy%PYKWfz<{K>LR}%$g~EBm$2Ef$!~?6Fp(W+j=Rz0=XnW7pWZYo-|{ z6}x{;dlyXB10&K$A}rtp`$Jf!nt>RR>2)`-#P@ihTd-ZNC0my%q1`ckv^@KRWq zrnxXvkrE!$GzfbHN5ni$*&L41mz*>ZCBKs$(b-!7=4`(Nk#Gs}2n{#yOhcukB85oY zl+qcEa5Ku87vbjQ9--t$t`U}^pp;y>g=?gVDi?MS=K-eyERom;MtTx%;#13oN%EXE zGa(L8Xc}eM%*uGmlaJcV^z2enGPG}o31j4Z@3+FjBvy9w)9ukkpkWI(Q`M&$me?8ykeOk5pXqgv)tc^i!d8{--IQY!g*^(AgMJxYq^fd z;tcGuq(RF(o7*(kypon>akxk+rChF;0M_eTrz%pGsRr1>eYp92e3-14>sdrvZ*I|B zJ01?}y7jJ}6++!qwJbc(Q&W3-dRBF~Gjv<^e6jC-`~3FZ2a)+@yLc((aCbgG^ECa@ zSAW=?j;BwKtY9zGd>{##Xqq0MKhO-0X*u2>4zIfXw$8`HblUX#-G~0O7yI(fQL0s) zoAxkwpsm+Rd_0^oVnS+dO%KQ8G!+1@9u`dDVai(Sv~FuxJswVVnYom^``4{?a9NJ8 zqaILZE=ws>Ti3&Zp>}zGk(pfS?*6{@70tElRA+OarkaDuMf zZIO-Cm8WH?^Q_w@wE(;v?(lLxKVM$GIZ8cTE-Le}%%>mTzdf8@JwHFS?w{X%_7{Ks z-LHQ2&33)s-@WZWcs%`(kXjj}I>k(lMSwe7OiWip_B^Zn`WRhYt~w-uSGpxb>pORXA&~_EjSPBRxDI_898*!Q~B*ZcGty z-VY(V{nGI)&*O+b+7NHyeK~jzA<_<$@3`Zx%hl!33Ag*F+L&^El%fT z>U(Ygc1~dKPBi4*Ls1iBwXRL z>wJQ{dq|jP)-E7)+nl?07LJ|S$*@$6`!Q@pI8ut{*#~cv7;9drmgJI;4?NYs5is`* z4#~T$mYOSg-m~gfq`3EC(q%GtitOn?;-?8uTjZ&QoN-e=lp6g2G<2+LG#Vb5_swu< z`rW!GRN)>nW~8u0=wnFh&5V^YY9%7nEE5QO9{_(KN~!xuvKK8&%B3u?$^3Jo5%-f= zF@d_;ph2{QAVqptGmq#X(yj@kXjdlMqv0bkPK*SUlgIPsfl#$<;qG7*sb<4u;5uTwz+^jMPwAeLym1F_0;1tYa*L53L0kqtV&mb02xAe- zo>t2>pWM9;;}IcKi88=pVEMzMgv8-_M1z$o<^vh3Q&rVzne)U=$Ds(jcamHW$Kxx( z%yea`+VObkU746gN)?0?q?RJWQ>_tRMKFr9qEG7rY*t(zfN#^Mv{mDOJxqJWo5^#o*`T1<~n=k&C|K&?%8awzlLx7Tlf`+S;Da_}&M+f++y zZC%@Pcjtudd|sB*^?F?nhdPzR>Gb^aJRJ|PXuYpnzds$jsRyJSh^F41>ohIL?d8nV zGTlGWe2n=3DP8q&So-zxdO82>v(LZ#&0p7LTGwkSEL67(*E)6Wt#u+|rc}S=9Ps$z z`To`Y@BQJ=zx(#PXKlE>+FUEp>5)%jL4J+tnj3m$g$qKZ=DI#w6Ks!F?43`YbSwgG$_KPNb9<88&k~l0!Jyewbu453vEgk#3+1$pm*!q zmSu^6cU4Aj&8?SGDB#+u@Z;s|9z;TfZQBqu%}b}+`MB|M^AgUO=Ab=-1Ch!ZHOn;1 z*hRnqR4qGX6A2nHyg5rol7oop2HuTw-yzyDH4Je{BE+Leh{n)Ew)gv{n`jSki-X`kEDp-kxa!1BtgE5#2PZyo71ZkBmO02FiD4|6(I%RsZZN=D|-xl(%rY5 zbs$s00s{cafqV!##zgChX1SX%n?*#%{^SqH7eFI^9!XGSmrR~+=v`Tuf(EX%OICIk zK6dtE*F_PC=oU1Ruw|mZnyxB9d(I>lJ}QL5JVq}2@G6YK2RRQ(evht}tiB^CQj~%S zGPiV~77>q-lt_)$D6;Md0dS1%!r|GZo3X0-fg@AWa|jHMTL}Q~pX!jl*eD@|N0@46 zIw2?*i5tZ0ds}7f-;M6t3=9?V_{>J|J7A81X_|WkXYrg_gbT6deqy6EAo;IEht2Ze zXstyIfj~+mK-nFZ5kPEeapSdyYl3o68;GZ*T*5}JaKK>zg&DJSRbr~OI7V1)ZZ{qp z5yWKXY>tmtG6C>zfC0N~479bL8(>66><@G5P`FtDV4@7d9wGgN&@=|PyAjFlUK`Oy z-H3W#cB&pgVNL>pIaeOHY-GZY9eeCx*{yaB5t^4Y2a=0$a~;t}kx>~;(qmtE%v1j7 zrsH+o$;wXur=T09Hrk%MAKzJ0|5dv!F=DF=9)@A1K@#=l2e%!+~ z(zPQZffNqqnx1}wJhcNBnL3}pn$j0#ZtCU+qEZTmi5^0doC7@a_%K%*p7#=AA|OM1Y_3eiOk)YA zG4F;u6Nv~D_11-{%ryeql~NxX(B7COOot{fNX@}z{FLMkdP|TxA{9{`k>B8Vx^;l!B+bQ5`BB}G8#LL{I{VZg)H1Hq~tknSdR z$^;zoYEE1V59^+jjGwXN{x>Adi6n^~0OQ~@7a5-E8&Jc<;2=uf7ZH!d1{v#z@5)G! zP!^yR*(1Qsi^zTtfQ2H&%>pFE9-bJMhZm8l6il;e*KK`#xxVzr6D}uQYiq@$ul+Pv zCYWwu+S(ds#W^s1PF$p^Mt300URbE7LKL3d9|(_+&qTbO4#Yf30rB(8bK5$yA+=Nj zrg`er-IapoWnv=jK|pWax^7!bu2x9aOV{pmJzOuB=ksMb9dw@UhnJVN(R?h0zWnJ| z7WnONzhQ_JnhpX})%L&opa0KZefH{K{=fg5(=`8A|C|5nKmEsl|Nr>E{onumfB64O zwAZI&0e|}N`OD+mAK$G%|LN;iQTX7#wzU&SEi;#?2&s3jGShOnTc%|`zdY4>Vk#dV zAFbPGpM7rdFc1q-i6CttOeFJiU(beey053Z&6tChX{PX}$0s-r$HVt;zpVw=>lWca zK&W{)*Qpj&MbI?O$K`mrUW(MsTT>;J#}ALSoIm^I)5~SOUe{8lt#2fSq{w^}<#oG! zc-(?!$JGhf>s6_|f4n}~@^`;}`|aDe&FH-KOZ=+x6kOc;VHRr7_lNl_`1)7B#I`;h zkN#I5&gb~~Pe1+17hjldkMG|LQ4g&m5iVsawXEB^wF@ZJ`?5@+aPux*wYT0&5p(aR zz4J6p1I<#uUanIuna;f&mvy_Enh1-G_>Qf$TBWb+RAs6Y!nGL_U)M{Wr-A|xSoEz2 zH5S&bAMTgKVL9C2ne{X!YIk=FxAlBouWOYuE!E9I(zd>?z0?ZkWjPUQlzUz7bvkyG zyi;P7xDV?dXNt&J^#c*ih4(aGSI-2`A==wD;(Mux>#pC6$Y;k$CZUf3HM_dcZI{6e zWMRkd2N(|ki9?j0}$zvO?Ss0_KoyUMrsAdAAe+1 za^ghlF$Rw75C&zkcE*nI4R{`+7tg&Y6(IY?|h>5^H?*7)Gz;{Z<%SzEUbfp+AY5v?m?WR zB&+uE-rNb4RmwdD-#KG~Oe4(F@sPrKM1TiZP*B`8lAcqDh`OovECzB9#>mP|Bt9sF z3lO_0G+1~lQ!YS$3!&UIYD%d|x)F`YqFi~?5R)p%S_MGBmSxTpKfR-+WYY&kNQjuy zWjrF)BPac!wf5n_NnzmY-Qdom4S0TP&l z3kArGhAG6D#9g7t^@%7(`VGQ78|HnO3j!(P96ci#6nyTrER^dhWe}}tYJqTzTgHB- zk7qJ}gmXMRyq3y)q;B3jl_~&Jo65w@7~6H)Cp@ zW=cf7ltLg?>(&FoEZf=;es_PbX6NS@A#$a;)|X3DwIIG+w-&PnO>=4RyVJuY_2t8d z3S!nnIXu08FI9-~H-GgX?(gp3{`S|uzCQo2|I7cyr`uO8Hbs4|!3 zbW(3A9GeaY1@m&~SEczLGQ&&jK20!>x*Wdv>gQ5Wazzbi7Go-fbu-o5+zFMj`5fAh=Bc_S+7y;kZkR(Ybby!~*U>*5t}KV0kaBy+ug zQ{Vsk`}>E_zx(j+yASKPA3j`G2Z?iK)DiC|rG-zkoV)+!ufKbK-LdH}etO4Fg)Z-Z z`|iWANjt941tJK6{oQTpEnX zF$_#_Up>3OPUDboM)TS9~sd8deJaNZuwk{QH#qU6N30~WX30@fWb^avkr zG!rSz;bs`tXK+Z;9cl&y<-|#mHAxv^ng2>T0TDUC1WhAFu$gAk&emFfT%{ECk*mSX zJ)1Fi-AM+^(wM#nuYz6PzfUx=_HB1;bJ&1+hUY4?)mc)4+glSU9+pf_3K#P#Fw;!w zNEoHJ(X@adv(6$k6eb!w)8pn$a$UEaLb`SlAtZW8SuhHKXas?Hz*9Foz@U)|2$<5t+J$C&B1Ic&=bb<#ax7@(Oad zbYkom<=8<#?mRJaFh>UkBFCQAGChfN7Id>rmkP~MEYv*U!jhk`kHuDi5pV=h7P0wm z@$^Vx4pMakBEm3C-rhJ7hUWxX^ycZEP8FF)ltEA67Bwv*0Wh(dXI@i& zAgwD6{rXUotE!n1Gc$Xp@G+Z@x}KbEIk7rW>n*b{i8$}#0D4mviGb~BEP;AAAed7E z?4@SJ-%u0ewGk}Z6^J6TX%`lC$4CMkbeDVDKZ2OMrgq44nuo-|#WB!fRgEI9N(^KM zy%{l^dbnl#UR&Ep%^F!M>TXPuWJTAk8{2^Z52CQ%buUNCZ->AUnvA9I^%R*ooJolh z0Se3Bbaw>Oj4HwiGSdW;t#tr{Ak7UnTvb!iO(WiSd?Sb<6V%pAsS(k8X0|ejN(64Z zvZh`tA}_%RaDW)@mYq8h1Rv`x5C9_v$HA$mCQ?=JV~$XIIG<#!fO#??y5pru9h*np=!3b zt`I0Ju>ZpBRUvTY1pd7U1HnX9xa>WYM5tv7cP4J)e=B4byI!vVHVCqj@{cBWNu;T!T7x%MLR``+^+FydpGg+$GeI1mP2=r}J?aOpu ziyh}90}=9Y|LW@>K9pM6!`5};jK9;qwPhxQGveR<`M+G?MX!2&{=fcT{?Fh4_N}1( z&ENdR)A=0>6Zm|+$g79Pvwi==>izt1@Xua7Z0+U#ZW+?d$`c>W04~#E0pfDGT(8fy z9$vrxgfjWTjB5>#Ivu)dkRR{vq8yG7pDfZzj#7`_daV<20Y$es+}*wV{?|lU=7X@D zUtZSra$F9}ytuu*T+Y)p&CAlZR-{Cab!&B+icCb*n-au<)BWAc`Q?0B%_tP-a}Sjq4=J8=6`RvVcdi&keyC2^D$v^zX!>iNlhc{1;?<3H+E~VB|UM?4pkXkZ24TOXk zmu0|bWvZsyT0>MIpuQZYfP+HKx968-StcPa)LYlq&8)Q*@Odh9n0jjr)LPAa+xq2v zbxR#lCqN2&S8o;?$5-=VSuEVNowe7gPE*PFL+h$dL%7u1x^C-rYu4PHip!CX_pPDw z%ymg+ou^&{nQa2VS(sVW9PWu%^-&yN2KR1(;RaxH3mVy`iF!qh9Kb|5E#MK>b%dI^ zt9y!=^AH&7cy}H2-56~>L3eTt<75z1Mn#PqpGIm5jeL*P;85bYHbNUCM;vz@VG?l@ z&IXXsY}Tw=KA>y_{<1~5x(_d40FaWUeglBWvlofbBN74|@7mq>LEK_w)u$3^2xTL2 zNmBJpl_gN7+GD7&C~>-!<`YBJ#<)dra~VPla4L$4LEwO;nh3~#Od<-9xm!{b5f05- zw8X6U8J$#nLUj?N%yqbngWMb=DsW7n+t%{~6h=;j$*x3LMxf>x5kvL~OBSAJe`_QC z$<#_M*o&WdNPSZ3p8@~5%)s3;mnk*nwj0cHioys-5BCvy8VK@TeJTvfw3)PcMGl%6 z>a{)W#62k7Vzl}0*&)7PbwfMko*mMoCmTTl?Wtd78l^gWa&C;&3L{5=%GlyebqLJz z%8fzkL^w;y#Y45_eB$;o$;VXzGB-0P<{ZtZX#$~ZYF46@0-|g!97LVDa}4yCvIJnT zRSKt|2q&Qg2TjMC>H+H-Sp)&Nnou%DtZE*?U{6y7vxcWXlbE}9CNa}Mbai)gcTy8B z09k|%Kz*nPhF3Ke4?`;$7Op)2O&Y3^GL0J4+|xV^16Zal*_e`9QY~d^5!p2{1~w2t z$>)c;35k1{_lOY5K4X|iN|Z%l>K<;I*ER)QNX*>R@(a321hI!4CSR8}#i6{<)6534 zIlg0aABKdqn~&To8aGd_pCi^ZCoA7e&_G~8fVQ4DY(%bnIY@&%j97?y+gcXFx_b)m zOPH+5P5xXQnSbzmisip}qNeM^OjcZr{YNb}BrkL15iMXUzK9|V^ z`&on~_A>SxgPc!j;=u(8b9cX0rO@!RrP?=T2_s~h_J^W{$4Cf{K|%_9jP_#YQQkz% zsc|DNZk9xRN{5@;LPedoORLtouWUM3@vc%!7&9Q1;oi;QzD)H{#XP_?AC}xM=4FmR zQzhnQI`-CCO4n|x$K%P}wsq6qr+JwsVUch!v#LHnKMQl6DnQ}Epem&(zdXJ4-Vlgj zDH9jj+IqcQ-#=+<#xhUDZ(iL!o2$0IT?}F7rpD8poc+us>i_Cr{67!J6G49c?!5s7 zJh!H;6H9R7}Yad?Xlh=3Wb*;Rxm^V4CTx64L30XSGhds~*}a=!F#fjAvc4-apIYcN5keswdl26gV6w${~x>hg5f z{7w_MG{mXBEd|I`GPp5~jbeisd@q1r+#4msOE45~3of<;b&|Ry@W80RwJZ|l; zfAj4heEHeK!x!_+KfHbX@%_7>{NzocB3wx1<$SKS9Ogwt)~$udRHniRk8Zt6ZM|>n z#!T~6L0H$VRw+fU*X?kar)3U{Nopx_eYtw~BE=DNt(7S^`EBjOL;wf1bwyASNyYto zT}zRPCJ{ntErk)oLP=ewp3?zsJ?E8_DKmxvaX1k2z)!#P2@yLnJFwr z+Dy*%0ksiq!o%twP8JMi&PrF>8-jU6?`Q6ix$n6;Om3EQR6hF>REC0Ov_>Gn0wT1v z)5t;?^_+X+Wc003;#L*`2P|!f`4I<3zS*vs#*q4qsikKIHO3eU5Oo_u1)|ZUF3jQR z-IbBgf`Q|jt9qSEPSFAh57!)#CB_dS0wN4Mbz`QbMAuG4C3V6`AUVZaj$OzRKC&Nj zd*q;<*Nx!edM64Z3-|Q(<^M4=9PXL}3vT)@4DJV#Q8Ol7vdzg%g^GEEc?Mj1dT%4# zoMm{Xv%qqQRYqrAqUDYdMC`fb!@|hI<{mbpKSAi$l4VlT{BcZR8C9>{JO*3^fO}UU z)DyHC`sBne&72r!?rK3H*yTVV8&A7oW*`EHO?B8X-F>9B!nD&+SOD416r((cgCfwo z?#Z*M-3Vgzt^kBfCXQqfh-G#?%y0hPyy7B;6n;o_k?(C#6T4O?(`*dUx-xN)5d*|Y z9|2%yO#ubnE)Nhwx0XH}wb<9>2tY(q4q@?8YllpaGzTEe>YhVqSLIY*rCLKrp@e11 zQ-pS%Cb2*khPZhttnNUxX3Uo7lnpnFu}C+owW?ZgEhqj=(j8GtjIhX6WNkf0#z1QS zQ#3mWy|8>TC#w|*TDP8wqDkiE5Sy0N@CY>)b~6G8F}MWGGXZ+!2L!ShFv1*aR!TK9 zq9CBFmMXjaCtR(V4V`3ase>px1@aD$QO`9LgsNR?F*mc!1Pf61%zHqIvQ$pS>XVv0 z<~#!PVok?SO;u%Jhs-hV-d*~cexv-thzTj^N^UQ*!*6up5fc*)x7Wzdm)1J(9W+G< zK-ZoYg07kgsP5tBs@ZyLMM^M=NWMrSB=nw(%250S_U1Mpm5M~!0FobKLL?cIrD;h( znx=Y2c%=|1!-Cv`0@k)*@jwtmiicC7Aaamt&dMAttv6pIHExp3{FKl~RbwG=xU(Z$vN#DL-CUiibvaZ=Q^~Mk4#XD3wvj zY>-gx%q2W6za>R_oGU?^iomLx&nYu=>k?KXNJ^QO!~H8-t!u0EMDWY{ScK+jE-b_( zWL%|)xcTvP>ZYpQhAB!loVm2VO?3*OOr=iKq;|bt)YQ$MU!D%j0ZfK0pX*UNQXFNb9|bL)1vJ9bsybe@-aZgJkWt$p_CC(pWW?;o3OfoYQI zx^2SLw`&mf-VV!gk!e0Gpor)mD8jvWcTER#>+SAv=t$P?{`#-J{lnk;Nqu6HpuU|yJidFnzK_tCZGF=9)4OGg`pu8;PxtlHhvVURcYk;1ic4Ra2#{KB&^xKtXEij#_K_l+>+ODN_-lG8OIEdUwQnxt@-v>vcsS$)!?iSMR3FQdvqs z)46rMUUKQ3rU?!aS(dqN+j_Zf*Xyg-_W@%j@KUJEQ&nwn<|+}xIvh6Q2ZTtKx`hTW zcduoBLwBFFdRkR3-6fQYH0{DA^?I7=7^D4)QsA$q>2Qv8?hQ$DX6a-)ByX(&2!{w| z`h)6_+1%Venz|-)07XWz(T+dSP)ZJIt7koUL|8t2A|#}n=8=)(S|Hpld4PP)_}y&# zTW$;?T!{%`p^>U$jGyfT^Cs--L{6cRt2ud=cM^N@@7kS*-8Ln*k$;t!^ev(^0J1<$ zzcs-ra>%~(e4FHBAWr#{Sp+&j+3jm)0T*I71yL!|dy`U93qlMgN+A&i6GGk1oJp8t z_rFH=c1Ff`ie$Ep4QrpdM^~-2dL|}1K%;{U5e{f(Fq=t1jPOSSJoVNrq}p=@!U$HP zrl#t|1RCzl&~P*2f#sQN&O1!vG%_VeAV|cG4++nr1<(Czuqp_cjl9UAH4P$8NJv;b zpx%i%f?SOR5n*a-gIFGsCqs4~k%I_;Tpl9SH2o_15d@rxldedG8wa_Ws!AlNm)Y47 zBQy&V0U2T&gC$Z+qcs9*kynvNu%yj+{L5oN??_d`xKZG$5fEXIur?y~Qe!{>IB1X9 z&mleYaJqScfZ5a>xlJ)*NOL0)K^{XH1#m{N_)goIDtkn@K_fzlbLdky*$beCyCX4Y z0_Bo3EGtCA&OsoUD**w{(5igT2(lq@DY?AmH5{?eiXH?@zlV?bZ|qUI!_SjnO{R*c zAD@WQ1n3@38xZp}yV)=Vj>ODZ}KAKof%{<^*N>R^kjMhx@ zz!+aa6dvYbVX&+*O$X@+Xwt5kY8e5jvy|}h`GkjL$PElc?&#X`R+UncG98i#LKcB# z@Nfk;?KkV?&`495?oJh=owIadaYEBnri{qlLNo?(k5JDqFVh$YumMm+Xt%*iY`Gf2 z%~hQ^m!AN#(%Fevhd?L`B|^<|ZqD7;EYq=w5u+135N^|4nWgte%teZt5{YmSaj-LS z+geS?ne$6Z^x9I@ ze2308Z(xs8y4l|jGnzgQ6Sk;0Cg4I<$VrwlTN-b+^u3k%VH!0=S z!$ZEdWtnrh}hQEx@L29*9LDynDLDrtj8unhG%>K^}9RErP-+SlhbH)7>=HX?~__-?o%-Q=xS||FeJg zXFvb)HMJMrKGcG{)AIU$VfOhr2UCESd0GyKAAbBEfXs_~k}ws92<{r$egDN`qYetEuLF0T)>sgcV(r9`W=Hnirw>AG&UPQClO zwc}}_Afod4^x~$qRFNWF%QQDVU)Sw&_4)Yf`QpU&Acwota^60?|KVj@!%KaBKEGUB zk?9YA{?(iN(f!$8;mw;*U%B@WAKraf4Yl^mzT@AZ>7|2ZKcZPe4fjERS(yx9z@F4T4D-jI#2U*SP~#JQw^}ddfidxEXS(fACVe7g;kb1E(yo%Z zeSs0mo&zg=OcltS{t>K_q-&}`m`K>ehpOE$bfXb0GBhX5P8q*5@*9XUfgo)N9_e$+ zM2a9Y&-iJ#P;(JZ1;_5kjS*7B3=aXRX@u8WRn;Q~6ccGQ%`Jf4vm;V+3<8rQOw}R? z+`3VI!UjWtoQR?uGJ#RV+{hC!N1EL7L16CUH;0DPNJtcbx>}^}mqybwc~Uy8o(ONK zD-8+E2z`P%8`04D47bLB{D=Z*-NKw?_s@DH@^wS{a-579t-Z-t5Tf@^;ACLTJr&h7 zjKvOwa)uCj67WURT0oj$qkGmulYe><`?DQvuppdOxyKv|x}lv9_VLf?(FjHa48Uu{h zaFa-?2T2^cg;|Q=^QLzIo~{xMxKKpgE$yQO^lo`~n`wpfA5+)J3-@3W5xOvw; zO_Q0DbB>@9(bV#tj!DD4NU?|vkleSP0E3ETC1?&Nz4sz1=Z+Y*Sg1!t2(#yelp7A9 z)^ipmu&V~b)S2Vv+vW^BwP@Pi$8@x{R!WJ{*o_!I2Y|vY)U=dP(+s*4CT3QTlJ*G} z&Nn|KZUcsm$cA=e8i`og6>RK|asRnxPupn2CUQs6z(^9B4-YewG2e~ZoZK^EIBQ{Y z=gKB4ViJ@gh0FDVi6KE`ZWdHTI7MAahy{VzZ+cOWk&7Az1d9ZLm|9m;x4ec@YDa{; zeBs%6kfUZT5(;P+|9x4 zVd^lrhf(o>ARxr%ZXA&cV-Pd-pen4rr3SM{FO*Ui4>J;Si=d&*=RB$N(+eXa)lN*3 zP3eGz1C{}K;ZW_MganW`XIBf5In;y9ngz?O5r!z!Y%qefwh^%u0L&yQmGeM2x((TB zo-MiQrA|QeFk=RxcO`~M$rId7rIcGDn5*U!D{B-pM~L=3f&CZ{_XJejlVuWtBiWb0 zxR`T`W`L1VSwuh)5hszNn^W&>zO}VbEXzC}inbOS0QzRWtsZ_n9EeE@Pg9j+ks{hN zQ5(duOo#LJCGSR=sfui^wQVb9a`(gGU~1Rv#RF_$0hiJ{6BQ2Wmu)#N985&l^=j(# zyj-srvo2BtdjD#P7yJ4*U-#{~*=*{6^3^Z?Ccgdl?PJJv**0;^^PEcNZq3$}iSCX^ zk=nZb`2MXD)~!WQou|9gp;~XxKYqZ6H?QQAH|5oFo+oeHMxgD(wQX%)mZn`_tjb*L zWM;(0yVm29Iz@t%S}!kG)VX=Rd-WwBAEGQyQ>};j@TzMAR-}nY2!w52q1~G@Q7LTZ zLFlIK+T9`zF|H0oFkW6RrXC2>IA1Pxn%#pqmcvndST`mUDuC5_UX_0K`+opp3cP*y z;pM#k@b2lUnTTAjSK{*NC!c=x)1UwR7r*%C>u-Pi?RN|umgT#*@0P=HTUWPUA+PQ} zc{n}P`t8@>{B1l&t+aptz3$Q1!V0eQpz%$>uOj{={S2Fe0k-3 zkL8Zb!KAd*m*-&MISmp4sg8|^REnEoXq)7J0WCI}W7UAKN z8qGVIz|)5&FgR73x7__P$P?T{%^@k{NA`>+eiqDmgAN^y|3o+%AQ`+p>iHuR0Ih#b2lF}NT#@YxTl9Zg8`j~^=e44Mq>g%1T!sM+(W<#BbvK~ zj(|mB3fjL`hoyC4Q0}TDIF3t}#O9lp$?fDW8k;|OcgY0w#`(y zxLbbQkz{zJ@xY0Y0A4!xK*9U^Y|LfC*J14N*pTk22Oj=(UwU?fB#5EM-{S+5x1BeE9CN3k*G-J$7C zFEZv-fCuw7py;u`-nREn{$3o2A9Gc^75)OGg z4?YA$zF$9S$*`Rd>Fy%M6Um4`K>N7Ib14$qFT9A%k0;6)qHJ9t3kSGqD*+EL%mK

}h!N2&(WN$hZUt`ao*Sk)|xYl+A*ki7_ly7p442iDeNFXS3Fq}(&j+>||C!W4ll z@XF6nV$3s4l-m>{0@1n>!7X?kFP3sg8YOzESfRwY%{}&hL!xX@^+85t*PxA}@pPx8 z6*)khUQ;3frFS<|sp2DGHQm-bjBOU~NMa(gAtHs9o&>HY*5LuqGo5_c8Inj!qJfwx zz+q#PBTlx?Ehx;yiJ4pX5lJd!JsXvy)B*xRdV0uwB*Fq*3Ohh7$-U*~njW&8dlRe* zhY+cyJdRX-fW*Y^ZXO&^3neci<{*MFXVI1+oIQ1-y8w|0Dfkx;^lt86cvr4uej2DZ z1&~);qSQ^hxtRxp+LpC{P9#ClAP3BegTPUXtn0N*69sz=8no6z1~vC?A!CoURM`^3 zNh1)H2Xw*^Y6&4E{A0xA9^HGoerm16e7&xtksv>lK*S)d5&O0ZTF)Ewu_2FooN?t*v#y37Dpdn7XNlPlwsmMWpq9y)$()y6#VMz-~MpDw*UMe|JfIBKYacD4?lePpzgL_ zy0JPf^W+g``uOzx)*cBivINU~I2{i2RBL;ACU0z;guFW59i@Nr>h5@mxg3{c1<=}c znkRQxGp;2FZgDyt*R{1~>$=rxI^91I6A(H+|Ni;?+fToI^Z4VFyG_fnma=Y*$bRwFFMj;-hs*Zz-G?W=w*Sc= z{^1vI>UVG7A?{tG)N0!AkN4ZwTklm+>T+E#ttm0C+eQK5(z^!y?(UShY47d2uG%{j z=4G*NtzVCaBXbT^R7z=^nrqwEC+|}&YFiZ!BUvU8uUn(uI4I0}Z$x3HM74+rGZ&Fk za;})?Lz(95gwtMQ;@sc zxD$gpF%x#PaF}bU6%pE62Bi5eZ$L!bdMbrEHTOKy2sknqVLWAa)XD+M1PKBWJKciP zcW0zWj4(um>QGeYkR3e|LQZgjiPR$n`PMTUkBHE^jZ9LEY)x}3ESc{uL1uPKzo!iT zZi)DUIr`Rb8bxiJFz=!m3U?TQ?CHeb@{<(5rO7g*uA_ATsTFpQ?)yuMXnohm6A@V5 z3DJjq8MIrK!`(ZmBO)QvZXa<{^OXH)?~-rc)7oofg91;kmBvhojOtf2iNrSz zfl`Iix`xa@iNUqz?qp^b8G1vRx)_lW`_j^-n^7LRP6a;Y}CxVdR90kK(1k>lmQGIb{)KhhqDcuB{TBXMqWsMzWJGCel`aD zkF1s$TWI=5cQnwlbSsrn0ZUa%!u)v=*=^}n>pK^b>K#ulE_Ua4!s|nUmMI-bq${_o zk*L9(HKS(DY2R(qB*X0HMwu`$@|}c0@aWcaSz=;O!Eb)HHpILE@O{7gjZSOPhN zkJQNs}FWmxI%#^f}(M}VkxWnuPE)l#HqAskr*wryi!mQh>e9;PZ1=Ht3;s*dnLYZ??% z#E0kr?m=oiR-Qh@7aoBSCi6&IE&*K6Z7o8WO~Z#?h%$L4#n{PyaP~G7ws1tGA~Y{J z%NZ#-(14VZ!j(Lah>8x2Vjw-8jrXlF5tpE3KM#3r4&%fF_@8LkOdV7xy8t-9QNEC zIguH$sU}KyN&0s4IR}>r&v{pS1|)??lu|}4E@+%0d4@f{k zGzf|8pYkGvgk`|C2i~|L%xkUC$eERxC^$neT^$5rX|20^in`RCNmBKs=ER)086!)A zBZzz6c3iqDF>00`t+yCxzYpq;8tjnmqD445YqjB<%ejLmx*i1)Hpl(n-EGRSWqn_EVUp)b>pcl%b}Zw z+x2`2qIsS~N|iz+hr{W7erc_-uyw1om|5F47D@Etcsxoe>v~nS3}BEth5PC5uD2~q zc0F9L{rq&E56d*oWeT+pir24C5Lr&`oA1uA@1}F>Z~yM^mgVkG|L7ll`TRr^Z>@d( z-FIuZBH}F=@^JThnvU8xGviXb+H#n@>*08i9(Rj;{@LlY_)=+^2+_~iYdzcxmvvjG z`OvmjrV^x7rt~m)K!lfh=@##Qe0=@cCy)xBe*O>t$yyF|`NW@|Ed(HuqOD1-Ze(WL zcCBS@=j+qs2O>Hg@0rT=(oUx-*a%7_wHDO?W2zMqQX3O5$2Sbz$z32IXj>~>>S0>j zs?9-M>-6J?^B2GO`BY`y*5}Ll^=F^mzxm03@$lKd`|H2?=l|y4#IywPr~mYy{L}yG z|LEK2r~mqY|G%4;_&@z0{*V9Nzxo#shx_~KbpPrInWtJlynB0hnAex})$#S#o4K5u z|9}3?zpc|B|H0>n%lUfua5x+$?|$7Hf(Ue7*W=;-a5%IrNo8bc#xftazV@!B3dA&5 zc+ATpO!@3T9uEZWy)o0auArFbI#1J7<*>|cy#y>Dr7zDHL`V@KDOEOI-J#v4d2x&F z+C*6_sN3;$V&RvU7p@x`)!d%Tt0V2ap0+D@<*=ZF_>FFElpdq;rL>Lo$I0s=^^T$9S zlA8XJTPsr0iofA_cKq%7UDRlP!_#L0e~}@;>t# z39G7W$H=KjF+>0no???!?|8~}JiAd-b)7UtP=GXoL4E5v=!+BrJk?GPP}a90EW2J{ z-6%g&kZn4|EF(X`(}9hf>N3BS5n4h7w65#gj>mV#t%-ON^m> znClqkaO|8zzyqV#)+`8V$}`K%K+*Beo%BRv&>6BX&0i%ausNi(;Y{1=; z?#>A?|C5BnhiJPs%^;wN#5}Wh?JZ6Urh z3%RBG@VJk9LGvgK;(d+Dyb~XTP}&YsIA`V%N_Qf75YFz-Qc4j=s$J4g&pv)9NvRo2 zT1o{4;U+>u1WK*g9f|wKl4-iB;^P27FbfdZTCismCx zz4@L};5lUH_5dA?hX{957iIy0(?bga4MvezyN*~Cpq9eIs+w>lGdn_(VKGOTnz=~H z<0^s?+)asg|F}ob0^rtq?<5(aGq7G64-ddB=gaUQW~Zted2>h=XcR^`H64BPfM9p_ zdbqHFVPW8sDZDLb|9n3lq!CEPNHh(~2Vd_%7#tu~mq9ObZ$?baux>=$oKiIeP&b8# z2dCiA14Sgw1XFf=P#MZa;@&zl7@Q^UeYITp!=qGAj}*cg8S>SM#5`hEFll!ncnDPX z+G@a?f+&G$2h@Yag4n#(Wl5mH!>5@7CPb~bDT?->@L@Km2=khOcuv_u9AN8NFYnZy zG7CDq6xq5GPzre)Q4+4b)Z+Ui(T4q_kG+M--82w}rY%6dX%I*X7Z0#@bu=QOQo=0U z3ngX2y)_Uas9BdHBa^sf=(V`J(=Bs4ebu3+k)Xc_vT)I@>nMN8{MOO9$>}Ea%takF zYaWQ zyO_eiYE; zJ3o-AotNML@F!>e&;R$ozmYfQV*U^R@jw1={&)ZT?b~1fZ~xc-%YXbI{)hj^fBWBR z`nR8dzwhDRdatV2x2vjhQAOr9+S1?N-uTXaSt!xrt=oMsv1H1P zxj&S&ZyOC;?CEpK>avcMJtK-s#eqzlUANI&r{lqw*$Ilw>`)b=8t#oLt8xrw9gdjL zF|C;Z;zrvB;COQ=TxH* zRXEYqm_#_TIAV96Dp(GWS)j;;FrQ;`M|fUUmXZ*WV~yb1nS`xIL;>B2NV1@lVUh%e zENsavWkMm>EDwS@e>>#DDzH0aoxtA!(6^=(7{=~U)pCb5Gm0uW@lB~>Bx+`a{BjXh z6|*GSwvCWMN#v0f#YOM8aFTci6_VHI%rJH*MoJ3QH|0U71`Busu_=l*N zXTY3^hm}QQcnM_{3V%ol0}uR}C88{OEHN}y{)M#4Dw_12N=6)QcRa%3CnP~XP_hau zLs9|KBN2AQ97UC(W+QX{!o)M`iHN3=##`$%D+^QraaE~VLd*k^Edp9l6An6Y+KLD@ ziqr5;l~t-56|7pB!^4^;Q86l63{s)@0`JyZW=dfWi6T;!h)U!fQWDoJTc9Fl&oG@W z;AS$jWv8S{py46>osLK)r9)N|$7_x-No_`+5z&^87Sv)w-Li-kIv-|cVR5CZ2rBb3 zSw$OD66?fAaz@RPM`9Haoe7y>_99c8VlNdullwrxViD=s10F0$&XAyj!<|J-lu7n| zc+k??9>X(>V$E)q7@ikd~!Niiig;Gy_E9Y{!oMTucT zn6`Z!lsW?^kjPm{!bIB^B1&u{@HZ#1CTieFRLliKJC`tcLV;C+8wk*z4uJVB_5MiM) z29ah}%r^!9+#MGNO{4#43`QOcky-&k|s7Q7od`vZ~yn+$o1k z91b6>u$7TcM9vwZYLq#1RGCnrWvx*W@{e&J#6X`PAGZ@klSQ*j1r-}5qGK9?fn`!; z@Vl^2ScF70JtJedD{F5{`E4Kj>9k1G1I!@|CDxRQ$8Z+8JS@az#2#_K-zu~BmPrxW zPHpTvYAfE}t}3c3!$(9e%W9_Z+l2c*#@NTU-y_4ikw`l&z6}+2AK`TSb`!O}E)SPW zRP5W9Nso_AbUvL=zkIp+l9zt^>(9^kasT?4+xc>SeR+9&dTQ=M+NJsRYbAgGVR^bd zy}Z4ic<18f@q^Uu58ppLJ@L~;*KQ_URASn#WsEV#(i@BP-j$g+hv&L3N%i5oOHsMA zUF`OHJwJT<^mP7m%^!b$zI=LUi$2svRNvmN7cpc{Y3tklo*rvQo>wkwzrTL1lKZyz zCZatnpox7xQO+Xe!9;yo@3*VFv(V$?<>htjOTWE+>lu4Qk`lAF z_W%6v{`>##|Mj0v>t)aCYX86g_kZ|5|DXPkRPpndUy|k27m+N@>+R*+^Vi3xkDs1S zx7X(!dsC%ILtYA1KRtZA-TwOXb!CpsW$pXEQRUc&sx~z?CZgNzHg@m5XVw_r)VP>Z zOj?ojzU=$GudTc`_iek0a%<+nm2^Ix!^bv;yL8hk@6CkOJ(=YC_Dbcg+1TBh^}c{f zU)HsX*53EARaLWIs0xot&dkx5&T2tYtkTc76l#}#dP+NQ++POmk9ea2ft%0h~!fQ&dnC1I0t%qhyk z&@V@+aMa8)M_H9{h$xBX1ZyjIXXOLmsw}#1dl4}SOvZF9gT{wg0yIeJG`aJE+*vyGZk)}$_!pcTSg~C?BEK~D5JAxU2^-Li(5IX317Mc7Y zB!x{C3w#FroWeEz8$>f|Q%#E~Mwm4Va&_PC02zGRc+l+8+@#41by0E zeW|RqSzuT19dle&Fr72gtbER7Vfe6N>n}-c{#dgaomwbxsH!Scqsb@Iv=Vs`TJTh` zkh~vs7+Yb>!Cn*-5-6f$Jf>2$DQbkt3U8Q)sv;JWCRRZrhgqxA_JpNC3H=ID!s^Mz z;(HL2a1m(*CoO(rT)6P99%ib`TpuXiW0ROT3Is!`0rk!jXEB9lZpf{2q@a~~`c zS;)|B)_32Dh{>2FMMg0P3Cq|@5VJ)rtl>1Ag++^~IHFWoRbwO(v1%m~x>uxoR3&?| zs@4qMn^lEK5GFWsoRV64sC6RQhKu&$p2;R!qfCUWL~Eq0a0!ww(}cM+A{syxZFjG5 z1T8ZWDT#;o-lD`)_d!z~sLI7CXCP*Z6qY@5-v~}PBuHe{9=ptb^7LRbv$bUXoN$0m?IF5OH`C;Cd51{#YdNDRe^{MX3<6!8N(xLcM<9^ z*$K(o$L_+~7h&dobI-l9+R|EY+x>pM-72g1e!t)E+eSt5pzGSl2vIfS`}ShiwDpME zw=LX-Wa;a&teNTV+qSLCNtcx5{q44GBO*o7LMALU+|Q>avn<0Xzy0#dAAk5Rn5FKj zO&%VX^N0ICcrvfOpU>^n!z#J_;k!Tl$09$*Yola*}uG9fAgn5{MY~XPxp~^ zdu_{wiIt6|+@00jL%1^P+?Lp`*OzZgxBX?XDziL1e)MtMHgBf5)34WibN}@5Ls5y) z*!B`CX5XEPnrW8ZZ#Sy!y+>45o=%I(@_gOgw@kL>(Sm>c%U}P~fB5geeS0?3+w1nb z@4x%oKmCn0-hBV=!})&QzI=J<)^4%yI&9(F^_TzhKVBb}hYt@=LPeP>EX1nq`TDl8 z|BrwE<>_Hr7JB>k+?RH~tZolcSCe!nquMC@7a?!!eGT6_@^wQUSF)uybL zs5a0D66ccMWI1!S?B^lt^F8kp8>?Rl(0lP z(M%`1?RPazcVP3ClvF}RvvP8d%w9wlEYpP#z$G(PE#gQ7iU<#DeIlTd6ev{Uh+tr` zbC_V?hP$I*iHpLKaV*1A)u020G9f)x$ph3QVSqqZH=L9Ru~XrF9{}QUSGpse4D%-9 zETHa4$c>1|%%~`)iHf2e7-SMfQG|i2}Xxr38e;c!?MPw$qBYX{Dwq(s3CK2>yGAU6h@zfc^0LCn;EUFbb zBIuCcWTdEw${1so@m586YYT)uP+OrFnIO=n{MD6Lj70|X4RbOJ0f@hp_ejA5p55r9tsN5m=!daCyehDDnd z6*&_X3t`^EI6flqL(ajsg8n2{wdwUH;(!nimTnbQTme&FrBW5F z3`w+@3Vtst0&E(2ESW^YNH8>2v^WV0hD8um4NR68&o7G#7X<~fVt~X^5i=oe6J8Dq z#>#}O79fiO5+i5sCld!n>)kW(jzfN3M-G@Uv7q+`v>(&qc|h?nNqLP_vh&r4m_$;i%%p_**ed@|cYsSnF(3>>I8bYasym)*yg`(F2 zna7!#rgmtf1Y9FNNScxx^s%9$st6cARn}aY>NwdVytkg2!@cHOnBW-Iw5l4Y2`Um& z(a6w3o;!&t2#?``*DfQ}lxQv?%#5Vt$Y`cmZdt?&W@fXJX4bI}ppT;_l(y#}B32utaE8=uFwW{=TV=K|h8cD>B zvPEX4s#!q}RhAyi)mob%6Q|nZGk!}Wu{=ac!>b%D+eiTGBS~0^g^Zn*N|=NSCrIUC zwu4SF!@+PBdS~XbAS05A!^2E7Dxw5AfDkDW$G_qkXO0^Q#F1DbYWCMCAx+@`l>i#n zf%o9x0Q|ra5hX0dNfgRlUX>~36>5*I^lbEZ@8pD@$Ij!e?-3+S9`^G{hD8QbW8Z)Vqs(AM`$KU?@&yOEIfBEt(ynOn6e#<8IaQWN6*>1NV zzW@Gu{c;k&?caX|I&J?s48!|CIP*Xs~7>urHV&^@D~ zhKY*spa1+%Uw`@Wv6Tq*<@D{ZKUdP*+x_E*4?q9(b7h#RF!jDf)xO^&{CqwUB{3xp z5xw57K1Oe=>2ka8+qeyPl|_Vlv)%VpeR_PTl7IRy|MHLj<)1RLvi|nBfBN|0>15JO zXi#5HUtabP+J?u&X}$VQsG8)tvDP-Wmv&mW{dT+EwDh#B&Gh>E?WelEy!yjZl;d(< z*S;9{OuvttsP1Ebd3{T#^ZAl7@wUty!%d~NB_fxlWyb52_udSB`WZ)MHIzC;Q?6-PFQpfh;YR$NFfFloT3U9 z3#3OLIWu{xU|`EigHFFFGBQ$h+Ppy1gFvqaFqWoQKxRB~R#ldn&fPBR{oe_~f zl{1y1QV~8vHC1)nHdTwPau*gRiVQ-j6O+!;&5Fv55Vpz$3X@R4FlQxNQGpFloKK}l zloiV6IdD`^iAT^8%pc)~2K$;{C4pWpA}T*RE8*!BQ`o*SU?s%IGc-oD2@z-1bZRoI z8Hfv21!~SB2z|;((dn!s;v@7$GL9e^2END`&>@$H6sm}hK2%b($jnSm7#kp4O;M%8 zw2ChXwv&h%eryRuwTJtiXh3ZqM8|%pe3e98HqJa*gSJy>s4(+nUh|_Od`r+GSe^T zQGkduRMDk1&B9VnI6Z{KK&(T47<9`6_~8AWq67bd*`S%JExN zfPI?sG{9I#r^>8CpU)NKEkt%5>CPNfRKp4S1S&4#O06VQbuUrLq!bxd%<9vlO~t&2 z7nPXRtb?Y$sOus0fa6AOL}l zGXohxn8SSXNFpgRB0|fmQlxvh1w(tn!mK?r#_-l!1rf8C5D7Qg$7tGSijZgt5z~mI zAZ?nQ#EM`x_p}-D5sihD7z(yXs>&3Fx~_0^$bE;qTFOaSg-{aMBE~@M88>3$(wkidP$mfEbheQnLY~m z1Sd1k(2U{pfWq8fP|*=H6R7H$6D2U(5L(8BaV)}!^J^avG5c@`C5fasL^<45gd-VugzdV0<_>k!%N?DnhBboI#zxfTXOA2pQVNFiwWM$*w-bjRb z_}JY~r}Nvlx6|5P{pX*4T)Ua(+jbxK(AIA+&+d7<-I!$fF05@a>;3imrY6KPJce)U zvNW~(^}h6VX{&oaU*EQEDCxtcZ(D5JJ%fvgq;A{w;q>ue{_^vE8%wjU`uMQ^@pnHU z(E983w(r-@BqzJ=q0M5H5F>(ZGy+(qg>w)1%r$_xr0i*;dYr*nYRQr@?HUDhHG&)ypy{8;Z@$X;Hq+HE&8$B}Iq#5wH#?G5$6f1l2U* z!aA_tF@^|_NQ7QjrU+$a_|AuKm8KFPtZC%T@-OCn3=yrE85lxb%zo4i!)pizA@lGv z5D!RkSTHnKqHTZ)TJlU}UBD|THUr5p_XvVClSBcu(TthFh>$?SDU&0!HH&>jrZtPm z@*pNM&%CKJYXT+DK1>zv1qeS>MN+WD z5>fGtx2BnW$Iu}8ti+yd32ry|ylbX6&6IkX_cmc7H?z4KF)|uqtFFwz;|H7@!X8H5 zfk*DNG(TxI}y^?1b!rl`|$^=8KPiextT=zfqV1xd>7>!GJ-|;SdpM^$FE4(tEXOoIIAWPGx2GevnZ2^5))-j#ZDy&BhH&t zj|iamr;orJ?AutDZmLemAcn_7rm-h7v(V>gVmRw4j@OS_O_gk-<`6Y1hE)D1o1ZjY%Scm02t^M-;T7fC7?8RXt_* z9p_q^U@OG>;YNz;B}HOGm5|!v4ZkhevS`b7eAKM^uwiLZUdSTVk-HS&#%Eg`kQly_m`)n8-Oi z-8x6s?RM`*CbHf4Rm+&fIHO{Cx{J{HVHG10c8}|JZ)$yAiO9zwDit}cr;Lp7W+o)V z$9~^3v-Nh{t_Xm_;?B%s8ZoM9>;n~NNqIW0>#|#^ON6(Z{pcW_!(aoKJ+5fzijDEBHBvCVfPO5)pX@5@@;o}XWzzWY53*Te+>5= z@u%PYVe??(Uw-*XOe$l&oL;Xl6s(Gi?WH6Y^nl!Z;uUy&tlEXo}#fag9xbC$# z>zTgY_lQ_~7nSqnfhx0{!qu9Ygr|yb_Zu>@ic+n$QZr?4W9*`Ozus}I-|lx7wYGTH zwnwPFetUIWxTuyTrRB0l+Sqvz?fWQ3QmzbG!9+b$m?QkaZc|if4wsUZJg-2?j2Y20 zqsj|cF-DvzcSaBj6A=kw(137xsA^2c+kDT$Tvdz=RrvKPb?=D~UqMn!0F0 zsnAj8#lng-cojMTQN)8nPM|6t$pZMFA&p3%H88U;6eC>#8MQg_Ga(p61V4<3=zFUb z<@+cj)(q)>V|Z&OB2v={jky_?UJ{0iNm#`U)CxQcz-Uo%ZuP8`j$RSEwIvCgr)rZPK=5Jr~;x7(rN z!Oi?W_|6fKi;|1g1ZsPx!}tggmq{5-sWV*%OIjQy3`Fxqz?%hhX$EyuRXU4KCH{2U zCmmH1$Jh;-xDXwE#S@!{??nl}9VN|AhUeyhHs6wIc0^)NCM!_$j2YDmArArmuAJls zRf)E_LLeAW1i)O>%sdrzE#Ozg+BsD~MTckypMxq>6`R7nfQ%+qlAOzxsEP_vv5K*x zv)sg@LRF6z@``wuCMKXsAFgVc)U_%83c?A^EEO^&qCyTqLg(W#WnMFL?1+0&RUyqd z@_ALOYTI_ml_N`7M8Wk;&a>cVi6kNwW#NoCmKkDb(6T_w($RKORFQ!fJUyZFd0*pE z=9V5n!>cGVRAz1rF$uF>N0`CQsgYcR)P#sDNL7N;Jw%8_hEFooSVl%lE4;f_mAOYU z%ZL)~9;I#dOu}JITwJUY?n%NyDm|G@6*UK$GD4D+guFzUoW%#evnovXQK}Z2RTNv0 z@UpD;{jQ?i)Uz^ak2Grm6Fn)J1y@956tfm_GMi~e?vXsrs6y6pK8!3WeB`XAzb<|A zWMMYhGdW60a$sARrS+=xB6qjeS=gr*begrT_2S&J!pp27hYQdR6-5TIR1%woDk~@D z9wB1Hf~o{cVih4NN(#y<6)oYBOqFU2_PkO};_y&wK*JCi>_L=OXzP4GCUZ8fn;}IZ z&6M$!G193ii&@&pU_$ju3oHg%sun=GW;_`uX7U8C{ zEZvh3Nnx$IM_(3nTD4}anOWP~&LmZ}jlEckSbIlzgIKfPxUyLH;Tie%`ns%bS(bJ_ zQ&CiQ(erXPL3p|MW?8hfw)Cg<)U?^@ab=dbx9$4%ixho0^}eWTwnfk9h0ks2?WdoA zdAr^|eEL9K&u=dzOjJu>Rq1xmh;Sduvfnpr&5KEeTH7}NdcE5(Uq5{JgDvL>IxlC_ zcHPEuT0sPp(Czi!dUpyn6Uu-7$G?AQqTJ&Cb~-KJe*BSA|M2@iZrlCGpMO5Di?W3$ zuS;Yt>q?Zy!X)8?DpmD%y<^3FdUztCx7XMGc311YcWXUMo8*0a`|FpNEl$@jqYMA` z^ABI%p6}Zpdt~HofBEwA_I$mbmJ4|{qop_DjJ=|`b$z{UKfgQ=Pif1#$V&2fYUh2<>X@!QB*9w-)><|No5=R9{a<2h2+{O_uZ>PMOyFLjJZ@65h10q4HZ%|AFkH2 zGAe~MGn7iiij1^*N{9uEE~jrJN9@#ksfjl764BjLn)JS)FKFkXtcVCp=|s(@-Upsz6=8`?9}fKi@@i<-6(zA4+MuASLbAd^P=Ll4kz``@ z?^ZRNjk%Zwg*7Qv6ly&nN|*s*)@CMD3W&-f6QBN3QKD((VpP8Y`$Qcz@bBIl(TqA} z;q=HX=EQ^zSw&Vwg*C0B^n$3GW{oKw90)=H(?Bf0R(VRx=l@&aBO(%Mi$I9MDMtk! zvxp;Sh)N)z=R(YtFuhwgF<~R$;(hhSRAv3f<%xAyiHSZB9OjBL`D$eM1_zY z2i-Y*C=-%)m#OG*o@Vqy_w!*$NhSsrCcmP{JYxVeh>4^eA>Bf(BzO*44(A3e90>sr z{)HKdLT>!p!o*B)r)QykHknnJk|@HtNQl*pnRxCM1Qk{xyo<<=m>m_#a01DQO*^67 z!kP)vDRK2zBJ|vH0)llQnzJBx=9t#I4lU@2Z6)HdyPCLz@GK^BcTtH9Q3P$qKA%Za z({MZp2n8Z$@(42}W>n7MvrR0`MXj|eLj0gL1L#Ra;95P##QUkLW)&KiDLg$`^ePH{ z6KEHNPa`WtL`5=^q0(n!Wr9IM1hf^;R;7YoTPAjspp{6}w$eytR+hHMVCFr@v=Pzn z$zr9NOhp_=uC03|S#fdk?xVNv30Y1`>#Ryy$>b5Obz*_$l9@@2)u5z{h$O|<#Kq+) zVx}@8l$DZ5WkxAeMhP(msfQO8izU&%4`v%VOYayCHkMK$qJ3mhw$@17KHStIqDY2Q zl_d0$1#}@ZNL9on2%)$XU6J)_O*GW-G-4 zz)3`?lxG&j3SUGSsb+YUMsZbFq$k`l zSrx=tt}3bqHLj{{?vj((qM-NPb{n&xYHlCKXEAx1(q8L%=0;`lLVWN@>TGoYCe2i_| zg+-Y5-A&1weS3M?x0|)5()!b7jKRdMFW6XWmaK6J-|u%-yWV&A=+=uUNn!-?5K%Rb z7)fMH%YDDzH&uCfc*u%O-)~z+08w`0%jIN^eD@lWxwlT|%W`HVWn%K!d_+X{g)ira z@4ox=cKfn#xAVhEO?;1}Je^LZtgTg1#i&YF-N%q1W!>&Olnd4xY1?k2E$7!;?#Z8i z`^S$z{64v#K76O^gZ3q>Jo2pl_V%i#)~uO^kGj*MZBY}V?Y^xUJ{+#3rrlcm^2^V{ z%B=f79v&~-w)In6&G!A4L_S9A4Wg9R8)qM5zh2)eQ&`MeUlyw(me<$YZM$EV%l-BC z?d^HH-@p5OuDErjc0NVXa$Y~tdE5W-<@UObdwKZCw`&M%IJd@h&k!l4*z>$Dk888^ z%SlhG_5bZ=nUNX7`ttm?^kpC8_O>re7v#{%sTSE8k?%|P1B3!%= zr{dxJ>3kWZB9o21sx3Xr*7E~ho^Em)x)QgfzK<$n@c5*nn6)?<@kJolmZU;; zI!a^!j{#)m_v;oyMLNVYQ^7o80?g<{kP9Bs+6AH`pf8_JFeVmoo0#y3S%}5p(V-HZ z-JBS5F5Gig!r{Nko=1L613IL$nh znwin_zny|(AvKx16>82f;?17o+0I5Vi2|u+n%n}TJWKJcFwZy?WPwfR@PZGhA%ghaJ4fNCsyO-?8gyb1itkgl2TlV&m6&AA036thlgdP^;{B&8 zruRKcgsbM4ugLq3Ad<{cmB(O(7)AW{#lqm&=QM%eh-V|)q;7<;xB`p^rr4?nS(=t6F<~yQwhz zOxqY$scP=Ytem9Q)l3_mL`CiGdTXo6@Qk{>-7cr);dFWNux06@;dy<1t6O>aY3<7L z^x-ip)KsiRN_0xB~x4w^URE*t)g(#M_tq*;V>-loNTu!~M+kW%R zQqxAm_jOq!@;3H;Y|CjS=8O_#oKf!kzE~%+(}xd17JIJi`STBdY^R6y;bTd=x;O30 zyq-_)<8(eZvHR`%^~;yX$EWMJm-DCZw)^g$n)Lemayi*gFV}teayeCH6)o#>zFe-a zZ^X1L{j^@HJgb(aRkb~~@WDl98k{7oy;aqA+al8M_oXl4&Q*(c-}ek3+jUiW`266c zd=0hb`Zn@@ZA<^3|INR9x}0xYjD0ud=eO6_+jTjgV%*lHRm!88^wZKC<+watREo3q zypN)fm&Mjg#w`b__3PVa)>xF;n8FN_B_L;XeY^JFnwgrY32W0PuWzr-&A&RPMT`6Vww)7Qw^zIJ1osYfuy5Dy-U6vC%Vm^HQR9UzE#-iPHxO-8M z+~02cux$6;c^iF6rfl6qbUkfBB&w!+V3a0NL~X)8mgIy*y{bF`agiP{DiNb%2KOs& zvY4(oDM5gDuv0{+8nQA)h2J4wkxY6g_Ehyf21J_q@B%r`z$K?bR}fT^$pFDo284f5 z`0((_`?X5NSTm?9GMcrTwgdnzj3gOC4_A?Us9DXas#G*G4NQG8MDSur_Lw6}9o|+o zRS`~NHvA+i;+a#{4=t#>ndO8wTvd+*1448gAJLF>s6mJj#laqqRbtu+fH&I&P4VgB z*J4FE>Ks*RV&qVrAQ?t765I;|p36i}&=_tehy;uX)Au~6Vl-hXgUCS^X%VSOW>Qr0 z-1nf?Hf=SZ51J7vNRFt=46_!nBO|`Bf|4@4P;WZRvyw$bBs^F!Q1bB6T4SOr;+Tjx zGnr{7!vb3spoFu6m039>YOb*Dk8@3i}eJ55lcI4!I3dDXHdNyA*4s#USZThJ{TPp zcvE-;1U`%KPsx#%qbwp-j3GNwE(T1SW|HP?MkOX8%3=|zS$3xi7W?rzq-b-B&qSJXROA%G5my8- zA>{BJA0TsNDZqQ|wMeL~%A6*ODAL{{(D6)3d@Pm%fSC%B7g<$eTAZxvRm|*JN=%ti zDaC?E#CnoOI18y+W^JA#!bBtdPf5g697SY?&6YLkqNnIRp*oFqX(YKia}(PK-V zm{}1KD~#E46q_UEjH+6j83>2~CMH(RUsZSHkR;YbQi7A19I|2NBt(lKVYa65=8$jV z1_BOoKzdU*2({?+Fo_UTcqmW!s;Jycm`f(smt0%qPhXztPLC}^hAv8^ZC$u_6N`~j zQc01yEGreo7#kHy_1=VJ-?!a8BYW@ey6t|_#>8r^H8Zo}UMz)o+elO?RjpNK>#Ilg zX!U`TNh@ zXyo?#_N>+_>-2Ey-6B#YB2plz$lCY4h_`)9s;7@n5&ru6)~vzUt&7Dzk(lki_w!1s zd$Mr<+yB}B`pqk)iJl%leRq2NFe3No=dUkspW4~?&HCc*-=4odKYtbG@X?!HUthm` z{l;RzJjZse;?wDp5%=w8O_}v}-%0d#-%OSGzU{Zx%sHhgm54TFq2ksxXyZLIg9MY1 z-nV_fZPxm>@9t^J50}>VIzO(#yl*ez{@cgP{hrtR>qz=HpTB#(Z$+$4)c(znKmWBV z%g1)#9?uU;Q{VT;%lXrHpMU!0=Y8y_Q|sDK;vdeZ)s}BR|NQnEmFsyujc_KtUhl0T z9!2)u<)q<5O|2<#!TbIG@Nj9}+&v?=+eSpE^NEE$BGS*7HNv^dX5qB2>nS4ALxjzm z3ZXy9Jyl!lX4Ybi5$+XhU(SyYWBB#97c+~swlepSR?<_tm)tjJA-29J1ql(8=#;!r z2{Qubfh^XM$HVyR1zm&!Yh+?GEv)ENP=RMwV!nW z4xR{)J3e7j)2iuV_A!K6Rc8>2kf|^gO!gB{tx4Bp29qJp=AB!Zi!K!-WQM@2Q^I6F zNAUSxAY2t|R#s9FL|j=F88yS8k{MO6$-<~sc-MC0&&5JA1qIVvI<2dj&MXlD_bW4x zu|uz$5v&AbeI>y4sM(JJTSLtBJQWyqijufcWQ6Cc(2+d7Ed+sznt^hJbpfAe9?1Pn zG@7q$&JvzL3g6U_RL;y$5s1-~sPsriVk7}(GO?;K6=nccVU~)iwVTY6Flv8PAc7(^ zjsT*SS(FBACPA~uLRs0Yk+AP>|7tO2V1g!8N7OYKD zgITOtjL`5`fh(biia4o?8L4eE?LmZ!#4I9{NmzXDb|nZ(LL<3Kh9kiZelNy%be5tlNBXIPDl zr{#P)smVR|{krX|DT$T4s)cXNW!i{>YQ`7r+fG7#=_J+HWueuYjook88vr>qWuoD} z?|VcN^RldvSF>7Gv9_YzN13+1n3mz?7ge=3#<;z_G!xGXB5l?dTl!f^_HBon>+$k< z+wSfznfI61)8(PB-CD~ruCH(9{`&gzo8SI+UF>>ul{C>LNzZ-1H*Lz)*LJ^$2o1mY zww}*xMqbXBV)o%ahP5tcYVCI48~3kQ|J`?gyL|jTbKj72zCPSTv-t7p^Ve_B+wjx* zVc*Bn?BQ|=-(O#zzkU1q`Sa(ux97_G`1E;u`OC}oIqqLi=W{py_Vdr%7^v}nD+aQ+S+U>rz){4?7E{mL+sQ7L7 zPm6d&Z{zQN_`^T{_~Y~SdOnF|Mbck>{>#(Thu=Iru%wEKKWyHz)y72Y-%j?S* z5}$_*_sAHh-ivY{Dx5Ke zyG+c=QXCOgXo(_XDKDZ)7G+k@ds$mW0uf^-f-on-u@10cFq<%ZWE}dj;vyf8`I3kQ zDJfIoUo1=sWEa)c&Qw5Ku`%X*l23vPK%ugt92B9eDlCHH?5a$vaz`IURV7^30R!+{ zPSE2?#d+-VfgN&2rZN$w%Dd_zRVC8v-5^0V_5n~?gqdxOS>v67PKh)VsAi7Fw18z6 zqj*m36znu|cV77J4yVl$nRHW9)i zM@;TsOwAe{fkNQ{P6@oh7(2FZe4d%fB08BR$6v3QW6s#~SCN+&5d|DUS^F5%R|(3_ z-OQ?Ru?rJn;PD9fDA6In$xtXUk*K)4@N`;N#-Zr~byL`00rX5AS;nFIE^MX;YXaHf zP}SiBo+TB$+F6qwseHMaL!X9 z`G{doIHu=&3&Rq`AxWGO$VftK0n_YlVd1&Y5F#Tf=jlB0p}C?LC6urzuH+-%H}beC zh?ucHLcBkvCa7V@aRAB^Av`1nj}bZ-nMAb&N3cwlPGG?e7B=lnTon7^6Cx&4BNpEW zKq*r4bO>Y6uUAye0=hy?k90Xj)?2X<^OV=HL0v;sYMPO(Ld8)@!iY1&`5slO67yuw zNLO@HGZR--fKXmw`If@XT8D0_va$lr(8O#i%p7B2c4w9Mi?fQt5#9`}ssMoHQD%Wt zr%+2NQ?H3{wwb}lLlB};3T_q3ta8V8j+c#?(u$YUy7r9RNVqSnk((7)iZ#!%EDMDb zlN3kzzVFMrh$%{neN^wAgwCgv+vv^SpO6?)nK55=ib@tm-MzJ*MAjB!t;%KT#F7A@ zhns0{-GrB>`_i_zYtSCMfBW|B_1nwC`GkzDe(t?@5nfMgYm1of`xg6nyIzT?dL!I zUGxjda%zi5T<`nGkLTNVQ_-gnpIMEV*3&Y!ov9ulpVno)zPC5xZ_h7JADUJ)&hvS_Z~NVU`QiKZqibaS?$Zzd^pAgy zb=mH3(iiLY!>8}a_v`g4EZXGvKYafC|MK@sBeOQH*Yg9VZ(o1;E7NGLefW5Jdwt6Y zmDYPBQe36Ix6Io1`}uOdoGtE!?6%1p1f>)ZA9bUvxd7<+_n+tylJ`oh9G zjk9XP;rs9qW>fQ#dk$u4X6w=-Lrpz$L{?E-*P`^;@B0{W^I|xFRhQPzY3<|X@vY>v zoHIG8iM8D;1zHHk3(73@tDY_h+1LrmH$xsd$3B|U?PKP}nS-H|86~TS&Kas}Y0Sb@ zc%6BKD@j!~H9qvs*!(ff;xuLiwvJp{45+Dy@VxfTYup-iCZ!wt(<{O60CY)xBOdUFDMqDY; z{KR-l6N52b!J5EAOzG2QDATDqque8pZWFi+vqX;W-XtSp5jEr;r3&;@M_G!@a~^nM zgfnqcR26ccaN<%HJ_LFHiV-#f+bN`6Qwam-Hxa{0OjI?-#JsI3vs4sMv%FO0mS3NPZ)Bes>Mfu%&C-Ix+RG;JgGrwTk_fTaO~Df2$g1nmHm zE@LCYAVMVa(wRiQdB6N`!hKjrz^Iuu87qyRx{R+X&6b5oZC zSe2qg+-Vh29(L@b*e$Enq-Ftqf=Uz7 z3Tc=~3T{qhu`y{xfbOlSv_Txb94!)u2SC&#P{z*u{=`dBp|ic9@lm1#(TPbEF=W;B zXoWdOO1wqWDTloLd3yoBi7G<$fiPE6Ax15Wh&%%G0a6^Gz9TMpoQqOP!#y)ufb}!P z00?==2w^4xh_vhwgiv9|M0v**HW;JmbR%fjKIY!Yt}B)D)mTam_6a(aV(K6%i=*3wPMIK$09U zUNI392L(Znkh4*enMfI8S)2|ajgBA@qdB$Il&Th7cLYYpv}L%kn9A6;*KN0+ZAp<3 zrPu5A+)J}4qAe{e3fajlVshWN-ns~ZjYE|0%j+8z&Po%OlG^W^s%FtXb~PPi>-|I| z&6>8h?PDLeWjPObHj5Zc>wK`RQwB zeE<1-Rf>$>Pv?iFFU#}u^YinU-~RUZw{4^CZq`LPs@Bu#zWKI=3TKjV+ish*mR0*6 z_itbK;hAzdUq~6nrZ7FE>EZ4zy0_R|L~9h@ZF_9oKHe_IX`qWPR?*s(x!Y`ZnyhsJ-xo(Otna$ z@%Aj!cE4|`(#%wAS!mz)v2Q!?)@0jv7Bl6?htuu8ZB>ttmwn%Ew>z^5*Td84;j!KC z*U#TSygk2hy*;kukH7z$fBxyGk&zWY{>$I5ZTar^Km60b{PWUJug^bz_jLaF;X@TY zfBX62Y>!>m-nQE&TFms}!_(W__V#);?Pf|uY!Z}AtS0-|i`&xA)RL%LGr*axY3mIK zF;T6$F!O0$_kE{XPgCJpQG0pscHcLmYGypQ-xMQ?@JKCY=3>6xZ-_3*;$qp|gWF)* zZ<}3~V)ja=iuCfGWjV~QL`-wYA}*NSSy;p~B63<#g*?h85gNiXitkW>laMevJ&-+5 zR8^{mo3DyY6{>I{(q`dNRSjnvLe04-=Dd|rJmbE&vH(C#bi7KK!=tsi1^~(ewI(Wu zeX9yd+fe=yoR_FU#hwEog{x9ci3-ye1iLb8VSqIeGb;;K0imp#sp`Zm1g4!MqiI7f z3kz(Q40Qn!LdTJ;4`D_f(9!%F@Na<muYB@jH&nF>C}Rhmr1fxq!HEF9rMirzEu%84G12HkhOr! zO`%8!c|uG`m!IFouj-sR5l$Yb3gc;3QZiK`(5L2anKGY+IR#DyQ5A@K&zNZ|6BdL( zNPsqBy?GaC)nwgCgdZvC1Fa6K<6ss2Ph^V!NMgp6(a=& zQqWYwf@+hZX~}gDH66p5NE%941^9YaL9HMx#mORK2u8=_KD`;!W5+3?QE8@!*opDO z^MOUyTx;G3Jc0udm0K_)UkUrb1aYkVI)JDV&+xwq0*mVeY;6rG>}s zzKv~IOS9J3-j~Hj2)ATmwlR`Qnk}NX*pd}AOT~pm-N!z*`}NwCx#)5}?<&u?jhOps z8QYzh&gU~cb5(R3caOnUr)Ayl_Z*>StvBoK_4WDr`O9fp*LBIfAq>h#S#S78sAyf! z#GFF?v_@7&grqQ?mJ4%VPY?RA{QiIO`#=0w|0{j`=(33Q6k}p-ZE4H>dKcAg8_d+S zA)I^Lc9h!#GWq!Q;rZoRSR2X9*S8GXH|H}GRbN)-+-{ridsUfeWWa^Y!n+Tl9Fa?3 z$G-376)76~K9=6uw8^@x%k%R~KXp;P-?tItvR*v$@zM6(`93^#IbY6~^B&uJ;`Mwk zj?1Io-}cJ`t^NM*{`8TBUcbHlW!&1r5pTcy{$nGK@$zu$I^I4$efZ7e_ne-7>y01J zpKsS&GI?h2Jl|lr82ddqh-1nQf*MItOS&Y-*}GZ!*Yzh~esg$D|Kw&>VLZhahGbG&)fsBbcUV zk^vvW?~At-AYVl(HYV95vwFR_wgi?1oMJ5DrcKnji}m`IbV5b8(-%0=ox zG(^Q?WR)b(aJK|qga5?AiD4+J4CEhTYbHgGAXUbEkhX1Wy^9JL^i~1=LQTg`0-m2i zu~B0<_CQJ=W2kB*+^IzTPBA3~$9uW~!|YKrE-|ZSzgRMXoK&o%hId`Xs0dpo9~FIg zOJSm*U#Ze}%48OXgB2s0Y8TbHzD}M+`+~U;~`ciEteqcxdhW^~Gk0=c}@iFu+9kq;lFgn3N-22}Uz) zg1EnbjYEbZLrrG+WhSWSs;M9}MeG`I5#;%EWn^JiM#~A3&gLTpIalLjpI{p9YSUDn z8LhR<9J?b9=n$CFEFH|O;~#C-s^}19lFSy3soiU>;s29W{A-d%&D{^+MbvupB5^ADRCDKS_AH&R+ z)+;kkfRz!Gt?Ay*9EV}gm@c8)h_4#_gni>oH*p21Gho_Og z`~CIpdEds<$B&oG(|km$2*vFt6#4f0{NeFqZ_CKo?{^h#YNB>NokS!`%U08GE5e!ac+zSshbxIVwWepu~tu{9$6{`Bc7Np5`q(+_|A`LBPDOlj0x z|J3z|50}^L_3QPGn*HUU|K$(ge|K5>7~}Qlzx?Kh&rcsNEbHsr>+SYh!5%4Qk@@)a z^me@?b8qR5h0M&yemOr7(ePbWnR{lqM+B#H) zNs5c15ib%E_XHwypxdBNX|~NGw!xGN*dpU>Mo_=t5Elv$o4rx<4v{7`CkbVd5Z2{0R^H?hntkT{^4chsh0B$k}OrIJYpp@iQ; zSP>C{i*hbHXk~K8=YUW{z{60DMh4IOXbNTkAp!Y{j5_LdnORupHZ}i5IVK+h+y>r& zX|5Ugb`E@;C~#NS;WwQLKTt^IF@fMWPfy^4dBEqG?-9QPVI4Yai2}AsVllO<90R>$ zSh400Bh0GP(sMj8z~-x{n8cbAQH&YnIEK&SHlN#qs**bfO`1WLETZo}Hg+>)#S)b; zkC{n2{~(ZQm=o1(rp-c*q7oj3xG*9rN{9ZwIFp$?5l%YYeGv|~F)P4a3im31xA+qs zSy)xXxGA=+*C{dLanKCtQiB-0UJwqx977oO(A3>r(dl4DqV^ozEaz#ZL zhr={%_?Th@5dm=r1rAh3%%n}JD)vODJIpqcQVP~)b^zDAeu?Hc35;y7z0to-eK(!E)!San{c0CDuc*CdGdVUCNmWwFbvW3`-@JR z2?`To$w-P4o=`W#cZ~TJD>!S?i~zvpR0lk7s#HnBDh$?GCW1>W?8BQHkOOPRNF@$yL%`=3FQ4l8*?xs4C^NGgM zMBDuQ&fiuvnN zWh9Uq@WPd;%%t=8&jXuLRWpS(o{6)93DQ8s5hGuW!b4c}y}248V#M=L5!Sgr6;lYd zpc(BtsZUkSsLI(ej*K&cY>_It_HdVMMEi)~u&%fJM&XNMs!^&^vxu>8_ttFYHcae^ z$M6ceJUl?GyKUQgI(bCwE-Io;M2Tgj4^I}Mt`O4^k+$X$>|W`aUYVfOPUlr9An_ab zfp2-=cQs*_*XL_qGPAC4Z&XCWe)ra!taiKKUaxQGhZEYyE)PrQ`1zM#o<4k{tg%x~ zNti!AJc!D-=WlERo>)}(efJ1y{XTN(>kohYcbDJ(DcgnHMYNH1G)F{*=~7utG~8ME z;o%{&hVNr{q7>mVwxvH@-`>7``}(`z{%}4mPWdIW^>tD5?bRc;ZHRWUb~>xO?|y$+ zpZ4qXvaVe0?$`U(OcAHYY|Glsbd24HC((Z2&{g*MbiUp<9pl5(r*B`s4EJSSqshav zM14%!IoI>Dx-faJ=bl4f-hTGF{@XwO?f>-8|I_Zo)w4-~CrK>h|{b z;nUOCukrT$b+KI5{%|@E_x%?T1`QEh3EGKnPM3zRjOah zwxUh#lLKbUIzkH|Sj9zHlkJ@H6(;<$D9HjMHrxdn)6nxm$`YtHN0iW%_dqoX91*gO z2uC+3(JW*DUJSe+{}eGp*MS8it4xr_JT(w1yIc9`O z79H^tMWif~!I>8JK{UAzT)uTU;45pYXt-2uj#hjGyw{|~HoqS|2p|%W9)V}W`Lc}UMEwvexWTu4&dCs&_g^1zFSWszz)~UK=njAv)u0+0+Od4xd@OaAMvqu1pGkjZ)!NQu~7IJ;GGd?36z^4 zd7B*OWVBVez)B}Elc_=Kn2}7$IMHe9$yh~2VKc2MH3iQJidhN)J28*H%*+ZE%goj~ zh>pxOHFzJDX3U?eR0+mn6m*L!S49RiFcD$a-tmv%Nu*X4707hbh5%NoN;8XeM!~1Z zG)4gwOd?4U0bhX)Gb>1D;??ZIIh1gS6ch*|=Y7MZ z$|8C=Y33hfhxUvG%AU!_5FLkuu(AP9Ld+KbtGG{5baO~$3Cm0)hK3jsZcJtkOqrQh zvIs4rr-e`I8uunr`)ga4+wJAyWFm{OtCmtSR}2@G))%HMqHyy2rlyd2oYs>^c#Nel z>*>TSt*NvsBGX<(9>pB3cNUE#5xws_R2|E*K;0Z8g;-48Yxr=F96J~HWmTsB@#D+$ z^Y!}HRhg8Tw%e^+)7GjghF8_~dfR8P&3u~s+`rDSu=$uZi2g+`@XNsnw3u%>gjML zA~u9s195&w007DsG5i;is!Bn-1Jnf8gjwFHtW>NbQ2~re;z+~%b<9d(0aF?QUS0(t zjZz(Vaql+q1w!Tkg5Rd10{4+uOyx`@3S4JR*gRwT$S?xDJux7-s4=4ulbL!X00@cz zI1MW*9mK+vB@}rD5siJkYlU$(B8-KF7@dZZEX;6H3W{!orteJNXEj{a7EiLK744^#B$2}5$Zw2<|jtX zB8wVa>mES_xdg&4rI5aYJptYiJQ;PsZ3i=h7x*1sbaxg3*py>TyBgz(Qk@Z1q&(j; z^p{bUdx&u0$uW&^pR!?{kX4b0W}&fZl3jqmtB6t8#?q`aaS*H;GpFBF3-OUfh2w&9 z)=g9q+HIq#>hQ+!+y;@Jj?9mmmJgaIK)nM5^BaUDTNNtRs!Dfg;M8PVgqbmLizroj z1SAqbNZ-}G{O(j1;hckt9eNWnRgo+TPap~J^5M#=*%O?-EyIT&t!H?2+yfCnOqi!3 zUO>)4RwjzWYEE_a4os**)<2bZh$V|~BFmg6uFMcYw!suvfyG* z+MUIQ4(GtCjif3$f}#mO>>;h5GPQYRrJ76)X;INEYK{m&Nn7A_NBEEs(E-X*L`2oG z3lv@_j9^5PS-vBC6pD#0WF7dvG!X z+R1>9%f%~5O*oPY(oe+(KhP_ujJr)~247~Ru+X7V#Q%U5n(G(;>PQfs?TM=Lo*e`- zi-kpWjKLx#g7X*JOSq;~8xb~CO#C%XY$76}%o#8?VWkmZ0AmdN4wKN!oXc@S23MHy zK)A<<)*3kUDpnEZMnuDR5d{6dUyW5qxF8pcuz%?TT@LRn~#X}?OqY09D8`VcDvuVe(I;oI&RzfBKPZ!G^yCf zxZm%~vR2VPoJD%;>#|nX{dyn6tu42)J+;-eSEZ@>@Y{WRdU~p&`)wy?Qhqo;c%^gO zQ|$8bH~;RxS)P8A%Vm%eNoI}PwXY`}od{!z98=7T2{npYChHo}T36HS+il;*sju7h z?c0~H>*@S>`uK3N=U;v*lI8UHdVL++##zb@Mjao{RlP03v~73O=HV*5?f1>^%Dk?p z?Y<9$6&f$gDz*&whto$cz22|37!gMXNuJk!dHn8Q{`ntox104o(qs45l(;^A{Iu<( z^`_+Cp1-W!co4CE|M|mxyWK@H#2*GqQB{nL$-R`H;(tGo8BI&)M9?M4vr^!4zto3cX7tzbh%jI+y zYx{k-rl-@o=82BOK}3m5#lVnj>y*iA9#uwJ$9$BD#K zrhtnVG2t~)F~KB?d>$3Sz(=V_1tmhZM!~Iv+wd3yGZRHowsf3q5_36ksznH~Atr+2 zNaTQdi%bm(1qTHkNQx8H2{s|q{lEg4$xH(@-^|%8X11m?ahs{x{IqF$#>y0U5oH7e zq#T&N!kQ*hNtkHD5g027@eh+s2cE~w$cbhak#NU0mD5qmNy&_}V(vHL4mePzzMMog zqq2%Ta)#AZQJDeEqHxENUQ4D zl?v>AnJOv{-dCr?4Z?IJdtw#@)l-;)kTauG53}#Ps!o^MOrI-!2SmgyR6yi#J>`2U zE{n_xu&To-jyIoGbqvSI4tNlI+6?xQsda;M0ALwtEOZtT0JP&(e;4G4$strr0v>Rb zP+%`T7Q6zq&qtn2YZjSBWlh5)kyXW5=jK>%W-zi_YZ(1unFU0sq|8)I5jiJR#`nW@ z#62O#ii==lnC}t1DH+6yTr46|Lo6qx0W@N#IaJTwqyq&|ob(>R-1lefk8of92VGaPpK z#{kC`6*;=F%Mb~_kI3u|$9FJQ>pdb0x}^h?NUqk5smzQaK@)hz7(h6->ZvMW>Jj?7dCSvZ$#X6cv*oGK`yQ z2|Re>!zpvd=rAjCf~idGxWIssq^Pn;c!&x?VI~6QPoz_oT4Q2#ObBYx5+&X#BqVre zk~l??u~5_8Z0a7O%p@7kA|lK}R3%hqf`F;{^H9zf9)k0NvQTDuU|Cet23=*UMJY;E zWu{{c5ou~w$y{a%iVFN)V}pNJbmkzDaAS^4%4%lJjBZrClNoVH+eig|1k$Dfgr4Bk z__YK;RT(b$Bp?KQ82*UxZk1x&>IzUvgKv4|=eo1QqoyR=6c zjqk>+6;c(+9&XB)^(pJVkK6gQn9hu-x3?Gk4DHn2-96vFd^w#?j}K4#7@0IY-2MFU z5J5@BBFPlt6zN&5FJri=7^^gmOcu!)Cc+};^I3_!we5D@Zf_#&V~k9xiZS+mmn;>& zUEiqae0iuEM6_+&*XvW+qPw<3RUKpZ{MQw!^iLY(zmhq(^8o$CdieEj3TfBN0uU4uf}YDOhQfs{{XGHb&flbJBPI}9N?T$R-{o$mWCT+P~j zzkd4s^z!Z7>yN)Ief{w9dn*6>ktbEicb6 z%X;!LZ0S*DX1!f#3}Y73uV22n>C@#bIOUhq+wK1ImoK;NzOL)14JEf zfCO>Aw?yqbwmyN^DA{WWWEhX1Z5t0uM{&nS-}8|c3$SxL}4U zbwInzbR_w2;-GYwV&XR>OL;x&RU<9eWAtA)u)rq-;*wjk^MMQBW;dbOF@o`-A^YcY zeXkMzG?$*)-?c(R69w`yNK2^Os*451BVZmhmv31-lZAw&OcQ1%)w??#EURHwJCtGG zM9|Kudjc%e%yc$-F~h^eHG(p#!?Juehv`f8(?iJ8TO}nrQ~o|#?WzM-VaXiX;j0); zO7~&=52%bM8ajP)Oclav0N#7el$xu0P=+){0Jsma(SwFb{s_uP0}z-RM!mNf64bcvMsg)=L)WSYO)&0wj&`*jf?$j>)TV133Ix{-0i zSKaUxR`d4HLnU!s?r~?r1BV$k``^gKg7HI#9kVb{SEWDG+WVB~B*o3s^ z?6AaNg%0!SXiRe9re~WL%v4X8nzkE-X=prW8(p*5=9UMU0<$nq9|Y6EO{ObJ7E|;;-cH znKE^CvdEFYzFri~3EOtx!6yk*8mE=JFA^rWVJ(thXD!URV8Zt72n&$>Wql?3_~cyK zfT#teG_=nXscSY0VVA6HT2k&h7YG0TZJy1>rvrjKn`WQNWmKrw@c`lzu#Lj{FthDM zHcWYv0jSiD^p=JR>W$eWZXA)4yfK=ipkT$8W7E-qaQT$PCYm<8GX(3n_^DSDu>BF4 zwvf{q;+kJP-ZCbrp}IRwEa4i?n6GTYe_cB~Wj`z?POwe0C-^jCM5C&>=%e6iBG+=_ zri!{$O-OiV4HmB4@ctmo3Jn; zwU2l$cvyeWS!#CXwH+8d*N#r=vw*IPkI<(w0!kYXe579%_QV{nmpZ16DjrKdAA6a^ zm4w-?NND(JwHp7Oxi{&!C`bj#J!@Jt{+{k ztJhp+m|mJZdpGCoBsS6m>67w8aZ|^m&-!tljQU;a$#(OynXFSkE(K-OzMt{*Fyt6k zS>&zDoZVMLIJ|Phg0oy~NDLy(;X;U=HUK>OWatox3I$l0b^T;vW^UKN1$l4DuHMgxJ7K{e!7 zXv_kOk|_9B^7p1j)B+?|$vV zR@kQtoyy886R*Tr%o^{?{70dD(hq}ksUg1aq*`R9TS7CUq2nw&A^%c#Cd}j8S1%@h z-{cvXPcXHMx*D24@TgQ>CcIpWtHee{x!Xq<7B<{FxBP8srueQVovSPf0d!Hv4!kp% zZ0vAOloc90Q%_C?RD3h|azZ)J7g-!b3Z zEe=x!Lu)nr)@gmP&f*(9O9MG5kyEsw0L0JqwkpK_y?!g@z^#k`;G8O4r?3gJ8~lsh z4!mH2KrkN7@xPTy_^e4UXzGqAp-D~#A3ktfCS|Yt$Zy(L zl7%0%b=D!7f*A}1e(zk~pNu8;!FD17mJqAoBj^VPCHeq&AyM~4)o?|juX7xQaOzJe z8{`YD&Wz^MfMksr0b(WHcerRipsuA@_(v9Awr?LR+@N!+R=_+WI%r{T%UC0-(}|%f zd^T~$o7rem*|(V}KJsno_nxpPBXi^@dkJ)K30B**38BxCmtWy340{;?@g07(zeby; z!C9CtM9%ALTV?lkP0uzI)MCIks_(^vE1 zU7XWFut!~q)eWc^i%!!R#^Gw*(?H3e^iSCp`dR1ahT|Y^t`s7_r)@%ao3tF{*iP+x znf4FX9NC_EhM@jSu>@4f-`U>fncmWaQ&)s4AH}EXGSx9UJtkGxK1vr= zF5Z#bB*3dT!I$F-|L+Sqz4?~;)sgL8Xz?OcXOx`$!essFb!eE+ zQ3%1FR+a6dyZVgzVr{IkVuO25hc#awk$dDt8%vLDT3@%1TeR-9I$zs7Ik?#C#AWH} znF40m;}_b}6{s)dkvAb1J54q>m)nh2mD4-F(@vY3PS(sd*@y!0PVFN5?Cbqqd&2az zKm2C=kqyH?eCH(-{6gL+?rofXV`v`QnH`77bTO)vadMTn51y;T_&0S}y_B766irz9 zp>Wa`)30r`W9b>4E<+?PxjiV#b=h0EI!niOC=4ujc7{Foxbkk#n1!hEHtyty9L}29 zTweWXIDX5iePQ?(_KsDGi(2C2R>Lpm>!BX#cUPYdT|B*5L$8g_)tb!_%+diw6&0j6IRqkdS&eQrMLkhQD?aRphTl z<#`Jjf0XgfTcP*StYiabsLN>e(fYs>4NH(I%M#?H8cuaFny9`tZ#SL1`u_bHEucn< zWC2&5bNp*dY_!F9t}kZOw5?>cQ;r5HZ~xY>DX5;RA;95)Y2H7z9;NzAFraV};T)j0ZU*MtEOcYkm=cztOF`DXw zd&XS4kIq@;lh}`@SfC)#d=N`>+ywNtG9E10l2A!auN9;+ETV^gX~~RGisK?78{~z* zidiR(s{T7j0NFUY*b|TmYLD~}*bC4(E7z@3+%zxOA@ArpjG|>l=)xHuA-;XYU{GGW z-ieAWG86hhOpajKT<8~>c6PZ04M6CDR`5LRc--NUSs`0iuY*Vn4JS|gZI%S%c&`$# z7ou?9Z??5x@c7Y+?cabA26gkk|2EEW-zAdBgj`lI#;Nm~Bi&y9VSlPA5Xk^<|E0|=jYrKG0Bcj7jj|Ppy=|;-1k?al{elhL zTUFk$_4C>SMSQ)}pEhJf4l3HBl>rEY&@?i{0r%BKkDqZgPZ*$uYbIDc~>c&L;QhP95`|<&{h1fS0 z9EFs8&E@R%Uml^x=J7J5^;Z^oo{pmN~5b-sB7aR%G;i*{_`hyOy7i zl+k%=(0EWv`ueF|<5aE5$-VAvgh{<6$rfKQBp|%T=c7#>ea{kH^S?Y3N3Kap?zE1( z#WSbcyl-<8ieY-xWl^~<8h|&I`)yAKiL^K$Jl6-J_U>p^`|C_8g~wDhbt`MhMK1WI zYd_aJ%02OGmIOGmpa0Gv#|xyu{Xk(c#7ib;N2Wdt;mu%nZK0=#;>^S)P%iBTWCz8za=JAM&9mOV!Vy>3YLp=4+5{&u-osZ;o+YH z+uo$%ch;`1uDrr7FY!6M@hXg4CWCk#Y_(O6;gM6PR#soxi zXHS%x_GG2zm}YT#`Lt_8_%v1hM%-qu4m=bySNh@Xy+zqmLYukf!bzP>)c*PZ1<2$*Tj z47s?vAnFtPH6@BOKgKgoI{vd~11zvCe$w)+xH!2an@C5wSu{3YbTEjxx*I_S;&4ld zDLI3M5S3Dm{wyCx4zT?_YK#EnD8^YgH-FYHKR+Wi)KatoE;C6)^xm-|) zJju|cr3b|+rlir19m4n2YEyJ4EuPfqWvC?KfN9Dx4G+-WY@=)5Mf;v6jC#eIMCqYT z5m>26xt;Y^(4;v9J4BtjcB?vjoG_HGp2GJikdf=bugMH_c4KJeyl06&1`CZ(u3)iTnS8N9id`i%jtT)k_N7AKNMOxu0W02m6Z8NU-6 za1p-dsT_U6f*GKN4x^*JWZV7={Q1bTp!Lnc2lFJJg|9gp&tv!USUf_`gMXae2MPi@ z3Ry#)4@Xpu#C&O-0C&0o$dr_7a0cd2NeWody#kQm0Nr$?kbqa*YnT)mVtuV5-cTf- zTrmwGjc9?$g>KT0`ep207auiY?O>YX5w;_o-q(#39)HZ1K4=L@+=w;2?bWO#VW#+; z>TL!yBm3mhe?Pge7DQmR`S1c>I%7CcAtxalj`_wbgP$+6tBB=doC^(Ip%aKU8gG=$ zeuHt|%?n`ts@jDdLHU(WZtms0D60+z`KH0WH9#26&f3k5mhgRyEaaRXXj-c0SX}9s z{e?x9F`r8;e`H+OAXd^6oI8NfiJIWi3xI&%bFTOIYWsH>*0y|I+V#MzE(}m5JdwBy zUgdzB_p{7n#UnCmWdPlLi9IL*4(i=hUXcES5uu6(JwN6ZR{``AJt(nAVy3e1-LqFD zjm<5`9V9}^<{7Xl=C6IdI^kqobYYC>}iaAA{SRtlyTQNy?noIu4f;sxg+Y( z@S#uk)oSbx*AEZ7!8YSAJyeq)quAMGNyqEFX%j z#aiaLNW72C>Q58)_$|IJ(fZqXPMAxGtgLS7i`LY+ZZte*YG}yuToPP+`|r-qwY+?2 z`_zM2g|L9Zf9Wz({^;RJ99x3jc5{oev5sb$rz!lyK;yB@1~Qm9Ux}vSUjrLfn0G~q z6z>|sPzuri6zMQqSt~GKvKEY;YMUy_Lhuze74xH87wh0!iCRIjZ1*c%T-qCOE7nmf zD;-v%74ASJ1WcoLUs35Ou;u!)*d};(9R=ZM?>n77l_`EAYI-;vUviPsd80>6t{sJ4 zgsra4?1(oc@)(?IG=&UX#8!&sWZ%hYi28%EtUEcme%+Ri#GLCX+Ma5?6BWl zJnFpMT)a9sydlPn6>x9l7WVx+uUn!ouhYWL&erf9og>`Dx%$vOXqOm+wwmmiPHXs&08*=Q`*NP#TH!JKnRlBnt>^IY4H^n!XIoI|@afxI~d=-fv>wFjc1Lcsz zgQKi;yX+{8K@ySP9OSwe--LgSfzr3BmnseSd%2}mdRP=;E1FOT?ndw?wZUE-?wL;} z7)?-yIqW^)v*StUC&0giPd2GPb@=4Wd!M@85L{k^qH_M?cW4w+i#SCIkh+K2 zk06uuM&~g1&AJ&m95`{x-`_kj6taP1jE>_C#jG};X4#o5;we;A<>Pr}=^U;`DI_bC z%F8V@hN8gd;Ssz|h=MaQBS-}S5=T)nW(A^JYqs(stNr1*9+7MKnTyZq=y1`pNbje6 z8B_MN)Rfycb#Uo5ooKpJ@u1`yYD0$g-kWM>63Gmf!5{jR_Xbb_NnOZfaufAxpF?2? zNku}q{Y$xjR{=f|D2TiiY4(x^sF>QhR*RIiMP2Kes;f?_xS(cI z(_c`aLAg}E2Wd1V5)#5=Qr|>5Ph(g{s&-djO*T|lSc~fb6REU8QD;|{0G)LIXanc{ zbt7TZp!(%|J|Cpi;W6W9lfPlvw&rTO*Fw+#y4+5^K`VcPvy_+ZZqgQ;P!FfMI>vGQ z^#t3arZTVw?qWFGAEzkaaWf`(H09T2xE&w0i`};87HVL;KqV%6^ar>YD&gHZ#@qk zus)>xx!)l0YH-@zYR2`&F5I!qFgm9Yz)LDJ5{`Uh`K%19>>C%6{B7n7qWS!dRCyh3Rfi+of_44>KSUEO%*Lcu4KMK+u{J06~9k=Pw7a z3~D|twHT`bFlY@udi$L14MUSC7#B(axtvg0+*<3uD>h{$`JuUOkcYcS-bp=AvH+4V z&rTR4aLsxIdK8ycYbUx!!Jk@HlSvhtcZd(Od`+DABLCrKBJyZ`Nb^hiId>0JZ8*tb zlnaO57A8`03=F~aZpL0djTW1FP`(t5>7D!d94hbhuLt!uTN|XATM^J(ifLF*wjHur zqTEu$W#_nh?|$F zJiY2`Fg{tKo5?JiIbh3pITo0i_!f61<|B!M#6w`~$KDL+iqzI1f#UWAO$CF&5- zy?sdGfWW|+_^X;d{i}0gD&Zu5_*M=3%GDKG3Rw2dnE3pH@N6PY;ig#O3U}i;efJ0b zd^tw41fXX$DXboyNy8(qGn-z3!Lb$%b$wL6K3a>Vlg2CO9rJk*HQU$QHwS59Az@)b z0Ub}Tj)J|e@WgX);gMcbTIc0jPQ798f`Yt$MxENu)Xw&(&D=p?dq+peoz*vg04>Mv zQ(qQOLtp%OY&@A+_WNpu(~vkfk5zdQ;!jVj6vE)1yB+Mf`042iPs9E5mA0UZbWbkB zM0*1B@!Hwi#r7Vti7)Kze64uL`@i^S1dz|sRN(A^iHodwrcLP0$<>K(o5DT%1Y#B= zedTI@Dln{dUSDz}IH2`Ji(xVVHH%M^iQ^u-Kp@DD7UJx4APF+KFK)zFDnr%8V-o9r z{A2bxS%53~%J>BO03=V5Sr`|(ea8I-dpi{Oi?IqAkxRJQ#6NQ`!4A!}rt_N|6NVV= zSZihG9WtB0+=V4MXxy}v@$Utg2Mg+EC%89k;B}sBa>hO{B730(RF>HjbhFyqo6(1T zvguvD&4tH1q%?`jkj4ua)qh$_iiLbDLg0M9cZDGEyO_N9Lo3 z;7pPFs~8ZX*7v(QvgWBou>A0}jsH0lwmqGrY9N39bUC8=)@pll0g z0U7%v!_2M9-$0Nwq0{8|TACcrO4i-VTBTZU^ybupD)4C3FSbH(VgRZD6KiJM1hq=1`j8+vqeJ~fq&+6q z+Tf6|KR+Cu)*_fZuJPAKw4*xuaN1!pyA+V?3*Dj~^kn34ia=wi##=|Obc{_fPo)AX zXTWghSN)b=ISO@wrr#gp4Ph@+Q!$(_yK#kFPRg?4@96n5Dgt8SAemAp&C#~AvMb>z z`dZb2%S1`SXpT@xOc!mGH0Ci}N6j}u=y~SoU=!tO){)0!1SekG$|TgtvE@4n=Q@i4 z4~%RH0Z)Z*P5nw_WLJjIJR01P7jo`>Z7OS2)n3+>GNf@}=!kHuScccce@r*IP5;xIXp&Myp;N7}vzx9mENNK}zO827R-` z$P6Wk|6FH-R`b7@by_AWiFk}UJ<@pC1um$lQeDCUfeI-b^annW59V4ga0+0Hpr`57}PGNC`M=)Lpx=_dzYJzD*<~ zlf;R>q--*Yq%yG^^(2XDNcEP(pxz#}i62QkJ#?cppX+l76l&LPydaD)S@Qa0wLg9w z$Po6j33uVCr}rPC$O<~I=^{q&&ZeTX{RXM-YWYI#b}p9BZhb27&g%B`hM`QHd4d-a zzu!?-5){_fX_9BtD8qjog~&E1(&6!X7guZ-f8R5Y!y6mhNlh267dz(n*fWqJ4xcsK z7Zuh!ZY~c;J>%o?I~Kg2r_;r0PLeVwoGwjik8HxOf=WhEE6ko%p6yK=#c4ITtNp9c zKTXXUK@B?x2Y7|cM>nU$>bOqrwwV*-pZVfs7Ov{C6c351_fsZ7r3vWVvse{?_?O{2 zn~@pd3IBIqY||lcLqz%>=@I4Vuq)y`d~x%S{d%+W>ZY;mX~S&i^-d1Q!SQxg=gqNA z-HA+HZd&ZAt81plpgdxG)(d5S`xw8wtMkH}XnWTx3Q)=O3zCy3wnG`5t*r&b;cyqk zDkU3g{RR-uJ}e>fi?vj8&dqsRXX65mEI0c_MEznX(N`fCN}RrN7w@F|nmnsUM`s#E zE8n0z7dzdj@f~~dxCPN^2*2xh(KM}G3;)y9>Lj~XfjutCjkxn4{wq_~iJ+<=BF zO_jh<(Mu3)2C%I3gEXF{7ESh7((`+0f)LA}Lq#)d3PBQrrc}X4&sL~v<`_+x#nvs| ze%PPX2#`g$u1d2&n{HjczRzt4{&}GSg|Y4wa_p5Gl%Hto%_@`fPGqYAZ$-ydvE+(B z5&5oewQM&sDEE*COl=jIF@%rmw);BFA^z;|w`*2K8Ib;qFO(s+(L;jFRfbB8{N!8w zu6<097QCC_tCF9D)tkz@X3B}TedD}=^xSaemz3DK|F|H@%oAC8)JfOq ziS`ui2*mFr2Mf1htY0NsW?iBVep&DPeB(=c@>Vq38ZOZX=W`E44<^+WrhcyG5)$xD z6z@><(N5JW7iOs=H=(cwK68dXV;X3xyU{IIMB3_aq#A6+O9ybxuWR*Iyj(V1VSd9MU~n7+$gz_`?fS240wgS$C*V*C+9owKs^-mpxN zXAu)#78N6VOT!A*o6=}SnjxNRk|E{#Zl*XuyL?uE9$e@MDlN*inR%k@r z!y*{W$jb*1Eue%EMFvJ6lpcKkkgKfyK(6zTl72)*piImtx#95W9sG~_SbAOyPlVRB zhb+Pv4NaCPdOH%cD%Hb_c7N-Ga;%2pDFG6$041r)95vZ33fPOShyn1m7-(?9#O@}b z_RXA4*^oP0T|cU`woj*zr$8I7A3R7ZZ1>HWeDIC_ic4`3dA@T$y)hNFQ<0~m;9$dX zX|1edM2gvq_LB1S?i=za^qeM=qyeGVT(VyR-mO9YUFCqB;BvfuVZp(Bvr~;lcG$5% z{_26_khr-<9c`>};r!_ZZom0yDMR3TLDa#u$O%xz8bPN)De zr<3Dj+%;v1M_O6>%h{j!X`37T%|3C>@5CSTtAFqW7;w2|y$|4Lb#VX!;3R_k70+C!1`3+>{Fbwj@Tt^XlO8^8zCO%~_!9bn%v+N7}8& zh^=)j+cyA%b7E9FudJO(euJ(Xo7!=JxSPLHc5iHH&>)sJf{Bc_Ny%Hl3imc^3@M4u zu$^4XULP}7nvxyjO0B$AypjHm@s2zI`zEbXQs}`_^|xX2$eDfCm0T3Giejhgvs?u@ zSyHcrny$x9Ntl%rdVA$N_?`JYH4F1W`JIgJt`6R(7RgP<1j2X)O_y-(raC&$Nt+YA=t|!4n7T zZvlm$)PF5&$J~m+=1}Ji>=9%dM#*ray(!~> zD9W4FSK)AyFF!aAXf=UuFGdENU#Zo4KNd5MEBX9>BGvh0XL=33Wyng;s!5GNz5B47 z7ac%*4Xr2k-kS~PQ(Ll+Y>aADL3IOOs+BNW@xl$!9j!rUIdtIUAp-)YCajG;0itqLjgWIWK1cR>!sr|0o5%iSbXmHB z0=Yj|QjYKNdptSDQDjj+HIXc!1T{O7n`o7Xo+p;f0)#7SD|%lKgTKvMZMcpw2A$dS zkW%w{j5sEK-bT%QF@W8LOT`a5_*`a6_o7Y0#>gI+B!yIzI4SDLqkOI5leE;pPtZKPR6p%SR5=6w)ftSZacW90_vf6dE!X^ytjB%Y z98=FfKc8gukmpXx^F}Zj!3uvZ%$3ulCnsySypJ(2l_FAevM9f5du6x2Qm~S+N}J;y zn=cc)$0o&s8MeriuA<#=v6{op%yoa2gJ zD@YdPyh;XF3Se*ZB$WGm#2{5UO+J77Xcc9#*ZHLVW1rQOp2o)}6PmB#n5bUEog8KP zWeKgn$be+mmVZI+>qH*Zn=I$dO z7vitv?_W0{+fUad13l?ZeQJbPW@2nr*lpS#-#Ncr*?ETgujKUfVtl+1{3vak^{{3U z*J1N0ZRLk2(e>xKST*Z-7hEc)US^oi|A$#ps4UYWhgYnEUNT;JF9cSt z=U=!Y^tCOwF|Zq^S8Jf^t78!>C-#-}zHif*mru zS1nuRG-9eu!VJVjuisVvoPVyhRw=X45}ScP;pK% zNr5#rX57dvlP+-0V4*B`!hSS&h6SQ|d!QbQ^mIHdhzW_KMot~nk#VsWZ8M}nfRr2W z#!pPKNd}0>Lt7vN5n*$QJCxy<(c-$MAI3ByX!?EwWU-f)T?DrC$^_ctC{ zhzyGsp&ZCjMmEUx-P9Z%VX%CNT?DC!KN27hp@pSE8oUW6bokka5|9 zbGkOSr}?vTG|z{<^g8i^+fuGZUt`#XQ(1{^IYR$#O0fZdTvBQ)kvs-3c}NM9BG62i zSDe{KuwnAVB0CQZ$XJL}SiZntZL+y!8xwRZA{8P29~%#~cy05%;sEKg8;n;)YvWde9F$wlK;>C9*TTZ1K`Es@v|OQHpSUScB&F^p zZm$+T=IQ=Dqy2_kE%ViIJ%|&%YteUk2d@h0G62Vb8JIOyt@`vpMflpo4D&cTMN>ji zjI){ql+pDLhyMG1FX*jaxVT9f277R7Zm<%OfW%yDGZMwSnM4 zHP-|Nb?r14fReg9yTKU*-?<}Y%%K6L0KCe)Rsc0 zw0c}HVAP@}tr=PLjzCDiWH4qVfL5*=uC-rZQI{He+yx1$`TgMNucN;pNt6i#6M6lFj z{qcG_D1I7u-JB^?Z`^QNiC73`3le$oeO#aQ^aHlu9S@1~3Tw5loW&oG?|9t|lwa*% zui);N1bHfiTnW;M&HqF!IB7SnPmxBeE7(W3A6F?5qsGmnN83JD9bYc5!u~w!gnlST)#o#jyD5unmao>9P>(# z7sT^VJ2KQb6vD2?iIjw^L&~V+mV`Dxw6RlN+B@^(NDWCVcC%A5ApUR>4-oSDJ7%NJ!69r3$dtR zi=0yKVU1^hT8n93x=4gWGTtHWDMbAF^2;`8Hnekv5<0N!tX%jMN!;olUN)n_HSZN7 z8vMDLw)_iqO!-*Kb(X49?!T6bYSWlg4(Yxjxw5-`Thcw3N~dyyWFuE*{WhGGvi6$M z0~@M}@9g1xKIdzw;U%B_=!iP#hkI_N#{{1JP^+U6!GwhJ1E{JN%&feQ_8Z18Cg2Ul z$`@?|GiBjxR;-!ypEExPY6jt{(1D1%bhm9Ik>z6N3}^fY6v4HYmXK52 zd77eRfCj)z^DCs}BU)Bd)X8al)`_FW=B4j6?_a}cP|nxZ-=NnxKSxKW;?mr=gLylb z%MPW#W9S^kz`9T;w@YDOYh-P*gp#}UdKe} z_6?l*3Y3B2)b}$oRYaf&V|5^t>5D=rpATSr{Au^ZxFjkDFnVItESo^ssx*j# zehOKWt3XYPAzL-O^$D)VDPZkQXd@~`TPmX*56@(dOt0U-kXKd0(?K4Oo!-7T+W}jw z&GmF~Py!g=(7o(#r?Ryx-0rai8$IiR`=Jazdu2WodBFtcNt3cR7`>+x>zY2 zRkH{0fsq3XEF3=f%v)qz^v90#!dwhumPpFv-AG7DNHkzd#^(cFN#(-tC=*E~X9FLV zt3-$B*v)Ny)}M1S5SZ-uVIhG=u@pc3lq_|eKW@SXS`Gvd~{_x-!cv++b(V056@GiW&lq1N%|}p7ShQC4ADG_!x+C; zinQkyF{UPzB&94n;!;NG=^w;CxGnDJ{y?#^@zdxiGP?H|UJHYIk>wJjA~{p1lfkTD+}OK2MC-v3XwL=9?-`AG2Ltc`2~Pul&e7 zMII3y5l#5xR;wlmLTR?)KU@ip@p$UIV-MmVkzvUccz98>$IT`f(&nLX^U^L>qVetP zy^9}h7lf8_&$lxCP9Z<9k`}8NHT$!;v{Sg+K9j7pXj(m#<0_v?Cnl8dyI*bM)07)J zo+@-+PT!n&?p7&iA8hL>IQF4Pih^4{UZlW?&I0XO{b=s?Gr9E+cJ}x?9MB??iUwrM z-Up1eXqpU8x`;fSfO#}(v#_|pweHz@<-UD=c`0ts=uBhK2ut|%_e9=lH{O2RvX>aW zhKH~|+uJ!?$I1)MhoDdcAGG1^d#A*YPF9vDb(G9XM;LN5RIYJ>I7CUEPPr35!j_I7 zYl=k)v=|`W`02%+S}t$auOY|Tu|K@zsGW<_HY`WZq~p>7&;i+24IYlb_NQqXW~Rua zZF}9ONMweS&m>=dfpt$-p!QUev{h_ZFl8ohl3-iv(lw-dg zwMu{eDLOu;{2$}Vt17dnP|)Yg)PZzvSYc!yRv*mTEnenQnUb#Dm$hzEBJ-i`0r(l2 z{AAbMRBmn5e8XYYA?{sN6=g;mUmWBNUu&SDP#EbqIreW}4Il2=wY`H2?XZZMc zf6PNB2Q^YVddO&_QH-JxG=-O1$@ev{zG)QFyV~s8(GUbN{i3v5j+B*ku?c%waUd z?t+0B7n-b+$F2Nog`58wXMXpQ$~LQ&rJt7X@bA4Vl|8lh)hU+jx-W?@AS0wYX;Ftt z;2pv~XV}>ce85LY$)5x1IDvnA{P|@(zH35ioPXg3qn!V*aF>y+#uA!7qsJ|1^#q-7 zQFgQRyp$2Fj0y4vSack&igMt2xq|z$1dlf8;)jaLM!SsogBh%EMoPGX`zG zseh~4;WWLKCVKhyE3gjj7|==;UNVsJOE2h+V}i}TGQFc$gEJ* z)%oG}+#FkInD1P<8l7UI%Sdf;HiN=PXDfgze!!hux$FoH9NF*RHuCk~v;rC|g0cmV z7lX-4w|qQFZn4I6DZcIWLG(j7sQN_N3Bpa`5BXhs3`9A2k2IG!RI6qXV?=Wx*(%lDoM zG})Shro)sPr3i!J6)Oj@O%n01Jt19SqqEwd=K{K%Pb^zZ+VV)yj**lP>KnW64@EKq z6rHlMSY(|@UW`7dsvSGt|0;>foI=T@MOI-Jsqeg3)`FUF`6N@3)0RT5W2#R&Wp@k6 zb9k3rJ@p>Tf7Yw(27`;K>esvLDj6b9%cuVD4> zp=X+w>!&Uw)nR+>#GLC(f1>#C>0~1*lrYz5_pYd>6$xVDg`Cyf6Q}@bKxC>xwAli) zF)6S+fx+LQW(Y?i9(T3>qut%Z{d0O(6F%NE%-#LsV0#hHI&gW_~N*S1I zNIOiW{O7fuH>f75Ww4I~DC5riT_hwxAiy|JJ;(QsibBlZd;e#Fa|7@? zp~yI!S}GQdJ%jUCuigzZP9OvTFN|E0V4h zSu<&XK1fOm1(L3#EjtK^p4A8UsY#vC&M2kMz{I9QQw&-r zsOTUUlMLyJ_U~DCRM_BjVSn!QQKl+irj*~Kf(;3Y(?<`KzBU{!EVGf(v;Ga`k8FXn zro~$L!bBCMN=34sv<>}y{1F^x)s6K)9D>J&NC>a#0l zFOp-pluG5J_mgIn8rrewS`to&{&m=#O%13duS_*3M9OJ=0xrvJbVWzg&3$HzFl!VZ zEzSoc9ExIEXD}^a99)$j^~MM!$k8McvVMnCrjNC}JwmViIiDX~j*jt6HJ8Nw1^dS% zJC0@e$f1ZPwxm|UTV`Cb=#fNvOp*`jRBSU^B$+}SPwr+$0%vVy zd|R_{DFJxwG}vRS`%mSKoj1x_#!K+^wXR}eY36oP^-xUS8d4+ZjvC}#ikp+wth7dm z%~Z00CvBh@EVPl8FL%n&l@UDHo_!w3#>5&nU7&nbC>vr7EYpF;kjf{)hp&>mmh1Q> zE|9K+h`XYV4&dBpk!h7pp{y$UcJxM(Czn{%+pb5I;FWY&u%FR5gqYTBCH zPEsO8bUE_n{04Krc%0Xt`wJ+CoY3?0HEH?vj!_wJ0uCoCIeCfKQ~0=(bl7~f&xH5KafDbZbIVFDbBVcb$mQeS z+%LIo?z$M0dkndR$Yo}^4@2&knOn#`ba6N2uG}h_8~VNzdS`I4uxzO}qm7<;fn(2izYc6$<(V*Q{ zjm3o`9E^hMQR}Pst&z;{=Q~2-V{&p8fu!k(Wz&RX7whl^f!X15bZsVyQu!=QGf96# zhKk|Y?M`b~U*td1UvBoK&Y#du{g}Pk+2o_y*{q6+;Z;?tMp6Q z=FZoiM6B(Tml*xq?g_RF#5YHD`pmB7l5+l%Bq~vq6V$cTnWssM_VRnlC+dA~?$x1h z>py+d1(oYr?DJz8284{P!=7!Qm~lOSet-G4Um|4rvj!TMoZ64-R&%;X`)>nznT8=B zG?IVU2oeu?lP#rzWkwx34O7`4vo_E&znwS9ust?_*5x?ZMC8);lszYXt@B<;ZEryY@s=#c2cGNeH72CqlaZ`sWT%7W(2TV z&LuI9%Twf#yj|fu$`p9Z7D#SrF3e^C1A(umGvv09p5A?V)5XD7Y8 zSm#(Bqd`jpDQ+fmYcR|CL++>+S5O6S!uwp+ThiPQrJqX)E5n|b*-Q;b^$c3Z7PVP9 z_J2~hy+L2AXIVXGqUX=5+xIL1TX_>Se53RA`ZL4wo;)$*&sVMk{-_M#eu<4bHX-(g zQ>@z#w##$Fd}UIo+tiVl>43&~I!dL;ZD+JEPb%d0EpDS}lTSCHz+#w`w# zw_`$!IRN&Ht53II+HJ*g(D1EnkKtu^g;t)FbF}eFsrXu(jUiWjcvpZcOQU3_Q6sOl ztlhtI(+57n;2$Bk9)lGnK@@m~Xccap$1+ud?Vc{r&|7LM7Z#)Krew`CD`huNV!_jlFFLjuaFntCaPNIy*Gk3kA{OTPoH)+ zlsnYLAeNSwTeeewAGTiFKYqF~pP6vN=UaEQ6^gO()7MR6m++0)ENPi}L+=7&Sp6*AMdsb^oOe~<~9YbRMi*+dNvSm z6t2)h6yByJom7v`B5Ga&ZkxH6hKQAUcPfr1ZAWzP)P&F`ux9PNw_X~LEKQ9-YfK&| zt6T*5$Xpw?!00X4YA)TQ{YKi(xXbCfv=#=zTAOtP5!&G>Gwa=fdnCG)OpCCg8=S&e zscN+?l6CPBZSd)P$t^8ULN(tgN^6@wsUX(1(8QNW1Yv(0Vh~wXqmK8^G3Q|!6pLc2AbgI+WMgegwxLz_BS@Zq8r9P_{K>mzA!s9#08W} zRQ@jt`>5~MBgliIi+2`;*u~&s@=ZDN)Sk*ryR(7g9;=zrca(P^klIIxM6%-t_T-UC zIKqoz(8Qh)9O6b*6N93A8ZL5ScWd;5vxB8^ov;vWIPihxG0WIp2O#-O5ni&_CHT_#wzW+*3Q$t|HC$t={*t0M-9O zS6}O!Lv8{CSgl$l0IIYNZ$LC)a@j`UA8Vbz|MtS!hTeEay19zH^== z;z;#QHaxdeN@M<&Vnxe5%OI7K{kw0t+(La>R&OyrzLp>;{!|7%C6G<5CcF&HLm3X* zE>MSFCL@?fgRuCMiR-!x)vy1YGLQcaxtn$ z^ks8w--$l}g~2)frj!(C&xy2ndCVs>7;gKz#e1@p219xXJrToAp9H0Uy#xb;=e#I$ zP>$>Z;pGV>zhFuFA=#Io?ldV%f8o8OYZ^`&M+zqyKgp0wZ}KhoKwa^8jgGla2{+SY zP6)dr6x@M*{4rk38Vma?B~%Vt+%>Os-9wZ*Cx z2m(mL=IbyWx7)|?U32zTqqn12XEh##=Hj|DwfRp#ky$;UMnxc*mhMXM-vc$0Wm-1Y z-9JJvXl$OFCqU4rQ7=8YSEH-t$DSjpR5E>}e)DEL~IKA+O1nHbDf6i`)`<1zd| zRMOJJr78MU;NP=3q~pk1b(fP6Cp9^IStbj#HLEm4e&h&UG61XEX5 zG-!vrA%kfgQJ$S}`k5B#ddla)FCk3!rS%Dg`qK8_fBlxHb8`~r^8A*MvewpSKKIiq zE(N;J_gDWPwLPI>WpgD5#`CHR!?))|Ys%WW8a^VzHh@A!Odt&TCtrMr8^NFrN77>! zZB)1@|H{k%2LJRMWN9T~O6^KtNteLRBJI4nD6m5-tyfrD?q8hxJa#|QFLA~TpN*lY z(Aw6HT`$}$0KOTNnceW)?5|I>htsJFTkqXW2b(e}^9u_*Zu++=Y-uh<25+3;XIoBY zLK&MQ{_Y;$33T*rIgRL!e4*_L_1z8p?;yGVGn~XU0^CWqi-S5tliFixet0LEAy8JuItGU@FmD) zvlLV@^zT{v~M-SZYouW ziG?#SuPu?VdHJ6slK5jQ-Z?WFX*BJOfxZ#4c@{bC;!cR zKD>uWdG^wgzjK5c2D2B$E2#bu;P`` zvj7?(02s$X9RD+BM^9SD@+_De&bo6?z#j$rD-{A(b2`I{`?9SiAjY*eq-^^>gtB%I z6kghP2LW>o9V3NV(@co?w2{^q>ZPw$ zLjVgR3SHU`{Z$1qRrKs((aX_eBMUl@l5vKM{W-ayuX!d8ldJk8Dw+rSVkUa`4_-eN zdb!nR2deBD3P~9u++MU_F*3X+H7C_;wNTw+rqqJ{cwAoW?d}^eVThVpX!|>Yo$1KF z-RNi11!Q?dx{$o1I1YPPDs%o`n1xf%{9#$mMy-oD|JE&Sg}lIIJg*JaK~l8MQ=UfQ$}tj{ z7-~4Ni;_S@i(9#3x(lpU{8f`oNU&$LYEu93urAr8rzxaN(%$L`LzOpAO}{hM=VtWq z7Q|yE+aqG}_U)SeuC4x+_0?7Xj;)h?OmmHI%Z2{7{^XlD4iWzj z$IF)T&Pnr=G~N{==3BR8nG>s{uCBSK=XWHl8Gdsm`25hcPFnQd-Tkxuqr>0apJ6Ir)8wO+n7Kq4K_KWWwuQlWJx=|8EPT&&4o|<12(6AhgN=tT z4^||)kF?&alV`Bc+6dIVf;|puJKZmfD?a31@lykrXM07gkL;WXQw61zxsz!bV*YY# z4-Xk>QP;jVJIPdj6!~+Idu=Tb8Hr4;ziW!2xmpi}b5)V%VQJVlq-!V$*`Ps=; zq}M!^+I@Q9uzcI5fc8@-Ct=|ySu8r<+k&yu~q1t1k88V_&c9|cJTpo41@|Ld!XUe)BY9z@Xs>W z^b}qU(16{gme(>fvh&{8VvkDXj3JU4W~{55^1@S2NHcDn4O)npYJnukk7ZmP>T>_6 zptAeMAa10L>tVzL0^z&iP`yDu`ht?h0R=DaTicd)gT4}wv`N&Cy?ri)$dtYu&idms zy^GyptiwCNE!T&#aDeW(R6KgT*B7A&C(iqeB)W<+@MV)BHpIoW$zR-G?7W-_@9<4b z>Tup7lqL4ovouLTg9e5&E^%u^I18hXLMp5QW!T2ZVAf~*h$IsmBK7+Behthy7QcTi zSwEGmz|Yca-7|XW_1eoS;5)^KdK?(8*Vc1O6~$-&A`&{QY^|-i&QXjGZSK!g0}Mpi zGkU?owm0wGQt=dDzr)SUSp+wJr!2OVSP^c`fioh#iti`=P2dHydl%+-iq6OUiYCKi zRlFes2Gx*!Fz=uBJAxnk+$7TbL|fvV^hA=?WTN-Se$t16BJ0r+}7IO{e|*1mD|<{A$uyf zBs=Nt(`|YZ+opA-2W8h;bH2nlSN+`I{0RumMzw}n{>Ml*{vp96QHRIV%(Y!=l3egq z?W+lH$WghhX@2XnX#6AZ13+SSIm?4`X5=_&pUUu^jfY&zUenm~a#^Q5Sl)uunA?&= zi~9g0R$R$}!J5km5);Jr6>X5bDXcv)aCyZn%7Ed^_6w4~cvvYSZ4;sCCeP{$6vv^- z!pxE{bh)?^byTf(rf@<61zN-bR7CYTS_b}byPS#3u_9joXPjsBDd>Z{Rn!p726AO2&kw}bB3BP( zCf@yBzrct47Lw7?GGit-;IIKP7Y`B8D}1Hi0azp&XM&0da-I+S4Eaj|P z^~-TdDt#KthW{QCRp%XkHXiQCd)kHYU{y0`1MZUfB&5d+>d$zCL*x2X?wKFPc4b$qLY^^2wMx7gnPFnHH4@wIDIXjCGQ@et$cRq z@Y3qVhU3+!+2b9{$BLez8&G|WJPirZD<8%Mt z?+jkYAu{JFn>16M)!8=Rk+e8aRrEjyd?F4#=!Vm1vEPifw(*5PvJqC3dJekM(?`#4wB?;o#?=0vvIp zG7lcMa63z&k)ri!Q%GJGM(vt&&ta?tj^&;rzDJzjbwkeL<=y~`7U_@F2;`^w_kG@L zxK2skCcoYo6{h}6!AV{tZ4f*v8^nDbS!R2rzKi}j1!}`K&STea^eDnG>g}X5FM6=$ zHQIwclRihp7s($C(RbtH(518n?izbRKMSw>o=%zx{LTe@A4n>daJ|;tHfxGJ!%K-X zPP(_j{yg^FA#|Jw(^`7trxtSQOo#dtS54>ZWuh*^Mjy;r{aPJieJZnpy_A*bGPYqC z#G@TE04e6MTOTnJk$~lKEn#7qXI%P7Bli?{cA$9pR|cRbn=PJV8xltFRH^Qr8^8sb zYIy|SsyY8~H~Y|m9A{0Io_ShV4lpg1nj=&O)bq@)o-!#Bv4gDfYR~1KO%Xt{RGH#| z!NsW_BM~qTC29n*`|yIGoIwzMuVa$AgEN3UK=PI>wi_x2x-|K8D%$8;Khbo%A#wY& zHTM*SpJ@NMg{t93$^MxF)-SIlls$02L%_OaN?@_)^eQC{mXHyI>8rd`9LBXXAh}wl z0(aX9;!55DTjknh=DP1qm)|n4Cwuu=auBIT`;Cp=U>o1<@1yGkixcIUq+8+x8t*%i zOh`kp&hnKP31ZIWc_GKHR!tdlxF9f+k@?sMJd*m-#_U((T>#KXa33erh>N(@b@@)3tQifmeVTtU8JC8lrq>YJ{9Fe1>?Z5tjSF z($9S7VY#LDATxv!?eYDnT2PTYlR&1V!Z)oDuv=SHU?YUckV~Xg+tmnbQ6$I~^!EZt zergmG4rS9Uc`-r1O=Byb$^kqsoNkq&75z{y)AOb|{F>m}ie)6rEv*Jv1TGOQ1 zdk$!MbURr_x@*qeD(EOZd9p?kV}l%$`4lI{g^rc^qDKR`2nAfFyjziAoi;Na7n835 z5pbD?uw|L~R{Mwwv2S(hwWOrrcgpy=lGqJsAg;WJXQO{s4>Aw&%B(g|=MHpwjOk)F zE`r99+d26qTUM<>m}16i-SozYWP`8%(1*K^+UHzVI=?9J54L&BUrkz0tz`CPktpi8 z-wPKm&zZGU5|aFLtJMLYoiibQ=>)YPLxT$pFF(-Y$k_^&M7SgjH9Juj7C*)NcX!hO z)iB?&yLTC0_O~wPPnETJq8E02%R}KV(`w8kl2lU~2u!ux`@8;k>nwCrAi2N4Nimuc zGc|lyRwa%o@c#;*s^Wv7wx(g%MCi!;ieF?TtsH%=PGCnsi;CYK$<^HUg;rp9`tPl7 z-?N|ieE6@)DbmPqJSc~?H!N3Wa2WU2kx=`!a`?|Uz=mnE*59E=il}kjp5sL_iI8Q2Aupp49i`Zwlj;<4f6<;bw}!>!#zl*>Ux%>eYOw$Sbh4f~}HODZ2mfJ~;+ zE*xuHd8X8H&u9De^vuV(Zbx5!qtj+^ZOG9 zVG2|`)|uK1+xpbL_zsiZ)#x4caZRy2W@FeSeOG`BW_IahYA4WUbL)Sn;Dn zSLcniaE|g~J@FVtS#$;BU*6O-ufkoq+RTHRpYl32X7UQ>w^zpl3}iosO6)Y%+~GAY z?xb3r@JWMx)F-}-Au?k~0x%-j5b-IHD@>pDw$jWIkbL-DKuDyOV` z;DlNh#ESvp>_-UjFrBKd0@bC-M66T}6fxw^Ec#I=E&VZu%w-K1#tymWXI)?X-UKTw zd1QJ2u&G#$k4&gavPYsO%oDS7k+`nbZ9`!=hplM<_qXtI9d;t*Hr#H3>mu$%`&yFP)bGbR?FOmhEA zq~mIEYA041!0j8eAC9;&-ukno7L8jow<;x7mO^v%b4Ci|&C!_#LmupDj>SAM6D&n_ zPbUKsMbH$Bg>iE#Y%Sasj$Wvy$qDjqZC<1^`Z9ui?LpXVPv|I7`R^2=K(KbdlaO_dbNXOM9Noc4=Y<<8xwL#0Z7w8Y8Q)7 zav*x~fa8*p%Ow1|2h^=NCZV3HHLd(65FOmsqH=}K2Lh+&RQ};1R&u_WG&GVJ|<#;mmUCI%Vk2irTeZQ6npqe*JyWnFr>42CiR)V0P) zR;fJqEW7@Q#Y~Sb&}M*wXG1FGA{G07QOhkqO^G!0@j7_ETT-@?cPm`7%m-@M_rAF8 zn>gS^BDj>78YQTRx-@wytt~Hx+Du{>LQCHG>X9p}QeRm&i5^NFzcWJGFdcGufTLdw z2q9~&0l*t8o9+FGaU%Wa&6j;e?}?fuo&9b~-l9PH#3Epw0q)vI+j}}@>~gF?Q4xh3 zP2QI5s84zOGalbi-4~|cBzPe2`DTGo_)gy}`y-q1O$=XH_pA5o=JJ7CH3As5Lw!#f z4~x{)<%oYWDK$zT%D{9rY}oDOtE#c`m=4F!XoeBeDK7?JF`pi0Q}0Kq&RH=($SUZH zI60$r1Q>S{4?soEqP#$B=Ly+aw>>jsF63f)v8bIb^m8GZj&ke2KKuZPcCJ2I`Sa%w zZO5=9u+uVsIhK|fLoGLtbx zh%3pbWuDpG6*}gzA8pVP)Db>DJbAZ!sr&zvcs}UVm|uA9^+`u@&QIE6<6L>^kOq-0 zH1Bku`_%NSEiG$yMVzvdRFY=@|Cf6g{R};2uDZ{cgHzN|_heg!RP}z5wz4jh5&;P; zz8YX;?*dK9E0cS~+|=CU-9ZXNT3=C$Z0~y06@Eh{>F5q1`Q(%qXP!|?`Wh4V?SM#kz^qSc3u>g}^o)#VjU(LFZsG&0nSNBhGQ3qnnY| zpB_tR8&VycnBhIMYD|T++QC?Ry-=m5N;wg@^2-}<8R&!D2Z?DG0|Bd=d8QIR(%Qs% zBAd3i2ryuj4xv!vz=w=htk;g^;3~m!u`w`^AYT!=y{fCCIs*YMV~n{op~y@NiBH@4|26?lE;bPd z1WtX-d_|0yMy3!sl^w)ft~Vi`#pLz8rp)|)(_|Vu(KykZPojEmCrjCkkoT{n&@;{# zx!(2J>vA_Vd;(M?gM4%z;%{Gba=Ak1tKpVyB#&aeB>3-|g}V<|OKvXFl5Yo%FB8LE z{h)%I8Y+@A38L_B27!PWJ#BYi*fccmgxvUjv|DlQbMt{1(|drN;r+GmQ|tt2UzY%| zvKFG6)Y-%XbdFn9=Vqbu)hH@zvs~lIzcWP0;A-t~FCn86Z|!ICK~ZH_5tsAq`p+h~{Zo&3izX$2|Ka z!xHT$)+UppsRIWL#K=f?kpxp-%!KNWCVT^=4Jas1<8ln*UJJjovIo?ElPFGMWgX<0 z<}33^tDHbs!}tmTcTz`Cv%cI1b*N`GQ3S-A8yY{zmU8{hh$(#moK zo+EvoE-@!2SMs@Oqq80-_xMCZxt;5Fxo8TU4ovlHse_imH}gc=Rc_o*8f%E4oxe2?Q2qGBqMJ=8qrt*Hjvv&;E0sz zHwRSpEvm$v?_C?+q<&$yP3+0Z#=)TPmOr1q=N9jU!R(9~d&w`#)UCs*V+?W>XxX}~ z`QvYpFYU1kQeG=RF+J6(-CT1)MXwKE<9iZuEmZ|gE%p;Q()axRd*79BC0MOTK{yHZ zYC|3NxtT_L$(;S$uO7|i%B6*hYpFF(Q}btYBTp%Jp6>3p4C@*YcmzM9#d}=-zUei< zTKC~CpW{aPXaQu}wxRp{Y^5&pFD)uev-WmZo}eO0;&-HJqgwO)_ulHvUwd`UMXKMw ze|rU?p5;USR%t)N_&VEKSN8{KLRmxiE=8!4uS2&+-+zfsYbM$^`=2$r!+v;i1VnW z7H|1+nokW!E9a~w3uxXGp3Lp=rKPe_9Se}%lVsZKW~Wch5shZk^UA$HKYY4-j~$LE zI%)nBSqSfXdaCNSrT;+UmUEA|H-{0ePQn~5 zK~r06xP`S>UQcc?K$et#4Azib-S8xt){`5wetLyfcUOKAkr zL7WdjA&1iYin7d@K{MgxM%2s#wTz+B=QFe{zw*NE5DvSOP-2Zu$Dd zdOSPc#vIGr1U0f2(zM#U)bM#L;kk{gUQF)6jB;ir%Ll5f5Q6fj4vwzOWG`sxSV=|@8t)5g&!_!>9rC&^t6cJaE+Ke%Q-pl zrBCV3fR6D~ra~%~>eHSBT~uN5|0w%@S0~f0yfPDEWDz3D4NWiA>HY01^={n+qT?8& z$3pimH+s;b_fxoN`p>ips8%J5Jb*n`ZA6RFT|1d!-K*6KWtambKG5|; za#N?o5>yGFp2CfP92SLvLwLq9t@nm0IQjDiVg;GyMQkGKt7C5u3T;i4Uw^pk1hwj= zG={ZzPKfHo=hCcwhc1A#&`p zG}6x1)tdCE&(`@KcSA&=n9>;0aTH?HGXlv_ee;K0PvlELlW^^$Dx!&c zS9SZOkWhUgZPy}Y7}C;Qv79v1M!My6ub9bDd@?TM6u@dR%;uDtC|Ak@bF;!-4_WFp znTa*9$)4itKojX=L6g(mb`0gqwUpbjIU;WhgDV4=;J1uuSqr8Ok)fA*1+HuNpQP(R z6tpv!UrN?CQz~w_9H%rUI z5)Yh+5=e>n4g5&E+ZT{OFJ|{PZ>wl6GNmChV2^DEu9ZNUAG%U{C-lgWOHJ-u10vSF zP6mK&2dY=@THey?dV{)edr{F9x~vUdvhh}X+Yn-UV@nKNK0|RcD@2shL=?Q}RXI37}IOFpg@5`R7C?|dB_2;8SX;XLSXlk$c zs+)~h;gMR@ps=*z)s>F7{fGZ1>$>cEme2l1($1<-t1k{0 z*$F=A$IRnCp@07bUmMh<*@pZ3ZxkYb(o!BLtB3o)_xI~VpmRuYjx;B7ba<~P_q;&vQ;=-N@7V`^mI5(`>ur8wm)#_m-ui)BM4@KmNJ!pVRugQ&8s(B_*4q6W;bu zlxk2T727WqH#1^_hMVnp7u<1%)<{}(`M;}2V$38y1_-M?c%}Rhpv3b*f;~!t(NKu} zvWdJrRbjdr_R!ifyd2J)`rzIHr1avaHc2*)*aBcw-Yx5(7YKrmr|a9gzeW`E-V#bj z9n`=Z|FC>1j0*0sSRH;K%9bjql7ghNNt*nswQ&7`GqBq;0=L>J1X;GAik(Z77M;pi zcy&rUI1fsFiVj9G%JS1MKh#cvll(2i6Olk;+s}*)s+az5u>5$Mu5!U3mh9o8{QQ0& zgib7J3@oWy<<7+u)s9~M>869!0iZLvLp?HBeQlUsr5k=sFh0^^L;uU^GM#-^_>dc!T zOxooVO@ATv%r8Wnm_wT$fks#!n^Xr(s>HP_rfZ9GoTfIY`6kX{5g3_vyLZX@hD8Y0 ziBdr6YZRXpCqTaW)PA1hH^u$QShoW8s@<`wkftk#08b?Jldorpns#Fta9XGI4_#QeC)AR%RYjxiNZ>Q(0tMIii$+l*5jQ#KW!4 zz_u6@Al+jjZF_2_9Z&mvQDl5RtC1VL~U0`UaLhW&9BVpSJO& z^B{Z`jeID9B+*yfnM>#n;ZCx2qoifUj=(h>6{x=IQo(9f9b8Pc7aXXVSuvwiAo$+) z{*&e8m+aSPPuWQ=6g+68)CK9VF(gC)ecfh$ft_(E%5kuMfyx0ruY{K>-v5~F{!2;U z+rh>jXcX+Cn<1^Id8up!$wjv=JuUw^`m~TJRiDg)sKhl{(^>uCeM_krokGVYgp}-F zlS97EdCtfHF9g18Da*bo>XC-Q&z_=ww9Y;CmxizAo#ejGV__hn*WyZ8wc)c z{HV>72uE3)R2&Fd{A=bUsEzbfIqpfvvS#yAb*28^w!suW+x@3X@j(Qos3tP}ug7fG z;lZ+9$EWiGa^z`~^zinFSGzf$HJxK^$v=I9jPbgnI-7J!myHEt1RsBiRAt4#eRe^g zXT+fV=0K*XQMq^;OgHceaxrX7C2XgO8vOl(Z?%y#x*y7#AZH33_QGQWYXfhyS24?5WkaPX|nQbYuzXn zdKwXS*^tcDh~h`#Hzp_dPq%{HzHT)v`YKtS{rb8>+mxR_4WIZ($;jyK>l!14s$D4# zjW||zSzKEI>ep{;7_P32XW#=5!;QE(EvGLdq-Au< zhor2a(=!qJ+!uk8FK<)HS3G@8J+4YhS;B|#&s6nk6WW%$r!nTg!$2Nzy>)8=$IWN9 zQbr!gKne71kRsn#Ry1dDu@7Wa;2`%=&IV zh;VVqR?KBvMn<)-Q3uarlsO}Vfq(N-g7V%xJt~2=yuSkcgUE2patrLc&epWZ!?t62 zKKrm#>qre6Z3@^GB(ug|&6xSP0Oygq*t#PRd2BCsA(Z-LUh5tG1gsc-3jSa$l|c&9 z6I1_a^j>NB^CrCkT@j=q&ORlMRTPFS1K2#!i^qw{b8v7egOGNuLb{3yF@ONsR}^l7 z7N>1dF;FOP%|(mC2GB}DX&WNB9w}-5$SbPU!J4$ezWj4R2Mn?r?{>spY=|(qW|?~V z-=!E8-CHa40*(8TESn8!QPFOvNi5Br{9}xF3qPDP!g7o4cLSYKxUauXKV-XD>G3k3nKd6%0)$#0lq6qjWghj2mR6?|OTPRi( zfhcP*VZ4wWmX;xB)1msx($YfO9v+;K9V^VSZ=HK5``MExF^J~P?qY+CY?SI9LDLN2 z2a{`mJU<^^=Eo=YJf36cQvAaS%q_ROrx3t4Sq%@oYVIYD#yG@-^Af>f z+g6{@7_2;3{4=kF>domisXik%kqGEw=g~!H;^eYCnylkXV-kdvqbwvQkcyK;9IJ0I z5yY0j+V~Wr3&Zq$Uy5sEe+-I$MZ0aWPyTq48#|cnH1b(PG;JJY8oaMNC7f|Vt_>Hw zaw=p3DGb9YUzm%i{UCr{CoUG^{VXlwCj|Yv`HFe>YD{qvi9nY4zka7>j@8ov4T=rFV!2i8 z?bPtI^K~(#qMtXD{7gJMyC*w%`N(z7)hSseQ2gpJ#U~rpjD*sCVRWk>=$hlXF)oTV z;FX&B3IwaZ4ohSWDUW8XSn_n(5?(M8n3h4gI3t}I-cK1_K)d*5H@gfWKz^BUSo;+z zGOyEy-LP%UoQb>>+2m1ChTYKZ>Nvw5s0sE()(ej`x%on}NySiaUxSQcQ88HWy*|a@ zv&;=unXb-GUFCH6wXG0t&S}>h8~!0-A;!sL1sWFAvsO;|d*4o*kCN^NE(=7N96too zTF74l|yEKXp;O zPyq~CeAQ0bo`BShIHy_Jo}X`@pCq6EQR+VZy>%c4D>m|45J<;)6!dFk1tKQ4G*%z; z24+rEp-188yQ|)$ZC@qp(@#6$YmFx7(;KH7L7iutZklH=y8l*npM5*Grqq zYz%YY*3W#k=#vcnK0mx6Cq1;1J-b2p^_X~XB>NAo><{BK^C7t(MSCv|`ZNRPX$+*X zCUXk0J15~zb@M@XEicGPA!;<5y#KD-=*`%>z&16fqf>G6dcK{ip4hsr%1>qf`PGJ< zldXvJUtjH9%~vPm*I33ThTAJ=++}F(QhUmW@v_6m>&N?{9n0G-f>(-#nq_9wbiE~S zx}u%)6~;b#qN99r$V)=D=yIzQKp9}yB{P5*An`FZUT|23&t-_N{OAhi3p>x8s0DX5 z+gpBf&WU!p(TnW=ZI?7`5&8eHf^6mGFLelS-1(@a+dGxui8M*P3}n8HmkS%Uv>eb3 zMSvN)3>HM4D3%LtP=Y&nM}=|9@OsW=!6)b9mn4r1=o;l89$&huQ59II(^tb19h(5G zXJJTZ1W7z_GI-5>#Zm|RpC_|`z%5ST=J5MG^BkCdD`e;~Zw`@y@HprHKK#WjYGIF9 zsqY9ni8E(Ic&h zXHtnAj8ZQbulyP8$sk4b{-QgRF8;tc0OT%sIb?_+(ifG~lR+bPaAlcaeo;mY0yK2q6+eA*=oz=pYodFMk_Q^Tu1C5U=vgx|GB_cDpT1+hrMXC%#|XYPu^7Z3+7$Z6 z<4TdA%Riv33xf&^08BFa8S4u`Qekac?4?PU4k@k=b1=Mik}i7=PmWlLktI+bMSXau z7vW;Z%;|ZNM(3AD@7Be-s3StUq`?L_!>XT#2=}C z$>J4MuMUeV)uuOpTCDJvNeDt`A&Q__U46K|SQz(BU1W&til^Sn{U$jj-*Q!iEZ#IH zJbGOlHZ%|u+y}?C>cOZNW72lnMSE;&X+HS}#t0EGSQYDNGb&}5S*%)KSAG(~l|T;e zb;gz4yEZ(46%j)3`a>P!LcRwm@zh(ypaVt*%20IK+1$)`ze$N`r(#K{;CMMexy?g& zUb+-b{HMuNM@R;?k#3s(!3MUcj~grYpvY)ITzj2f5m*lYA+n$f2_3cR97GbpFS#Uq z!>bYcm6r2qs%MtjIEMA!iM|{->?u;hGl6i@20-LPfK0;d3Vmf-i-A&*`sU)X;_0BpcuX>gMhyf_#&D!v)f?44TlzFo#H zkgs30?=68(=wcH@Vh+bi-|^I zmqpuFwfxb+obThS09GtTxkN0I_-U#EOcuY8`|3)iiA(|uvfO~vDSlnB{RDdCO_IK3 zv6*~P2AUkYkX$hwWHvr4mV+kjJ)QY;9}-Jh#y$*bAwx8PcV&^9&}Q=S%T~7 zYrn^Tbn%+zcK?C9Q`$oJ6%Zy4TRD(dnHw_ET&=3-g5z->#SO5@D&Opf$}9if$eNA( zbM@@U)${qtf4Mu|p%$suG^)dP=3vhEYwexH-w}}rM(rErzm=)&;Vn`|J$1`I!|gBp zfK6FSb=AY|>guOUJEt%1O4 zpz2SX8(AcrsXZpL^HfkWXzcFT(R@u$*!F*c6T`MNchY(&u(*HacE#9wB6%H>xmS3y zb@`|5!}4!e1vIx$_`hfQMF}2c$CED0>n=C{IB3|y=zCoQR(`V0tZr4`Cb*)@0o5bw zKmZ7X!%MZRIW@Ls%+P#uNN={>6AR)8g-Okbvp;w-c$bt9lmo zPA8AOq2%qQi^1>egwy!H4%9saT6iiw)YEaGaT2y+i)7LOLZ~n7D#4Ae2mVqsYWJq9 zf26PIa1qZZ`XoFrr)y^I!>Fef#vSD8(vP!lF|g#oO0t$T8&tkh+-BpU{eO&pnu+4o z<2Rk7dQ`qkR6atuuBz%C{dtenT8Ni|zMnKvR40bMVYUW*jYQ&jU#CjN1YG{-O(+s~ z4xU|YX8-rbsQ$g&OODig_3ta@9=p_4M)f8CFbUV{e%$2D!Sw9K(|t;g2C|Ho{+2>= zf?pz0GMd|Y?%t5uUyVEPj}ppsfxRYw~9Py3FJVMAwjirYI?8 zn`Bz5|0SLMJwI;sBGj_tv>F#1@9xUVDPfb=-}8L>&voV~tFZf*<7ef1MP|o1Fi27% zLwte}k{ZAb9gjf66NaW<8R4)->A{akisScO{#NG_u7vhYe?kGgbie~v?&7uL33wF< zliro0&F`Q1Jaics46^8B<|N(r3vkIH%RWm3S>R4Emc9 zEQ#!G@3~o-uk#dgP&K6-jj+Y0F}dC%=?YVe+Os>Y5-*wpWo~<&y-s#W3pnJ8*qK!?Y?zIPp{Sbm~Rbk0pE`yg)o4WZF*Xd+qnviGQ{q z17Bg+g zVuz#CmC}R+5!MEUD~J**CL)GB30y5@B4W-vx(bJ>AW|CP!N?Io7^t2|RfZS(v4uQb z3qMRW98wY)sO8HnWs+eXPb38AM-|eFG}>2|*hWoR4X_ZDfk+FjOdldcmAKlgki`hj zD=wchSXk00Rms4?PGZC)u)|lz+`Wprr98@u;3NkAL{vj~&Z8HZIDrU&QjLkhn6K2$ zDJPzXBaI3!;mk^=Wf{_ykdh^H2235-Z6V^6%qZ+#$x;!nN&#s(hS>d}PVh<-K5fdgO zYq2JREF#A+VT$93@Q24L@$va-q}^YBlM)}EE@Ry&94gNY_Zhw)_j_*Z_ShdJ{Ncm6 zT&`k9%&Z#8`|YkG<8pbu-n6mcR%yzeuMuj^)}MgH60ejicmx|)qD*)CTn9hawj zX|Ry0XD%pFf%-bCDv#sXkEvp}*T;YP|M-9XcYpZbKlbqAD$XOLYJcpRK~yXpKC_~d zb*QMI3^+2szJ3#;WsLneWLO28*zwp^&EoJq|LU*)-Lh=DjO{w-{^uXA`h0!8l71LF3~<2Y=L+kWhi{eC}c9&_gXzOOROhG&|N1m@PRa=JnjNRwszF)T|GppvAfN{{%60Az%|dRdcD1_V_TQa)L3PD65Bpq)I@cCy58U3;10^D#ng}E z?e%Ud*XXE7( zl?d9J27^QCc0;BhKlG=LY-!1r4yC1yo*@Q93+;M$`F!v>mnq& zq>FeMIR{mB4*IR|sH$YHGmqsgpNa&6mJvmq;R%nE$T9a}10#`XSJ5N(@toSJExvH6 z_P#(WoXPmAFv=@}87NJA(PE~UbEsycJKUEyBd!E}Of?S&>8iq!L6sFQMJom*wg zZ!!TyVj{=h3+)SMo4KjGW;3IZw}W;xYM*P*XL1VkAuVWsMw>EAMA8vXfLIs#g!#w${~!-&#ENm6d~qy z7(#ejxH)$(+m5$h7}0b$H>Oi_)@i1g_!JVc9Fl-)gP zara@Gr4B@IteL2;etYJkQhntP*6E}*&9Ke*} zDvF>|Ri%o)TUxj!;5tkVZ3!%j6HSYYsI&tCaAP-Vz8Ka`gm^GXS}}11<$1Z`O~gTu zts*k(v93^NIHr_SoZs|ZkMO$>liZknI7oNTc4JyOeqnbK0kc; z=*JajbY^x98$^uO#_0iWoP}8UdVR`_$UOGLJ+Ir7h`oG!efsc;Wl-_A zmzU-9FKoTAD2aqeB#p(2%MU*u`*G~<^tRvstN-f1{{Q@gKfJ%q8N>n}pN?Z!=6!!q z(SAH^jAa|g?)yAeUEPn&*tYK31q=V>>lYDYmStU-cpncDE2_)Whp}zz*na)%U%y@S z>AHOW?!(W2{&C;+@^q1IyvEomTx=pU+?H|N@1Cy9xI8~=UVePNd88k^?+>Y(;kGQE z#W^2yx|7d?iave#SQJdPY@7Se#3Ewb#v~<5ELp5Jbc`$xDq9AHzP`L(pG?iJ!)8Ar zwT!WD>-uy(+*_%}tV5S|^@zjgx;D17tydh`bIwQZ&!0ZJM@90`gTI;}qOwIPny#2r;=gR?0mgGtM+qAt}y2$?@H#J9SDvw6lgo#1TP!(0UGw#;}_miTE4me^`HQ+W?oj#e72r9#ZEyet1pR6 zBPmfF3&$~4l?P`QF@WRZ<(zg%m}!_Xi>M@2<-;PCs@pUecaiq#e0 zERfG!pB#XfP{X4O{&5)`Js@za#Q&n&P6CfEOsET1V zfTRSU(sVbJRRO~$BH^T}PG}LFR))wkq#O}pIjd>|uCU@m%WN3~5eac*f)K$U0ouBX z1e}*c@>m!pskaFdsybtMp|3$z>`$6g?)+V@5*4^xg%OY+g(W9*^bvom+*)JPbrm21 zib$9YrC?b|-pH&9XH|TD@V!tLv&P(sS=7?&+zy#pvd9C?S;S0PlPOtF1zaU3Az8B( zGLyH>C z(xI5tG!mvaR?*fHu#{l8N>I_MGz6DDIi$%X_+xDh5#)+O%Gm)y@bapS%*2z*MMWv9 z%y#k$(kj^`W~m`6N*O{_jusVu^sDi*7>mnj^9=H43WxFiv_QP+#-|zd=`gD7HxYxIr*R@EO%ZJZD zn_|Rv9Jvf4Dl@IfWts3ivXknIl%t2Je_tzV%mgMbmU$$lLkEiEL()t(w_Sf;p|MP$PcmLyG z{p#oHbXoP7a~)d{g;NDFX?X6(&dlp}p~}m4dHepy%k%SkdHVYDLY_JIq}ca+R;|n8 zhpURP9P>aZ(A(|n=j9X71drUdNrOjO zX~aY%C&|#TQ0s>oVrYeWP>ov+1=AZ8z|_j`22TO75zy(kU5&7NGM!m*!Wog#)i7O|K#c)0948H;)oD~< zY&FV1t(;Uu-7QdPy;W6Yo5~5RGt-y^|8=5D3k=3(?9T#m=UodNaY`E!@l!G~bzm z@@?#_MZ}DMestvmL-3uMW-ImzArY2By_StO$x)f8LM=zI%o;UeQ>bUg`d5Tpd9I?2 zz$(ZDx<0E?xaYJ6*j1R5^BfwO7%oqgKNJl!>@ZZdZVNSeNIldG6+6VApu*UnQy3kn7XylZaqv zgCdlLWwSb=1UM?eS{?3|Vk{m$%&I6d7PE+qOdDfAj>;0!TacLn%X#ZI`~8DFsX=+^#jgtX@hmQ(qEhEmB<~5#UCoF&16t^$DrN7FJWNu3~ZUPE$9I;G7C&886%tR75^j5g`> zJ==0hTnImg+n`74LOxG1vP;_Y3S6YKRIy=XwUbG3>-62@l$8^u9;%|o#a!iFI8_-{ zbs5E6EE>}j?*kqb2CEKHCJ~j%sGNr%e5xU3jHC{?qH0ADjx+S0;<{x~6{!|eRTb4q z?nBh1O}mkMKqx7yL_AJU0=N-k3NPUFpm&hYw4i@LG%J;rn1mz&&$Xv5K*OQp09I)Q z=267VLxAnE6tQqAk+qC$POM0Zm|h4bSxitql0AWC#A1k4t%m)EQB?+#&Xl5xib+ft zAy=vNM+FJVz8~qkNcwR%e#Rpc`{rjDYjVRr00Q_VU5`Tz1K|HB{ukA8S0 z5nBWZ(yW|ny3f7RiFu6ShnI(%bC0hx z)qcTB-)@%w_O~T}`XB!%tpCC9i3($lQuQnq+4l*MJ1RuY=R9^S#>&duw!M9OMCG!M zj0~S7OvU&6V_TQYb~%n?eSY57%bfFi-R5!F7|eWqdOr63vTdxiKaL|}UDuqRS<5!Y zuy`D5$|NF2Bx-}0MPS~TVAIW%n1-yydAr}4`Lb?A#FE=(i>$08BSd6bS5Y0y^7ZB0 zYu=W1-S@|F->**>Wq$qkvTO_U^S+#j#6_qTOjRhMnMM3H2UaXF4u z6SlF>Da#NwUIyFt=AM;mLL{W^0GJV-BM0R;Gq%3IeSwE?9dYRU(Iwo89sr1g0CVCZ zn$s(xt|S|lo-9IHSrw5G{Uf2Pb&yriOcD_lSH~7`6`?msh-M~y!#J11(-5*rr!Jov zm7*BRxZ}*FAwy8jMaqKQM$D6lRs_5avvHHvRia%WGrFlg&LxB@lVGw$)FAXQ0LlS= z;?~Q~hoMY5vE)XhiMfgzai`4kIYm{KRm6Q(B0ZB6$=aEb!HODGkivx_Mo2{%-EGR8 z5fNcFT9I3cixL!i8A7}Q)PZ4|^;njjzu1?z&Rrsn&JI9%c*dqj{0ztqlET`NJ z^*|FvkLk>9_63ZX1ZLPP3(3|9R#H5WnM5G?8vK^#Zy0b~gX19ilM?zRVqu0UQAB*Y zs6a4NbS_jaE5H*@*ii}9zo%+~h?oV^pbnG4kcr_6j)SV|NlW6iw4OfWG*xDf>^f&{ z2BR>lDj}&|ixNK-6Lq#Gc4~oTvkUSX2Ik&~h&amOn8nwG!6-AeqiuDViWp`m44`zr z2n?iO6vmz^GDY-{+W8;Cgi=WfaI?9KUOn1eQs-hy%v5617o_Mp4lyz2v{u`eR4+!6 zFzsg1qT^j^T9soABt!wp#$?#)uWk;-VNgU2#IvfHY8h5&-z!vBoRRiLilMFAEnQVw z=t1hpO6pbwr0f(%J196v`*AA5zY~`+6W%O=?J!HRu|#5jvyu`r7K_d;WH$GRc3csU z(P)WM5>e&0E(uiwlSd%xkm33jh*El|8fu0764Nqr_XsMYb%;;5VOg4y)ebc6Uk970 z2n+i$S!AI>Oc|*XL=>4+Bx*@1L?Y;GG85rQL69N~+DKrC0nh`H7f!PzlBgnLv@|kc zBy@QYi5A425c4r1U9{-EunPbTmS%KGLfs(eq)vU~tHQI*1seEoLcrt%O-P zDti+GmnsVT4$c?2$525Iu^$dxmUq>uHtN$qULqp`lRQoh?K*38$y8>5NW~+Q8N-yB zg0jj%O0aZ#8&*T~Xn}q(E*{!OpA&p2W(IT4Ou-4W^|3?Df)EcD1=3$tB*^Has3@cd zWGX9BRwX4d5eGu?E8_g^3WjV=$ZX2|NT_!bZj8(h)NfL)V%z)DSR0!5vYO*5-C-?0T`xA|Vn7D5a!B^$78R2 zte0&a>#}TFnZfS9Een^VM^MapOm`e`>loY6qVi)BlbP=Op(VnOK9_ab!Y8Rj1hJs5 zE~)n8fmk<$2+j;}gIkN?LXzW;IGk0gtn*!JgfgRGV^l@Zs?8GV|G2~Ii{na944 z1%br-{irN8V-b&-b8`1T5+#`!3GMe=mS@y_OFro5AD=&6m!Uis6ACHfs{Qdej-9B{ zhNL_^q{>!%`C*?&JobZFBFTf2S%wNTA5_GTV{Qu-)qTI2iJ1b-dhCZEv|Y9tA>p5& zK4uE}v~?Lox7&TYJQLURCv$fqUdLrWrq>iTVLkRk%)(vPMO7ci!}GXauGh=e!-KLS zr8Kh4hUf^N9{%;^o7u81tBQ`HRkW?wi0}*<%l7=78AI$gACKeybiIT}R33AxZbanH z(|50wu}lwsgzD-=B8@PWlDm5yQLJXkavVn>pt|6LZ1ft8r7(*r!w-jmzasa8nGnF= zL{?QCK~%$%h$BKP03+#{eTXV+6=ju#n~lpOKnAKPUT{b~BZH#7(Z)htT%3`K@$uAE zWADt$;___kp%4l^5(5M=*oKY@1v@Q@JtQl-1_!_msyV7C!dV54W_&7ZGqD~hQ+cGc z@sQv7L%>*=c!&xc4n84ntpu`?8s!xcRwVxb9?}+nR90pXB6o({YRhgwS^NdNs z800>ev1az!42i#)GLwmfNA(rOxQ@-FBD(bwMb4F&-7AYRf&#LRv%0t_5$uAs>XfT! zcTOcyB3K4849uh0?0n8;87JVwQ2c7!O@%o6&cnxtA0CzfZUY8(&Wvi!7`LQWl&Q`X z5GbaED=D+_IDmcbG0bGB9}XI)@F`Iv)0v4&2blUh0NfL#FrPv!jAB)eJrMtJ?yDbq z{lc07mK#YY0Kd6umSiFfi&TWkK}3pwy0hq{>cQs|lm(UW4Pwn^qAm&;CdSO{Rrqnu zG>epZSr??GVA?TAU>ipk6$J@nY6IZKDI7A>0Rb%}OWDpD3?7w*H>)=Rr1RsR$n4*< z=fospVd75E>OD$KKu++Afpczllg3yX!A{R5x9#}zH_a0EaU3F>!PA}9IRmN$2VLH#9 zrkwDFqulRYuYv0jRYqhM7T@Nbx$zpRcfrzWDi?8gqJjz&r~sXs&fa{oefk}`Lj_9? zcLf1tMyCxL!Cu&ZloHaQBB4kk6{VV0q|ok2l(QkUXJ$mM3JP9RCnWUl-g^_(R1$|VRD4z6)b(o00KQQ>N-D9XE(nSoRxf&7S zDM%@Seu;>Zm?fx)SQWMGSwmGMD}4G89jxS!r^|(c@-fq=&lD>g%B0HVb7thehtGXo z$Mt&E#V(htvV_k$UDPt-I4ZN|+-=wxD-JjnsS2~@{d?TsRrWn{Q8k7b$r@t1%8=l9!weO|W9cFg(R z=b!0V#`b)$iLH^eT^1dBzrFqSzx(UQ{dQSbiu{*<@~6M~^=~2*U61ZAB>QwzgZaw1 z<+P%*$l}M03?(+zV|q=ejPl|nHi>W+3-|pvbSN`Z#N$yBv5pJnv4_9iURmtp^QT2$ z9^YOD#djaC%c4&ouCk%{I3V%r%af>xmoer%j{Oj2l2p^xo+``Z2(YfuvvM&*y-<;1 zEXQ$(ENVhjyYEY_m+RC0@wh#X^>Ptc9k!Y+PnSwI8#cyRR%X6G9vgitvQSMHubNSrp3KOl z#$iIzpg9wR*2mz>aA5%yX;&%Y9Fj_lD56zu$1GJ%kL(~X7$LbEU5YY*0LpP3jb}1t zm!LtGeF`eh0RnF6mNkO1F>c$fAS0OMOP=dReGBXl~>iorUsJ0@AxhYxZh>C~^ zhrS!Q9~oxEsndlyR*(on29&5Je@Z6ZNyxxD)c!R)-W3y_Y0r zt7d_bbtW}YbzEr&po)ZtyFE#?!9URq8|?bkE3~*^Ipk>c}!4nX6SI{6J_hT zq^wo}Fmr1~gcUw6Ce9PzJwb9}=B`ZcY}PaJIWi)YG%5Ss&nn8FRA<>MT=g!1E}hox z13$t~)Z-vAG5L(l9Lvb`EMbIWF|$fIQb7SX0U@pavY}Dw&y(Mg&2uA_;;fUF0TMkfw+*b3*t-CpQ9ig{cTSw+^c*s!nTVXzN8<#7I@=aa2Uc%EY9Mu|YZokwA_~ zXDfkWD>FqVO`s*3(8$z_RiKQyDiT~`*3hChKB;gf3CprvJpzPlcu2cHp;Xe-eIZ!q zlPEoOS*i$94l<}|w5ZmxAbX=JCL%#35rmBbKBFI`_k-PMWVj7GjwvEZWqGQ1s<_T1O@)OLj+6#xk6;P+%3!ESjpln__$_8Xn z=LpO__#;%iWlQIlP?;obW;B1|H-JT9lgLa8f=EV9efkN#K}A~DDX=9VTJd#8curtK z=_Mi%{S&BWCSr-G%86N-nWL?E#4MG?ptOOO*(yYx_7y<@tLy!NQ&z$zlumg0Geg?v zSFmg&BgbHzfZ-A82;XJq z*W1JWfKv@ri78iw$1#tCnMGNZZ*OnwHmG6@%XFV}*dV6jI_5E_tJtw0>$(gxF;2>) zT(|4%*KdFM7k~b+J3FTQ>GDpW({cAx37A5khZChkbHK^2m8?7J`-|g)c@BhIse)h{xm+N}$ zJ5G1O^`A6!c+MhncM&TU9eRDf?%%%NZ+G`Vc3OC-+KfYO%;T{vvW%5DkOQr1SuxDU zy6iqL>#}ZJas^e19-eA?xm?qOSiZe}6Vc0M8|x~|%0##O&1{g+<#N%{>7T@;HY$jj zZ*LD)iSXyA=bat#x<@@h{t3^VRneM)h zu`a9Ga?FRLtQw*IOaZxC6&Jld_Ed6aHj1hmJeIKs?0~`|Sv1`n2WUXoLGxFm#vBVg_fT?1K-8-$C<;J4QTPtw=;9!AT zNLxqKcyLA9Fo8gT%2b1*uLeA$m6XXHZN!A2rL0y85aCAdG0sCo#;~%=AUwdhE5Rq2 zn5squQ>j>F;5tM@83oKYR8_3p7n5j&V|hS0ii$@1iHYJFN84I=vDvV!%uEyHRbs;8RmX)bLXG)UJ?5d%PI7l-U)XARv1-@U(VlEwqj{^Ok{b&`*`(6UEai^|V zM3tnfR1!>mu=ci_kGV7CiL(;bS{O4$1ZjuW%o{$mmg|E|JFpYHV6ns=YPt40YstTJ4z9a57^c;)fq(!0_c&DmZ%+xe9B1=!h z00P$BL!b#P2wpU~hK)HVQ)h3g2^F!inz3RtcWa$fRgvR6sM9Hxo%`8=o^>*<_-a{5 zPiUvAP!87v+{vI3QDu30gGO{76VPuo6-ZUo@J^pYr|5)E=mMKEM0)+76p>^H4aK`| ztRGV$CXPxLE^1Am_8%w21k&K0C}_zY5eaeEwYZ%6B~`5+%^Np@VGUAuJhs{e;GrE; zgB>PYq=faOnFpL`UBd|#OW{Y&#HfucuPEQqB4eqH*6z#gvL~ zWCoEyC5nzl;!?HnpoY+@*Tw2NMv0PA#d-lqI3cpCU}O^01g2tARZfCYMp=L|R)(pT zkVgv3`>#`(5O)z*WFf{3Z{4~KVGf@*49Y>yU@c=SfA|cQTDL{`vfuo;Kgtg?-7c5w z(}gk~`=0xaiAN5zK|Z(bTHN)PBxJl0<+^OgeBeTQJnk&@^z_73m+j)QSJdt8W%@ks z4;!|a#NqofFPBTiv98N;93FElOCByP2!~D&=KT2abNCdJa371AES&ZIZ@z!pp8nu( z{Xuc1JB^&_IutMX_WZ%^I!h8!W$ydFXVou%{>$I~<*$GJSN|@KAg_w72qiM9;2?7M zIIHM_DHkTDUze!kxK{Wu$)7%A8(Hze)#h9U;N_f zdin7DX+I86%1RPmma)l2)#mO=DOC}!L!+`Xmt|EM_x*-Q=5H@A%yPM2pP!!ZkNvmb zf6pRM&(BpfJ#7p;x6dE0FW+8ouWy%iyqKUd4T|3 zOpCe8XR-pz01=gkhHa}wpo#usSrt#dyrD0sQ@iq zJ-oIZ0t0yfDio>=D(c{hZ2Sob2qyDhBtUK8k6{9_04ko|!Pg$G5T(zReQuwkS?O&G zG(tnLtpsUq_cUxYk)DYk8U9W)O%MpoM2te*iV_nMie8Z*No}DKodO%&{^5DM2wEx zYD7<%<5XDTIphu-C8i22B~bgE=~7tU87uB1eUe5mOd3Q_*&76PuZd2n#Yi z!V&u}q9XY66e^pZe^r&`)Fk!4Q10zKhdAJChEzFGcMXDsh1Wzc_6w$02s>EEZ z0Zp|^>(%F35v@Y34dH?tVZ^!p7onNWMO6xn1&J8)pH)2fT4fQ zGwxVbm?XPrihEVfI?;VW8mlOJ;6kAHgkBB8r_)v8S48%eAtANW6S|Ar4a|5+U0zS78z*vSO3WBxaA8)2m3>*m^!N z6(%uJ-yddXW4!FgZ+`vj{c->9-8yF zrccUT*NuzQ9M1DUiB4>yo9lyp9)>@Y3|MY+S5B>J` z?Z+P?B61bg|Kk7le_3t$qksJ0|N6IIK0JRk(I1a*=?BJ;V?Uq)5c2fH1~06YUM!FM z{rYs>Z^ys<(?9)>{`Aj&?^nN|s%2Z;LsRebO+|{!-I+?2_P6`ocV(WH5x%bL?e1GSq&w@l|Lm8){MD}_BB|zd z8*0P${qU@}xBI$mKK$#KZ|k-c)zgP(7&C6KZ)k6QJRZ}1?uV)ZmR`5*@z_6IpMlVB z*QXlSDPt1N2(>{?89On11(T>M2}PwxU<0?IFyt`91*Z`XK}#bRxS`cDDZOxDR&oVp zk*PuDEUE=dQYN%~MBJG?3~^xcDJ+0~C?l&%2`-PUpvu-@RD1i-t62CuxtoY+I<8?# z@95Iz4Fm>Oz>mc!ss5K=-b)A_-xKc?tBe|N3 z#y?ii>;wR1=Y@+~0$WkrE31lC!lSk0ReeLju`R+#t--K`-3Y;J4*7L4@jJ+MQX=QP z#DGZg2%Wli=8iK0u!V2z1m+9n0}4*P3(ktrU(wMceClA{zeIfXm{#!s&?!iW2W_K465y2xW-QD}1i;2Feis;{n5s5)9J0XHxrDI6}++hKt zGu)GiSel2&Vg@Ayoi$i+Ye0S{Cs>JeKzI_y7&f$mgh_}^RmGTj8AC-P9JPcZ0`dKc zuo1OdjYN3J0GJBPFh=Cag7`uyGUzd;w;@Qs#6k8~&k&!)muBI)dIspYj?L*_f z%|4u*9za({G$3B&UIU7=%7#%9C$%Yv`%|S;aYe00Mym;%C?x9>ZELWYQbF%ct-)q& z{@516-M||X^$*z!?L;9)R$Df~ZfFx$9a&aY=s?j<;;K%_?@T95DjeyFdIx6U>9!da z9mi~SeUXSz@mLhEIaK(WB}21Hi^GG2R4fy@UP48>E>KajNFN{u85mOHorrZ^Ac9@C zm8f<+~1sMf?iiq7&uUKZ*=tcf}i!36_Fd4f$4hw!i zors}$15Hdys*2?jF$hvZic?sdMoUzSiy#J$TWW@}@5GZ-8jCOPX%P{ZNH;=ID??CP zRMXLx#f3hyXftkPuu*>nVnxN;gmMG_@Zp}%4NgpE82-7Jbpk{mB1%=~JJYNnmK;74 zuOOA=Yot)8~BC~+#|X^xEapaL$_KMWpC)qx^2%0do>!e&xL%;~cb)r=U& zEEZOP^(rEoJ|j!jmkx1bc*vf%2{jAxcjY<4hK-E+LW%!aWre z#5LKl_7)VWG8Lysi=2dgIufs%jLP%?+e6_K8-GTiM#Mc8gaws0=VNpdL&BUao?$Jk zbq_Z+VM@UP>0>BU*lH{*&5Q&3rAjJOb$>iRKhNVahFve08M~TAII%3-l989orHbe7 zV;OkQGHct`2xrmo>F)deW@Yy`#QxWk8&;g@Ce zW3P&s$NlZqj~R2`-flzn)AQ&1>sw^GCkqc7$No^^j~_k}y9f`_$K!5`h-phQGLP_e z**rq6HIbBjxm-Nb)2ByOF3a|O*^+{VvdS}_uGf8kg!}z|&&utxVdNE&u`C%0rR2J; zI)<7(_MM8<#1Ab67jfkvRmc>0Y{rdFm^Vs)?h&(?%%Ll#R-u8W( znVDXSAyKQ>pa!=tpi~Wy4a&$*SGR}` zSRRn6A)=LBTxObQ*Hi1qn5b%);hlNsyYR1!6zvLR`JFYgXIrd@_#Jz0#;Srr6n{Uy zdPKcKL4tps%Rr;F%*^oUf`^DIU_?OWs;WEM=~Q9$_SP~Gj8l5Zp0uf97jGAfSGa!V7 z5TGCxE3U}UA~SQuDoJHGh!qiB2PrI$lwv$I%2^e20*#Ok7$t!8^v<7F=TnAZ zLg%s-;r2QY5d`L(q%l$FlL8v~U3xBhyFigRR_Imq zs3NWihqw#^D^M{wJ-Oq3S(rT|Do%_FpL{WkP>CT#B19-76I7`PWWtOM@KpYA1(Rer z5gF4v*E-wz6$09YB!ATK2QQdt;?jQ;dpR!9)u#R=RM!1TZihDB2<$8rX zJi^76s*0#Zbf1s(xLhuk71P&k1B>oHv*_{m##Ji3EOy(EopP~d9*<#4qFS2aEONaJ zVm==GmoHzx{PssP`^C?Haedx0XzqtcQqs?U_KO+*dU+G!>&FivLu_GNbX{4Qp(Rsg zw%}>1fBjc~_2tJOK7M@4$hjY;_ISMBZx5ew%!xhs2roL+*pH;zD*T`QFaOhr&Hn7q z{>;=~UtXR+JpY&f&;R?kZ*TwVU;l;Ac|Y!t!*yAYh^##30pE;DsEyzMo4^03|N39u z@3&>FEcAH0#c_Q8?(?>+IghuO7gbtTOLDx$tniX3o|fvAoonHMZ`s7 zTtx{onOIoV1emIsQWbSt1F%Wzomm)}?zplsU<2B(;*Rbt@(AdjA#j9Ff|rQ423|cBSbiGnx1GOa~Ehd%HFC290fg%;aEc2C?F&vbfWaw zhTG-s3hyR3 ziD0VahT&QkPsOZEVmA7gfiOH-SP{D2Pe(E_VRvEB2-Q+o=C-ME8=O(*g^)c$0Sk+W zXi}@pnYo8x)66z(h;si`fgcZOIvDQvVHG5g(n$xkMZNPhn46E&T(DomI#>^!JTft~!(t<2F*){d9xqS?qN#350*Xw$;AFg@WSP|YD}{ZGnX{Xhgoe30aH`D04A5H7epvcl#b2nEyWQ3L_|W|8XDDhejxl#fud0{7xfGv zRgy$VV@Y6LlHnP_EFOxrkeJJZ`hgehPDJeU^zBt<^aUWoJ`aY95rG%pLN0e-*R}Wg z$fPctgw8mngqd)TpKGC-)#*En=~O8qlydC%!KG%xr6m+86w9#v5mh8=!?Yr27Kw|R zdt@HZ&mWOMwa*9q$H%% z=iz(dnW3~{1rT%q)F~BRmTk^Mbz#zFY~jbcU0@}eo+P|(s|{N!l?OB5_WLno-`}X} zx2)DP{q9;J8?zkyt}HYnue7swY~EFY6lLQ zMQWDi&>E}ggrvcQlKZA0DI#DBKzuOex1=aRVssGl7=};^6%*lW9W_E^s4>*L*W6Wc?N#3XA78PWVSVe$Z8kUX(Yptp!%>$ou z4mtO~-l9aTLmwQo5UUuNYA8k;U+*0HhKO20Oo(%B$X}XSB8sECk6MP;7Fye%?5 zSFZ%A)e`oq%u<%@nCsr+-l48Yh^48D;3vexKxd@kd+;bE4AqKceCq)FTANKoCP=nu zazIt_o0`h`vXc#Ek`qXlYHOSkf_*VzgI^%V%O%=aXFpKP%#7_x>5^Cb71XW4y<1!$m}m;`RGOjQG@+!7$i~ z-teJ8iA)uJ_uSBXl(b-4dNV^XCAE%C zJP=#f`%Q$astr|X>!g|xaU{ALn2L@$&02GL<`}eM5+nfby3E$S5x|(r{#@R@DJ;p+ z3a4tOI5N5L1UHaos#zHkz@78-OgFlN9Ux95hR!GZ^e*FwX(edihiGuL%wRmna1D|xH_ih#uM7R<}m@1U1%5l3e0-Mw8e7P9B7zT7=pHt0< zSbNzJB5D&{G7%3^qGU8fcoCyBgoHKxKrA8tcqZZE@S}>xvUnU^B2+$u%YI_ai6csx zVjjdKs_sd|YI;fqIHSoGTpHLaVg@9f)!S22m8tNyJzl@;Z@1NGH7ehY%Q=RbGNUYZ z*jRKSqJ6)w>$)x*gg5K9shLk#(P36prmF~Zjm5N>d`=b~W8B_emvw!9ewsdr`2M&r zi-}tJ;XX|@D%Nd1_G5jzp1wd>_+Td(stGJLv6me1f}CUiXJ*DqhMmrc!P z%qU{fyO%DPu|5+Slj835sOp3_`S$JG<+6#$zTf@WU*2xtUcSB^w?|EyPLx93Fwa#) zQc=uV%#VFn)5q@ekC;(e(+?bl9&_E6`*Dw=V>cvRJ~ABP|A zu`DYyvD*EPCw?;^E1`H z?|Vd);qjL631zD1T-GH^w#zk-!^U#VATHLywuC4P3MsMEb94ku;`$$-Wo!%zXSz^4$O-jzbk?e~SZi<#-1{uOnEYPiia@;#qm z-w#z~p@^bNwc+8g8%CrkX8=i|49H)h$m|?5YWBC4(^(vm!s3N|edH-a_$(Q42b_}u zF}%D`-YRjR`=_0nTm{n-F>`vduxL9FkoAO|W6I_zFdLWj#zNH>4Yk#h0e%q_($Flm zg;=WE+l^tqD}WIDL1$8SQgIV2s+L(r`5xQ{Y%86ctPzc#NUd#_B^PYK#t0q!lsI+4EIY1oH#vYQ{qg5Vz`>5Phj> z*9hQ#CvMlcs!_Y9`!&ZL%*8F#A#hsn5k?(Qdup1xg{DAOaEs3I;i#!1iv=R{)BS=i z2uT=(5ch_^VUti+oRipZWe)TWiN^w5UFJ!gaPw)*!oYa2C>D`0wW9#b1?tY4 zK{$hb-l5ydvZh};6$T>{WXw=fRdt&|W&lqG(%%BPlbe&%*g%vJgW(e>DrEtr80fu* z`XY(ie_VWOmauss?g&h)U6tW-5?{N{o>`FhP-N_GHc~43QP!3-~(BMrRgD zQ?ksRm4b)7n3iGh3Qxoq^pT>HREGCCGsmzzJG23j@=s})*pLMOueA2_r$PWcukR8@ zX^unR$5x;{+1LZlFGB1ZjL1cWcR)>5^zqnLnwW@)F-BFD2O>wY;5Kx9mR3l&Du8S5 z()AfG!tn1B_w$E3LYNAw5*%rrd?J{%5RMQ5c~LezV>s zaTzJ1_}E7kRvph=0yE7iR0^M>Qr%W_Iup7j1Sc5~ETZHrv@$f-3G=5)Mn+rsi#Sk6S^?gfsf%@_a}1i@gKtR) zJ?GhHw6M=9Tyus{EVhc$s$?8V$>sCdD{EG$s+Hg=jr6gsDmoAMBerqfmNhAD6)N^Q zby!v1-tNL|HjZP@O5Ep{>s3VO{-`SVIo4H#zJ2*|Sxi(!c^T{d_BM}cLtkEA=l<9( z*KJu?IA>Oo2W;QDKCP8JACJs>d%Laca_mPkMaD8#-L|n^*X6nwEn^UKM4GMpjIk_l zZ!bUo@WZyQNEcp~5np}IhW1*-sn?Y5FA4icKGsYO-zkE|O_n42#5}@vh zNQ974Ma)O+W=2fZhUQk7`rO_9x{ZtopI)`^dllbqk58XJ9>-x~$A0Kg==9X$I1U@j zc75{wet&%>k7BlAeuy7VRmU;6>vmbLNvmr4eK%DijgB;>+v8?pQDs!a1`)r!y@`pM zDG#G^kJsB>O<7gVUfy1pv0#w$jCH-(@FF^n$*#;|%TU(G{jsb=%_1Tsr%$z7$EIeY z$|8}FMsj7ozP<_5vTo(`%h#_ze*NQNlld{c&8bJY>0S zN6EHcri_xYd&)ARVQ*1`eq8~y7jn>Xm_(>5ab_mI1P{dQ{$vRyqR3Fzcd4$3B7anb z)i4Jj$Qv*X3rnE&iPe}+BA%GVeGaozdnz*nY3dcl3QQ6NQDVgDS&;>q&dlhJrAAnZ ziF*McqQ3N)J)KZBB`mDC9o|{%^PgOmU26&*sRz9HT&g17j{^~68=6_FiD*Ji{2AXS zprwh}%y4N=pI~l<*~}=RL~5~hqYY6Evn=y+Lm5`_!8Zhy+^f3*fh>5tv z-?S|XEYwzM7NMlmbs#E=e&e%@tZ>tvU^C2;nwh9^0N)a)cNu<$ldchB$_Qk<^hd3h zH7hZu;3g_Yf3Y+|M-=ynhmgwz7{ zEMiq7ru2~JkO?UuJ)$r(dJj}OyoiYeLtEjn>Y=JXip2iLMPpbIVfD%L*DPtt88l0D z5*g5sVMQaNGbcWO@@G>I80`#Z64eT9YSoMrk(w||c(93>Fe3S-F=otG=cO!w5dmmo zPII58qc!x^e>xsL0<@oaLAbiygZMp+pL(ao)567_$IslEOH?OQ!B_?8+!NaEn{Z>E z8g}Ryq`ytbrEtgq*9TLFIHRT-u&(zK(%azshyz3qrv&T-+{*=g=Rdg@z=L5<$KO@e zPGmw+E^%eBXi=VC?MgH*@j31=Q&NQEEP`usPB)caPGExu!`K1JKmT8NM#kx@UaZ4n(n|q5MK#usL3N$>}`DJ-i&47!xiSpAg0K`ScU)@ zRE?-1jaU?&e4HwBIy)T#kLtW+jZD@Sm5NAs&&@3K~fSik*SJ|IFjLAqUCgZB^B(BQlnv;-1jPocm%~TloDD_#8lv$+E@b&Kgj>c8ThW1I3 zFba!Sm6@u(7Z_Gih@sI&1SC5%y09S%f;}2C!9IQ9i$A5AD(|fb+|U2X*0CTPtEYce z^KezI3TfWA`;RdeoKFve&$Q3_YJdueN=7mzS5a}56P-H9f^&c*g;>CfFmufVO=<22 z5qCj26y740wTvY*eFlkW-ya--K3rbJ9>-%@*T@_uDA|NM2%cCb2t*qTA*iRqnTyIH zW;4~8u9D1D5h6sA;pLg``(~nK<9Z#2K8q0MN}4e-vdqJmb$R-5W-IZQ=#-)AHh+u_Nj=h1PV(N$6B(Zw>u zS@}3z1puAyk4KnYE>CJB!^dJqT#>>u#)ziLYS|WI64BL`{T}!Ibr5Co3?d!wPNt@^ zCWqe+zmxL){>b$0a#68m+lu`4e!D(j_slUCB~`V<=h%j*dd0e~Uh(qw`t;M+R56bv zz1%OKE|DZ+XW6cFcRY)V`kZRQ%!F`~6B1Nrlp!*+mJ%^+n(sgZAQcY>-A&X@BUmv4 z@j--=AQT6)sTCYI1n@X3gOo%pd*0(L615%?l!*}&1ct$}d!PWTwJA8g32l`K2AE-kVH+ia{<}>8FvT(xHUSAB%5>7s)mREL+EdzHtwn`O z3NQ*p1t-9X&1rGznWpCM?cU04;^KD<70^C`QaH_Tl%DUuLKRVQc%+w@3M667P>uun zfoKQ2R!^D0fD+abha5|Qa1yIYX3lV9D=PQwXA-}ls?e#FppN@s5iSlaSD@UxK1kZ| zMkJ@5phM+F>L>46MTV;3YMXPa0x9lf*uDg*S1@V;58Rx+%ru*1TBxTSqk;A;5$?xB z_>=()5vy^a&*GHh!wOj-+yI7GMnpvslc_P&FcTIoLe7t{R3w{dx9*g?n`#l8nNP=U z-?j>7pg(+SXsIeY^Zt~4QNdu!)bo+?w#jiYUz`omhKss?7x<7yvI&Te0OGt*Am~ zE8N)qyb>|fS?^6AuHDSlyIsQ*FlU~5R@9(Ii{5P@9L;n zqP9%;tla;AqH4e%&kFvcp8g3;Q_{t0aF(HiAd1=^I&1-q-QRx^3yEk%a-rmdLS9Iz!PiYVc)Svd_Gh&D(GBet~iYGu6n?QHvX2HeL91Jmo%0UpA2#Rvr=9S?h zI)>qaeScT0id0yL(aQ_fCc`H!kfI8!493bVr(3NRv2|{-OjNCu?aKm-Yh?wX2vP2q zhQ^hY8&!&s@}10FWE0DixDZ2ANv;EMPb+_;YhQ za=y6OJ!C8*!c|$MY%!0)6vq^1l>rZfnhmB*e<$4E#f1s&azR@q${@8!nJa^dsDivI z-9*xzvfOLVgJ`c+p0>r*ATo}OZnspMb51|dBePwuD#~2fr^}pvP;A@wE;iebNlY)V zH(;W~vOgwqU9Z>2{mo2^bGtre#(v+`%!ZlSp(gbiS(Whz>R7 zs6oS4lvaW!9?S4Je)#hJxcpMXp0=lqqlnB@SRea?h`04B70NQ7o-4w`O~@Y)R@*KY zQA+oW+V?#6t(V1gaGES8YPb7?L_U7__}CvJ zJooADMddLw!C?q*U6*i=B9_>$ z7aeHM4r2QJ`Ma-QzrY4MY`tvT*B`%JE>AJ%?QzpFa+XJE(d+I02raveZP>aU5vi)n z=)I(vNg`8Bm}QK$m;hFTOH&aM!8+ITz7#S0^g>Nip325dCa|^n({hf`KmhPy2Hp`l z>IgVh!~ex5#B4aUBk(@r$2e88bQ1DhU^j)}h^@w6I(;BBv(m;8K}})LUaFE28Koks zk}XbX%!L`Dg*cYMU|1oC?ff4RLC%Jm6%&MuU^UegW08;wQId1JX)9ar8brWwL2(Ba zKnJvaNlStAU4P6iWr18rA)RP-b~pkewdIm5K-sXHQFj2JJ&&iJrU!>=BLoqdT4q$8 zA_wO8X@i&`8tE`4DnbUdp6TZhgPHct2$-hv`| zzgdLdM;F4QPRy)oczC!!UI4?uP2R&Nn1~HQ{K%NX-t)ed6@X4|0!{ctl;7VbGVnrG z5GPlJs-l35s60ZF)x>nrInqH`0jZ0ss3>N$VOC9gpTIl>KQP&#o=CLh1jEqv0YsHW zm`yuWO3sjeq6&A+JJ_gCsRA*Vse#`E!U&%pvzS7>-Iyz**{X-S#^%qDzdsOW-2MsN zosqUy#X=CLanBHi_mIwbE~28QZ=il^#S6UCByu7bT~|e?5Cakg%r^YH*5Uq#o~Dk1 z3`yi%CZJ*}m@09cwlp0Y9tZcEC$Uf+1_h6h(@9_emTrGV^&yjyEOJ7_7@G@3a97Ut z{?-f;(0L|C(;SW&RxXUJ(7cHZGe*gFrl3=m$V@WZuwiuu)vFq@5V5F6I-j_HothJO zA7*C@gG5Xl13!0mi;55z!U5_migEX0gWsR6wmcIvxkpus$UN1Sj0U?C=jdjZ^Q_Ov z;%?(ZT|zU7)aA7h|EX$L&-7{#|G;2S_#6R!5l*vaiSaNOB!jIP2SJLT2cLsVMg+J& z5>~FH3Q7->W*+(U9#N$b2W3?DFL0tbRYa81xlYxO0>Q_9mMr!U#YAL?=n-j{CJ>if zauQe`0Z4$edr722{@a69MU?E}dg@{5#0JoWl9@A3ts6^J;As_>NSF5bcXD524^^sM zNtvm7ZWqlwR3d-(>~ZV`8o$x|n?vU@5=}TV_!iDAk~76$PLfHf((kwCCArx7(X2i|BNJ+}`&6 zv8~H?+0y6R%PR}*`=O-Ua=E>|3?0wUA4FJfd3}AkT(;x(xZmy{o}QR%y4zSpZEVk~ zi?AfuKKFIIF!N#q74Ys;m2FvqWJVB^nS}3^QsuUAAq+^tac0M6Sy^egEOhS5sD%;|N)dnU`gi z;$iye`Xa34i9*oDl^heZCM5}&7+uPx^LU%IF9>cKV+`UVme-5-h9r-aYKE< z<+{$|OfAwPeayLCu4$Ge-sDa*lkj;1ZOw>EreZVmh$;WvKF0Lj82P;*SH*zzoWoNYeNx z*$*Q#2f$AjhQ*A6Odul_N}c{x?Ila3@8O{$fXBUMp>iukKowIJ^>%S~{&aK?Au6V0a|j8l(rJxZup2hG znVD4YF+oJw7CPp%nBX<%1o+CF%rM>J()0-AXVyukG^R_XERi8-bUNJv)i5VlwlTK} znL1q-3Hgm=Ci-PF8KWm;UY%o(n4QcFFHvF1atMkr-khV6hzfp~Vwh(_h-5T<(J(bt zRhlCEx+)9EJrSj*PZ!acu4)Wh?U`6wS;;BP1GO5c-NiRv1>~Dgqb+xZ>w8l)*dU@} z4;L0;%2RAy^&ajCH$pM7si2t=pHctd1rgLw16Z%hmFM0JfpFivR zGdUn4l(l`#)ma7jSX9pXXs}lz0(E|+iPpK#L-B!Gu8=}-=DH#kr{keOtM*V=NZ(TI z;#I=s{gf6Z^^=3SAKDw}Wu#TgI1VBL?<@uwBmqRFibMuHDhbS}K!x^44lB9zT3axs zPyzT;YM%p`F(~5jrHcu(ijWG5U5MM%TOILE5kYM$>d07Tj&#)%;^^#kswg2wV8T2c z$aiHsay*<660e$(l-XGL*#d@N0K6Db3dU3oEy20b=9!7+(NUQ06EF|;7~w6%6lo15 zzHe3&E$WKC;_98WfW&ZC5huP^CXU%cn&}h2R3RlJ5+4O9D!@98ZMdr%P70&~<0;4a z-o$TI6@gaGQ_+aE5(G%UjZi2Fzy{T!cq{-+;A4--IbCp@3nSGSB%_B*>+h?w)S%Al z+5u|zyiO*tS}sY*5vubQrE_!dpI^EbG_jQ9EHJa~q2R8dA{Nmi^oEF1dr10)KR0;1 z-O&~WDxU%PkU66gm`YJ81joeO(Z3|rZpTVz5{h6Jyn2+ufCd7{4H!nI?AKdWS=8OB zI)b1wDT%27hh)r3FD0r-cjjCdWnMC2aRG>zH{vnHw z@Y0G5CN*UyRmdkee{Q2{?Zp>Vf%UEzsv+8l|DjJ?M!ZWoh ziDu^Wr_Xa=zI^%NIOcJGV=7~16p-Fhpyn7?ZZR0Ew}`3Bmx*3n;9w$^Xv#PwX>jC5P8msDO4dUCXOzk zFWBdZ&j=vms(=KanC6qf$6)xnvI8xBO+NDU1rQ|FweLnpN)v!Lsa<;c)`cmqjn_|a^z6C!%UhK=QoSF z3VFLqMd*^m#j4cz9W>}F>KI}yyE^7{eoHb}<}7SqXZ0p{;81EwaVxszeBmSDS>7}Rbn<~@^DO_ zDDA@eQm5zQ9qSbq0{^R>Xxny`ARN{}9WqgEgPnn3eN)x5Z61qFmoQ``%EGF%pPB^b zUO3o2=sg&Y`s_3wznTc33UD~k*;uJBXA|go`p;|@^ooyAF z88f?kCtGD!l8nYJ@VqvyOw^1XjJzt%-{HqlhA6w=D`FuOefRW_Wi+DY^Zf>7iL#uy zElxMg*_ml)pTId`bS53p8bm}06A5J{Wr}D*+y)Ap8ZyLt3|Rs`fwSKl$VrF3LH31b z%srxOL70d=VvLb7xuZTKBFsinDfepHj}u}sDr-)~jZ)a)TdJFWPzjeq*Ig0D9GS;V zA>Y<9j54Llr-vH$L}8JLLV-43TAvf~8ZyLez26?owj9Sn#N;l*qWai(rn+1=cZWw* zRh~b6sPN-KMtlG>v}xy!v=Fb_WkSQk%@PE04IBTdZHOGS!khK*M-$c^nO2v z%E?snCl@dL|p<1iykNaJw|K8{C{`lLsugkanaIYlNdF+>U%b@vm#aNvVQ*jkup7fyIi;R^78glEZ65} zT}&6VRk!OF=_XrMa+Gp$Qn^>vSeDq2*>HgUN|4jW` zuOwNPCWfuc-e%_R=MoVad8w?duCA)-BB$q~nUNkOb_>uG-}DOvNWX&s{{^9cp%+mQ zL~tmO1oWZ-$pO_O)jiXfs_N?MuDnD>oO9gW%=TVu>ET=3BWtFCtgOh$IPPY9uk~Gb zVF~9_3JK%tYU}D$i`62!OpDjp+ZI6>Vf9e72#f4(+xIrlOFfizT_p=;gPE6QG571X z&dV}K6jhp*=MT@*G|kIFbvB|>suR8Y@L_p;Z7$YamV=72DnfJ#5l;y@3pfX2A~)oF zL9iP`(?4Dhm<#iuAUJM42y#3~97LU>GUL2rreygjW5S6@SQ%oE5r>jQ{jgiI0NH^T z7HXp6?v%$E5pkxoyW!tcrwP*o4BSMJn??kwq8`vOqqtc(MUbe}f(AqFy)$E5^FV1>?ejQ&BWd+cHEq5M&_Fz8c))YAjTCjt;1h;bKj{EKkYVFYysF+V;Q zp=<`{B7(UHd@0N}3IA(J92D>#hHzpQvT)@zO__Tsg;K8TKvFra(wtW-^3_1ka^{W; z7dICvG>%FYHpGk&6;UHIcP+?%y`i?8WXdhi8YTe?7UcRQI2zG)$h9R$MlV|MoLTxJ zXo!ehR7)vlJ%WoSj}$~gGNx5x5pU2my0B32(9#DH@?I3hx9;JQ=1?DDa9IoE7@Gq4 z6gdP4DHRryZiZ`85Gc|+6RD~Wrz|tm_>+gDTz5S2obnrd(OT8a0HES3GV_WQT98%| zxw?Q2n)j$fy0+MhIU${5CsNPck9p_IFS(S4XXGMl~UCL zEN;-?!ceK@ca5)l@MBp~{PE|f82^=qa3kmFfp(8ymz(+c=kT}U z)zC<`NFh*!TSp#%d#XqfyNrb*KjZY+q=FywWX2C6Yez&yTW_36Xu!ciZaBMGK-ZVK zz$n83Pm->YjO~GViJfAqwY3H?%gHc$2?;UyLn=7{x#cR!0@-^h1($&^LFR!|+&!q{ zqd{6R0*o}Vq5&8X(x9<{!e#45gXkI5k|(r4*v^6CJ4h5KR}p7Yk%3!N@4XZq9ReA+ zNi_D;5n+K36r-*1nA&p*V?l34J~YWHF!GqC5T#}ggahGG1lJipmO_<+ma069nQ+wJ zW>!4$W91dRUYvS;d>TC3?A%l4m3D8)^l^5iBgir~I6VSPY`qJMmg4TB(zaG=T1l)M zvzX=4)*Ipoa+i~_<3^SO-WtuAnX~&-0iL4O`VF8SkHzWthFQ#w@Y@b(<8^Jn3-op&%yowS#c}sDDg}X~t zXarGYAj|S+rA}7MC9gMcM5qvzpU%!)$2D!XtOZ3w(lysEX!Qy z%gb}`ZJMV#%@JX}i%Qk0Z96-eg}F&7-Dy8xWunvL=hNN8bUL}}zBQ3rOSpHH(%LRg zbFKTf|Isgh@n8Hu{`LR#|NWom(~+dCdn1mb)xBf9;)u#jEIk6~ud1+0Fu}x0OhM8; z3agfUwuFdPoyoe7o6*BrCzvn9E!J(Ph*I0SU5Vnb96tZzi+NfgM+pxzFEx7al|za5 zeY4h!Dv`SgtF+dym+N-j%QRn}&t^8wlVZ~*3MV~Esp=l<-ub#oty5vv!=+u9c^0ip znW`v-ck53tFCucfJG%QkPlr?4n}s`RG~3PnbUYGO_ZAVYwOUK96FEEetoR`2?~e5)ThI7WsJkjW{?+lo0Md-g~^Q%%g_vc7}jopd2I^#u5($ zFh;?{mcZlm$0(C0_HB}8=2|LIxP_MDA5kO%OAgf#QVwq#Ze~)IZt0;^C1wcdb2`Su znFub8ROJk2NcewfY|>UMB0-VZ1>FGG5!WaTj~FI~n^FQ_0yA|B78<%C=xjJB(8+)c z4yY^6UM+C7`na<|f`<^g8iMd=3x;4oBz+n=sdIMGU^)CavWz53e8?CQ5}t**!y_UR zG)=fPISrIZxQCe$D>K(xaDB6KkHv;oHHw zlFL$tNK(q!Bha|);ba+BGY06}r8Fj8$s5XVg%as-q#=ADAB9t@8sXOAVHL~so_q`^ zoH^wgHOb6`CRrY~auymHjN=!7+=a3D`G~gf9kK|9Tq?%M8n-z$EJmithkR&Sgd!g3M)`#5@m7m z82xhOPRWHKOu+#P4iah@g@qydA`(bb5Yit*ya+cs3q1Q-DaSmL8DI)zR)(7}@O33u zAQF*BTe8r-T@eKwP{1HVJ0E_i3lreMWP{B=uu9S4& zBV0VgEW(wQZebnZbH=O9EC^nJd;4@hvrZ@zWPVvq1Z zXq7MtPg1e@s|?sPP$bz>2y&P<2n(M4X?>p^5Xsun4%h0C^G zHfwXKQ!PYNrlPFpm+Q3D^}4EvXbGa_xU^j;WZgG+I?T052{*GYB+yiB+gea)#MJjj zihyl3^Ld`NXgAxo4RUic-?nzOt4JwRVd80;CY@UAwr`j71yhT+4*x6Qv>@O1eL0+* zs5_k=Up-wfY~kjiLg}>oIBv#B!~ri%M6Gvb21$tpymu4fQVJ14PiWyte6F?PkaaW2 z{8V(D>THhq1{_&2mI3tv=1~A|##xwTIR0SF0wH=%(!}uHI^ygh-HCCfb2=W4 z(1|k%MTp{7F0}yv0!M+HI&nu)hXedZ~H^71rL_x4bL%u&OMso8#5NG@v5u^wtX?q$&PXb~@5s=Lc1p>K;WI=Cw zH$4JV?`?EQ6fmfi0yLC`Tkq^d=pEza{E5fR%`yTb5Dpg(r$J6EoT$!dV$Nv~%6lde zH8K`vk-j$;auSaaBGlv&a>&y<;xckwh^U*FT98#0C=&>xG=&94VA;qt%+bITV@t)& zf{tPp&acw5pbA%Q21-(>3hp=&-3+j(Qvj<1gPrc)97%yKqW;nY)jL{U4 zF$>2y4d})i4YCW;mmGm92C0zl>wFy|ZQ$@g=Cqu6^O&WuDtI;oLyR3vo~ICgw|uIh z)HV_EA(RTdM{wdYg>!OoIFcmoHx6zn-|XC2hEbE4IeJ(((bOZE^`NxB#`rKWj$+_f z6>}eRD1HWyL?~E=;mqk~EaaZKks#ZLAvp^cFzephY;pd0PvixEo=^cHRxpoB9%Wu? zLDXhC$01}QVpXPmn)7xEZyion4-duziTHzCw!IK!1Q^WNhanM~er+1QsGsRClOa^ACpICi81^m1JUb7bY`n*6LJ& zuTv1(HgdZc4iDq}K$9~XIQng?k&m8N3|^7pGi6wdJc3Xxhi}>x@fYLyxbYqljwS>= zhyqXWU>}%y-*y#Y^cj2HJXndxkND0pf)f!)AiUN*Kq4N{e*VSad~-{^{o8@_4NEgc z5J(AVY>2XY5TT;AR34tH}$E?q6?epY;y z<{Q|z zuA-$*BBD~XND$d>rIdL&Y}-l^htuL7wU!|1-Xdb2rg@&b?dRv0TIWwcy(6Z5Tep4p z>nah#EX2#ac=TmC^tRt<$F1)$(pfhSIvkei;qH39xYM`az4PG5Pu@N}zG~g~t(D`E zSj#lchsB7QdA**QC8F2LE~V7^#TRe?-Cz9Gy6wlqyj|M!^*O>_IYfxM64xNItifv% z*y^(~PB<)rN1wD1Kt6^rFQVY3>&{B07C>9qbv+ypeYf*@J)Mr1>v}vbpMCMUsDPoH z>pZ^g(4_sfwS8UpZMz=kSNqnsbt_7x)~Llo*WN@+t!2BePtVV`xBI)(VVY_!Wu6-a zYt>r1!Oh{5x|XHZc~Vs+GE-)^-g=WphhEAg&cAeCO`C0sn>d_ET`DqQPaOWoS; z=38%WUbM{fBvs74E=8D|rNu;;QEMu~L2^7Um+N(1uj)lu%=+ng$H5WatqaMvt=IEK zrR;qVl4$$G>wBFKrOZZCxW51JM0HY>%fiqZlo1GkCu!Vyp#Fq2?l2g-o!(to(Ds#Y zA}UQJvq}K_E2g$|H~P4@1507!@IwoKry-UNi z!Hz+D^efPimU8Yax3!I6V-?|Wh7An;&>|dRxVU^wx~bk_7S6hEW`-StJcI^coK+V1 zXbA5yK3MQ3);b}99b$)43P)bk zNT>mjD0=bQ^LqPUj|lWB!O1 zjBg5~lMtET9t5NDS2Qrt+iDj81;A~SdpKL%cFWr`gxR|x5y9XQ^-Lv( zcLJZ=5C6j)5Nw)tW)bFqYTrYISV;UvzX8Y(bY~Gsga-wxsE7!4@=PQ}1~p7Pc%*O) zkWmQUaHKlABeT}c0@}EwJ^`oTt#gc(h=u^z)7`9lP}EX9!dgeXeGy_|PG153z{#zk zcMM9jFgg$)xl;&>_f$25(N$LS_If?_2D zKqG>LA*d)td2n74gp^N@Fc173JYqB%;F+?h0$@g_zGq4%PSSv+B!YVHSW~G3Ckwc{ z5FQc0{v(t)oQ$)zQAM38IBwns*nfGfyE(6`u&4-`IkGt1-6J#{1gfxjfExh`>s&L! zf@AmrM)f!5XjRQ>H4or*%G}L>5eW+^kRi;?H14A`XLAO~;Aht?Tyg+i(Bw@BZ$y&)$Ci*{Ab-Aer{P$wH#DvmTE3Wjd&+XkE7r z=^)+ux?iJ5t@=;@=^y|3|L-qa_;p)_x1DWOk? z*@k$5(}|@xeME%4li57it+gV`h~x^Ir`ooiDdu@$rgdAt`0*ECe)W}>V!ijR^|?<~ zQ*OjjRLwaWl}SaH^W}Uz99X06yOUdZx4s;gMr8XwFH_U2P9wbprAuGg1{xW9jpBHOxe`^FUK{aIDYR7=pBCLNY|eGEZ}zCn8#}yJ~5BGqZV_nGe=2 zyx2U$54s$dZq&ouwqN>_&rOzlsnc@0Te}CbsssyX<}jHXirf)hE7755W z2?V#*TKpqq!g+;KzPUsho){5~f)R%ePB4V^)>(vvQJy0u=VR;xo(k=}r;BJ1p?#&) zf}=htV1i6$QkZDO6a?a?5|UtI$K>jHykoQAbRJXhC=|?7$h>pvCKz)V1tCp{BnJLN zP#~B&*k&o z4$>mcf`K0yGWSp|9v#6Lt#!>PZFLHA@(7gY1ci6Uv1T5<-}=HvS_^Y21r~OQg*Z4U zu&@Y=dw@>C^%oH(&1NjjMmDrTaD)dlTPjZk?K)ay-*)El`J0Jq1hsA|3R|Fg+QT3a z&&JbIK#AC`mUQs;fp99gY+0b6#h})EN3Sz(Z|jCEW+FzKIVKQQ!R&w}K(mgUZ{+Ok54QY>PBS# z5AehUVwoZW)|VX3Q~Qv#U)^R#g z4v3+J!^zz8BXbfaE^6H<5X(O1YL5b(&dr;*q6L&_Y9SajL3V!xE&xXh2ITdO@J-Hd zE(nUQlA04o0wz&!oomge68B*!cOnul_@;AHBMf9o-BN^)(@<4=x89qIkgy7)xN#I6 z^X;<1PozUi1gw{TK-YrF;}N3I^zoVOW+L(tw^otl-}6z{OfA5+0PDSnIeIri zSYSZPtBOkgN_bNwPk+u9Ot#*)ZbYP2IU|e+D2S6s_C7+KDWv2ct?kP^<8vC%(^z*X zKS3}CIFZl}gRK>?ONyt?txv-J=}ts zR24ubZa9oA_;$J(IN6exsvycBXH}i1sW!SMkXJ4L9%k$IAZr1}%bFsTJm*YK8HAH&^ zyVp_!K~xl4t5(_8?T>!>%Rl*(KmE`D@BjL8-L*)qrIM5N$HV-3neUcbO8pQ1*}we7 zKmOR0t2Z`5GTi=D1ohGf$>?+o+?Y-@Nn&*DK zL{K*uEk^sgUJLVbdQj$YzwRqjP}uqTS=a6U)h8w2}m?X4dU%f4O_ z592rdb+zWWjd;cTiXou;JU8sx)#+^E5v_Vl{_r^;c#@aW(_gj*XuPRwtW}T zS|{_^x82DP$Ky23Ti@Z*IUJVoj4emeo10D3RLZok+rz5|cVAoYtsNg9m;2XV4|2FS zov2i@O+|;fY1FJD&z9(h-6p_BB+v>X2muN@%VEoG@Rl$>rc5G-JscLuXaLt79;K+1 zV%DuA8w%d45exu_hzMb57{IpXc+1(*k#$iT5m*U02ZodaVR)w0g=Z##rZOD|Df9Tv zd+S=#shLXU8;PH{3{xbxjziep6-gXS5rGR$L}Ze@l2s5b0EwWvMUZO2CFSl#n3&AG zs7jr%Az*qLGZ`j$+%zfNtOc`L}LdA-w0|ua_xlq2ev&T^y9J$vGs05RHsUAP|@etCz26%BkczWv<7ie z;_Ma1W{3zM$cqY-Qw+-=ce5C#0{l6Vut~Wn<}*eNvA(){II-q!E+PQDJbMEt&!Ng$ zM5_pqr}kK}C&);M0;iGVas&$6+`P9Ajsk;V5Dl4Jm*iElNE-#l$z!PTp%TaQ>iV}@u*LON%n)6o85 zrl*f1HNNifNavy(WWC^s$la`yTVM$6t>9b-?>#7PKEqVv3?v&cag2X2 zP<&730e|Nf5;Xo~jDZ0kyGw?=hDbJP;om`n?Hp4B%gFi!c*!Hf9rL`8DT0!-2?YQD zfFHo=!|=o8IR>;o^afQ2PzoA<0AVKbWW}+@k0X>BFm$&a3NAdNk!fi;@k8rny{FBZ zB|}*#`8gIYf<3uKxYmL}P)P`JTZ8uGU?*2*QO59tO$GhLS_H|Mn9_>7YY}2n);v-Z zt;u;vQk=wrEKXupj)=YOMGG!_Ri>cs)VoKx79ErmThA`NoC@7bQK00tR_g}ZjvxY~ zL7Bp0r2^9iGeOda#|n>~nU$gzCgUl`DF&7n?;DM$BnhzeU0{z(4pB!p2MtOosaCIFH6pD3MRT4!jI5XegB^%qhlM$u?GxmgblE#WW_`(R) z--PT~Rbm=id=98op$CO|S5g)jjx2CEybVe`%a4W9k4;HlGoj#Gz(L_S$VZ6OvtT^2 zv~JdVPoNG>I>I@)L{6eI8YhxrC>m)*&XftFP%UnqMVKVFAkGXZvqS=l798L6e4r8> zuIl0BY)riOW^TgLnzi1FlrmLjE@^t}#JumjTStNAzVF^ml@G^b+uF9Ts^xH;r=^lP zu9&_X?x6tSKp($wTesD{73Nx}yW`0twr#D`(pqCtX6fBtz5e8SJ^$6O{>sDu*+2Ui zLhsbq_&@v?|G^)=`|aQS@BicfxL;rX-CzFozxXHtqZSIz;^ z-L>EufUZxRqPIp$>=C6t-aTmW=U;yL<1fGb-S2+)t6%-f!_A#NglV2CQ=AS<+pb^# z{qM?L9_~+t8ETy6aO}NzH+(_N-1g?)OA%|mmiq1Qe^1^HrS97%W~Ee`>vpA5^m;uX zmZ@kx-7Ta<-cH98m$Gl?-fg{JPRElL`Rw(Zwy)d1x$maBZtFCayZeXfx?T76Rr z)FQ{j(QMNq`}I;pmO4ApzP19X1F@6s`#w*HXx6=>W3ugST55PS+uf02P!99aBVNuQ z9v|Lpec$)q_DxlBS|PmLdvp(S5*6#V?Tvoo)Cs1=R>fx|uBGqahIN>whGC6ugbEKOK z+qE*xTA(8+g21@CyDGaSLB??;+Go^CgUA`A-cRy_=P) z;nBM>Tx$7q4I!(C7X2Z+JM0J9p+?NjdNR`ZTLH6{W^{dyP*68negQ+;9ucZ#z^5t7 z0#+pf7ikC=Bl8&33=u+0IRzv+H)5u|d06qlNM&-m{nWknkz7e0t}4A-4qFt&BUMQS z({1lvFx@i;f$0y{j|mj+J}9AFuzGmq0yPr{^}VU$YeK&@jcHZP+}(v4tca>mL@L3U zU0_5Jz za@Ljbh|vX(JXuVZ07C)JduE$5@yB}U2srrR(r|=y1K1~m2rYFmK6O^m+6te*m8>>kQxka$d_3~=8%P5@C! zyXJ`D3lDNCW91m1&-h3=x1y4|M*N}OZ?Z@;@4eAbTUwYxlgDGFAOd5HLm!L}K<98r zF6j^}iiao6>{gU1sO>G|Y~hVBGBkYoy}O%?E&?d(02?{ql!cieOqx#yGaAK-QX_)` z#q8?C_13$o-Xx(Ia0|>s{1H?hk4lhrFGbU%d67KcDqk~5rT*)X9=?cdyP&PmX|6B+yY1b_V1W zi86(YL@62hKXQW!79u=Znoa;*{&IVrATMltC(3E2XiEOwV&r@?{@EZ0hoCAHoeevV$(y%34i+8+duu&|INSq%fJ3WFTR#v|NghH?#rb5qd)qizyF)x{=@U1|JVQKe|UHK zfBk>{^4~>>yCe1!0|gVzdoDN}*r0A8i{AEM|LU)o`S||3@BUB!hyUZU%>U_s@}Hc~ z=V_`Bhr4yVzPf*)pwB*gI|X}T6z<-)^}6@HlnK+@WxY;Q zy*u4$L1{EC%W^)S*X#9oII-}&EJ1D^oLF4e%RDdU{&fDptXMebY1UGDZ`a<7YNLJI z_U&?ovfQ2K<6M^m3$?c2KTaEIXDX_t%rsg&-GlYk97#pAo8d*`K)WqRkc=Qh9Es4* znVX7+2DYt0fB|t(0`{47K|}eS{VkEZw%!mPpwD^BpjU{9IE$jd^k>$5{!c83jfUi4 zT=2|{&b~||DXKx5AzdFqVkl9FWn?)7%x3aYNHa_hrBqy8)~(b+5pG840LI}g!a>UF zsAV)`LNvA)_`!&Y8No+@`DNI^d<6dFS_0mL1woyTWF9~;863p7X@K+VbLdq7h>lT% zMN34(XI@Js4n&Hns)x7MYbi4FWC?A!auaY-(P8_RG!3(`xna#1WO&J|B&f9}BQP2x z3+}t1U?K~L?)+9yMl_&0&goQ`IiIq`ueGR0=$K%T%nky|$45a-PAT%ibPJOn7hM0` zTK8UrziV|a##88fj(+r6O6q#U+%RkIvi~)Eod6P1w z(cCHZw1P?vVh$&$^_US_7~KqW2@RS^RZCimbG3^Iw(6~jm2;bZi7Z&8Y>W{U{7^t8Px7%gftbmL5X;kS zmOCD#AGaAYg$^D-0Asj^Z_`kY!4av6r<~I~Lzxl|W=0hqT$~~CQGXa1d%AlVR&?`h zJ;y40dva*-nPdiyF%bbc4i5=+Hx_}`y<7eZVGSLs6OMqB5N2)y)<7QvNgV_)hd4r? zIi(r+esjFSjt7t>4?*SxN;wnspoGB34Z;TFVDBAILw;2`G{YaGq5|`YGP5XBbVN9M z+{)*J2rZMs>@bvI)=SKQ-0b6b;lV8M3E^ai+@A-%KF8=jR_WBAAHa9(KDmz}=2fVRjDlU@+M@&X`BoF!qMwk{RkJ zoEPDkB-AO)`Y6cs3{pdkOMbk>#LeADNS4z_s)Q1iir$)L607G8=cWWtp^&hLA#gV+ zs-RTC&ApqAONef5(|9UW29_CQ-QZkArHe4503jS)q-W3@2*+_hamGD?x5!AW7-tO_ zp-8$g>QtCzSr!WS=+iVs*md6z)56T#w&OJGX4}3;csFm|RJHd#D3;@bIjhvt_l868 zba%HL78Q<;Lt8)y)JcwqLlF12NvZCA-*yt;+geLj7(uhvoB#S(zqY;q;%EQpc$%I+ zJP{X_YRyjfcT&pve0})*Q=O&{m#5|Qz{I`nMA7#hH1+9pDyr+o(^ReZd7i%d>c{`v zKlzjY;$Qz)q2-(N<-hs2e|DVbum7|E^S6KZ{np#l%S($`omMkuhVKOm7PCMqYDk!y zMRd+v&Aa#Byte0e-~EsO!@oHl@18zOw+4}`#=1HufF{1%TK@j?9(58 zR+ottt%6bOwC!!uvaMHDoyydsnFp(U`0!yWWtwYw^*9$@U*5aLq@txL#eRL6DwR@1 zq|Akt5Az|I&D{5WS_*TpHz8}iZGG=;tySKzZi5MfQ1m$QnPb?a8=I@L*}n0qPgW+cVK@9!U)hX=K`i&hc@fplg; z9CcAOH)v2$;f$k_1^`ESv;>okbRg#8>llC<4%K|K9g*{q(Vt0&-sb#3)&j9*j*2@8 zF$<{>1~twYJ!Zyi zTF!)tZ_$jAN|ujp;emfU3YAB#m0K{M|E7sb=WL?E~dhH^8*gH6mV*}KQL z-#XVE40%&;jgviyl=G?JarKCXX-bF+nIS1b+;RW7vh;=mB{U;-t&@iZk^tRrQWHGD zkrR+(&;&B;X69Z*iRlK7`>l^66SQ)U3U_a*Dj_BbaStIO%dr&ENjJ&NczeN$Mf6DZ zEnZ&9<%iI8gdqgy4>U;ON+>1|$;AfmY-*waB><1d6Q0xx4e^g#_>Iu$Ol$%7IgYpx zsPmnO^oSA8?MS1XXkp5w{iwNu4j)b*MR6&Aa zM?wgV7PLfonv`J6k<4@k8C!}L zoyEdM3X?0VQ8ul4q;Mp!su~*jk%Ur-xUsw(WXc<~OhJ zfBfUmj}NC(n7o&Hp5{YENNSO7ZM8B5_1*S;JDpDZrfuCKtj^QJo7d0Zf2XAu;`8$p zMHjL|t);TrZXR3jQ>pVb?b{CEBD`ZWo~ttT-rdc1r{Dj>@3wV49G1uX&$b4gbouVP zZ#y;beqAqhs;71+s&C$W^78U>J#W49G);%YY*SS&o%_D;q*SMwn5M%dTrTIUiju}O z&F*$O97u3OlMwH%l_KV{Z*AW;(eiwGMuJvE^p-K5+qSmz<@oT5%k<&H_j-KgC72xZ zyRcYqwa%Uq@2OrTV&#H?$!^FrC1Qm6uYf2+b&e?Ca(IY|sgx2P%0lqPLY;g|9C9Pd zxXt1S2HkFA-g$w+VxTKJ~KNc#o zaBxJmaD;cDB<7JbqJczmN#35BMr=REl|%runuWlD@R-lSp`^y;J**~3aX{m5cF_>o zXhMLX6i*0;sfv(5>nvlvGIvgAg1Q@6ogD8Xfc+~ACUsy^=Eg!QN*_6-)}l7 z{CO=qn9STW*$lo|%wKq>PzX?NJ((H$1ZTpnODD2nEq{DW)r=Ka%*+ru?}1t_294%n zV+h%y5*bZ2P~N+{1(~R`i;QM#a___6EE#)=~_VO9bC~ zR?UEYl!CA`XI2v2>qaQcZ^NM zz-gdziEgGJ1Nb>8$U|5xa-a-zbhYC9hI2q}!Hd`#@<`2F3XoEsXFa3q1NI@ z1VQsUfSJGzi#18m_(O;p#)Cn?lbDGaEqD1jl830|Dj;LVWQ2xs?>#9RE$$A_lSRZ( zuZalMy|>$o1*%OkGl`GJNNl^5Ie;XEg_I(+Dp6qJDWx2=PZc3SDZhp zx@{|hfr#P;t*J_#st7dP`@Zi?G)>hbiq;|$!CnGu5_I$C4rl4QZA`RVU*@HjV!l7U z|Daku{@1_y%`blTi)kv?=jZjh6;UnA)AP%5y6by?etxOli|M4-H*Y@gZM(m} zBhNM+6&B&%_RD3Z2rWe@RCwDiZF{lxNP zX=>i3NOwabxVhIl0eul737)vP^|=;nW>R+R-D3OwH}f>bkDw{QUTMH_v6; zuIu$&4|0e_xL9koEG*o63!-T%>uPRZNS8W2zdX%ly1%=-UN01JzFxMr6BD!F-yhq0 z=7f#`=&)|nOoa?Yi7Mv(Pdtix~@&D&ULodgyp(kKRiF}tshRu zx$^z}qno||@ZKr5ZCkf>UM8MY%XD{Nub0cZuFP}m2IWbuN3805+fU0eg0u)Euuo4< z%VEByoVnW;4YR(8Y}*zQL|faNu&DCkF!$cvqqlvYmU%7_(Y9@QIJK~%V!dgZiijCO z!;CB5%(N61@u6n)90Dj)cNu|Zu+#-(Ysj>}-DQF*OBYIuWZDA|~q* zx315GX7Zl}_L07~Kyhy#fJ5=;5l|Cx&s|{*Dj7iU5p0BiB9)+=Igj{dgAm|S3dR=< zd@Rt$z&|E(i^->qsAgdmaWiK`nowf2dGrpgjxgOKbTZ`@hc9M9A#$*hI4Xg1U^kFF z1MP;LL3on`akrtZLO27~Fesq|jTxYsdzR)Vlc}1FeXZQxpyQ>HbPl#K;$w1mE`A9> zQ7LLSU9~xvkHN2Q*<6Vm26u4^oS29ibr88FW@nk>fg(7(pqO=MN>kZ@ZzBO9CJ*V| zR7(V5qmVRBN@P+?lJ=6)1-^-YP_v5b1Ah!32vdNICunnR3>d)!pAun!auD0(?>73j z0Eba##RT9#Olu%^n8LdwYCS<^VIo47ou@Y-+{}k(0CqTdUOAJ9QfKQy8px$6kca@O z#Z8ECee?inRoGFiTbNVPuwbUfCd_V~D3b<-8GKIbotUc@67mSQUTd-5T*emd;o*JX zBgo8TVlyA+ct)rwbiB;$yWND`c(6OtONRr5h(z+#hto4z(Hb!lzeOqv|$a3WNeI?@tr`DjIm(v<}`?CR3#P>e_ z8kPW}L26n@u|xDeBo9Qbw_H;&!G}kL^*v86IBD^ko5M?KBW}30rUPohnHfREU~a9H zYQ2XFgmx-QLff`YbsGL69-m1c3mslvHxG%yZYhpM%6&-~a}nMyVv>l0eUb2>!;u~V z)p$yS@+R0}djN(IZCqVh1hYx1EHt zN}k5cjQ!ipq$p8VyJ0(I=iYnKf+q0xp{a(9q_e)>2?ZyEX1mK6EbWL zPYeqt)a)z>w8V)b>bS$D8pb0rng=Kk#?67E5dnLP>}!AR z;8+fadu8grZ)>Y{7S?y)ee34`?4SSh-uu(Lci!6JbW-KgqZYk9UEaU{?(^kK+xDgO zQ16;x0mD?(=bOZ3~+| z|Mb--Zy%4R<&#feyZXMbkEhp|HMVt~=Hq(3F7rYZ=kr-YmU$K?vt3wfDa=u&M!TLr zT-kkDrr6K6+wpL4@7unsa4A~2SZ|$T<+b(B#19XT#NKHe_H?6I}Ace7^Rt=8hPN5tWDthJh1 zL@dkHdN;G>u%u-uOLbI>cK5wmn3t+5x~^-TYAwax_I;nGT5DbAWnVW|ah8z69(&)G z`B;Rv*6TDCm5dqbCWTcL!v|_cfVUB`6ovB8k;M*>RYA8}Ca*`Du9Vrrun*kW16+%$ z!czTK{e#JS@a{+=&(S<%BB2|Oj0ToudU<4e0#zr?)d-L)kAQsY;{eP10(b~Bf{c`y zrAPL3-o)hsA7}W`_9XgXLr)B`HE#M_f-qii9xMuC$KBn`fjguEGK-?%i;7z6P^Kf$ zIZG|Wg95dcnbA=+GYP=RWk0k#A_s8bC)E%xEN$;v6cxKF$}zm0%)J5rkfP#f`?L&@ z5LOz-M_l}AtjuT%caJol5^GTr4j1@9@Q+x}aKF400p$wloitBJgk%ylr^bL2Hj3nV zIBq?x>4Js*L^x1h2hhq>1t9DZ*6h}EmBq0Mj7FAJ0s@POM5zl#$RRfcYRR`m7Y3@;fYRVL!S?ejEs>*3^{bTl4dm@M&NU@J3oVj6#+v?xdt^goEg7#klca!>Yi zps;aUq7WZs-KD5X<~U(cVU=()bD48}h1{k0P6|-0!{-tR5;QmH$27IVy|-GX)|#q# zcMC5?nUR&Es@z(`$Fl7^J}?S0#z-yHySvGVR?lybIfE4=IK=u#>n4xBR1hyQ%(AF@ z!d07~Z+cC!k{MvZq6(V<54{78ZQ1c=CYpirBryD%OiUVf!NHM7*2#GUmMhBz#@(Zc z=8%N0D&(<`mmN=g!juu&li?1Ti7B>t3fOor#P^@i69p(*YT<%oxi3ysJbkY&-r z!%L~0()S?pBI;&%t{kCEW)d=N1A*@vwH3MFP=s5IyeW&0T^+y&9{7l0$4AdU*f6cc zC?ljGg!e-~dRwhx&|B6GkA4O$L`2*gmvYGQ@PcOrnV}TD!5cJyjeOw^dYg1sNs_HN zH`6!^dXa)*ZX?W^(MK%lh)6>*p)-prx&tZbVT;aF*#&k_HC}yGB4JR ziMsW5Tc=t|QLR#?xJS1(O||NjVpfX1H%E_PQTMoSPv%lKE0f znY!&HytS>?dOF>8YukE-pt;mKFUz)V*Yl;+^7!i2dR^ar^G#8y^HfW{yqr&``}f~{ zx1O)}504QdJk9f>%)YN5zWsW+e_iIgM9Cud-b7WWnoAc&+ncyPzW(IZ>rXuPKmOxi zoUiA7UE^JM%KALB=($m|j0QbL@|}QE*Ko&kNI^x^-D*Lwk`V`yZd;p*s!H4UPhUTL z{^^^qe*F3I?)c`j*I$11#n$)px{4smz?o@ZuN3ZNqD(^F?7W@#*1}NO*M(x=+N^b# z(zk8fwxZLeU99aQaesF&I>2`KI@MAuH+#9BU-s)V&p{MHMb*7i(AIY$IUG(n^tkZh zaM;@V{^i5VdVcfx$(uKyU9X#av~?>aPaobL4@bAyw)J#(rbaz z-K^-+_O>ju_ty5_%nQw0CleaM3vZURO z2;2jWi35&L+;cF!!YQqY z*e{%MMx42@sZ&asK#HJ*nHjDC#=c0RMa{ZpJ_26GPZ24!b`K(tbcWn)3^}R-`eLyF z04UCYZzaf;)$wztI-i88!t>_2&;~Btp2bNX0cd$3mkBPS=z}af1Hka}*fVp*L5LO% zX$kqV2#HAVD8&Qs5s}?gNcD+26%p>;&D|`7nQLJZkHE1EJ(#&!gJ^J68mpK)c4^@&H`NAAVy=55!Bxe||Y$S^eX;a3OQ*O?h0hg3X>r9-K3E{GjqQk2yLX^4Z zlt?3u4P2@3MJjO=trW2XNX1Xc2An#;dNF$O-ncN3!x#upaUW$amUe#mVcsep6ktOC=B+OU|$fT40J1ayRzQ?e1zHIJvvJE9=6t`m2dXki0o2l_q&WgEUtY7d4Lk} zV5U+EJ}bz$D5AFxZBt@~B7n~o9Sf-?$dp(bv=~0!uvAY$0|a_PRaps!2QxJ3kfP~! z>!MogREowj3z7Hc7Hhj6k4rGS)3#lMT#(2s(yiB}QpC134ysk9V)5f{y=W03)rvpo z-uW=q*4o~-AfM_Sg@rS)`TqW)Z|k~k%KY+jUJeI0TGytsef{^pUuwO-yYuk2@2}sy zIiD}*^QE_54|6wrINcR3mzS4!zyJO6@cQ)d7{1lISVU0h_*A=fGpkc|F=wmG^pl_d zq__S3_usv~oBq*He)LwKD*gO+L} z(=xeP-&;gT)OuuRjeBk-F%;hD?jWl(@JO>;y{^E59@Gc%7;QoyR5F$o!xP2>@t>m?2@8Rs6e)*>w2J0#|Te~xnm=g3MHil8hi zW*t*P7Qjn_8lIaEbD9A-lNKXI&yo@ZLsz2+b6DK)U>vqLf9Fs$%g{Te9Wb1FI^U8O zpg?3=mTwkOVgV~pm@El4&A?AhL>MnD9Y^>+1ds$#`;cy*eR<@S|t}TN*L*xF#{mBQz9bulg7Z@#j-kqEO+;7Dyj=bVLMlCc;zV zN|H%*;R7%S6li&Rn^{XNHex8FkjKqw5hkPwMbEN~2zHCqsuE}Gt>);$=rbQIF`nVb zWDXIL-nufu112ICuB^Cyz>1_rzIO^zv_?><6gP9XI#pq5kgz9m9*hRGoS>}?Fa?^K zqB^zb<9zk;2Tf#>CCK_ujf06zQo-bZ+}jwMInmT|`TGIP6Bcx5mPK@5F7IDt2l! zV`(C~Z%tLTh+7ZXb~)TbSez)_palZd9zot4jSWdy0#y!_geP^6$Q-ALtU)yEM3gC^ zc|s*iD;dFe1oEBCN0)lBFKgpTeVnp z_h2``z_)jRBiFBlJ4X0aQHh`t9uK_AOUV=$wn*?M{{yUvCfk9%v>660F>8*k46}rUcKAx_aD;Oloh&rkV8AMpzRaK@J!+|Lxm)gg10U_-5u|mx ze|&Q}Kd*axeD&J)ZCU2GZ{BR%x?V4$92}SPB_s}aN71qzPSZ4RZTsNP&d44>cY}?!cvtF# z>Rh+Ih_0i`^Z z1k!ID=81L;*?mf!5EEpX;xf*0^l{wqT^5qaZYic)nm|5 zDh>^O4(los9=-JuD9Os==0(NB5a~mjQF6rGl2r&{4)6ewunLC@BSw=%Aqx(s;BXSw zfy{%7$tyVZ8xfJ;1(l*{AL9&tl3UUSK+l9i`R0hAq5q*s_XrI|$GBo#gqg&`I1=(p zEler}10k-E{Sd0zi`H6FdQUN;xo)Lx?RMqRfsSW`zOKY)4B<>go&_)_uXo( z%xrF>*?=UUUc`skJk8cE$eDy9_Liniym2&w1VlwhHZx&1@4}KSu_IikV=}~Cm72ru ztW8UXolKRnL<`aG6z-*1QQ<)TVYW;*>q%|728Hb%>hzRjiSdXHjc`j@%h2?rzd~}> zAK0s5^^K7zbpx_<=qH0$B;gQucyke|X4WAVh*4JUt(o`MI!g+Tw|#593(B^wVTr<` z+pOUqFQs5A1MD^2Uo0x%vP!8$L9D%5M3hF zXdgg}nFSDakI1@j5)u`+9JYW1;kSz$rE4+7791XkAGY4>R5>^Z23#^j5Cx=44hsP$ z8ydrSx2{EnQ3Qhk3J29ILT=&aA)=)~ikH{SP|*#MCE6A}btCD%jXbr6-wW>>E*r@- zNg8(p0(XbnYeXSM1iH~xG$=$lA~@LHM%qAH*;4)yguTiK%Sn>@``&EeWbm@(U6=Qb zc_$cm0PW+*pop;UMY+^!-if%i9g(fxtklB73ga@ySydU$5w}A-QE(nn(?7tU5@7;H z#YDl%+`F^TEshEF0vzR7c8eBa@h}mM8*D#l4I0GFAZUrm#;b=%OGHFi;U|MO%-tae zN(Ts2ma62l%lOb}AV{ek;ExjnkFo``j#~|DqX_KwNvUT*(D=D9+!QUn8!=6__6}E8 z;4a*I3l3!|wM=-s;y z9C3GAPNzEw{oCLCy19Mz)lYRkY?qhEy9X55tm`_@g~G_~dfjT7$jJ81THCLC>n+%& z^5ffw$2YI~Zohr_9V`FMZ~pdo|M176+R$Vi>9v=P||Kgu~@%b0E$a2+Yw|&>5%VDN)51XcPoKF{~66~8ifB#+I`f-_+wD)!2&&Oq14xhKS?roo!qFU#< zD%Y*=)-BnFAMYQR`6SHNJKRiouxH5bnWLGLtOUb;>GL`zX7 z?@b~~LAI}xN2wL70uBWTCas%7P@jrr!%5h?Q)Zq3FHO`R>iM*40tKW*-!L38w05H_ zK$yY$2HhC@)OqCemf}oC(k;#u{4&_!@cRtBfEYFRENE9C3X#GbXuP*fhLNxch<+r~ zWzcikdml~*f-xyG`0-<6VbIK+V4|Ca6dI4X!QKJ4fO>ehuA=Z|;S<9q0#OLYZs2yJ zoD-@$RyFca7G@gyNw^47#e`ddGd?lXHAn&wxn*<@jyDlzC&!IU%)u#_Mo%aUd+!3y zA-^~xx^)Er3=^|ZEgBjW?l!is{AHRPtWe_E3YQ%Ht}!W+Cd7EYiEF&aoI zAV432qJTXenihpgj|r5BwAKo!7_(A}kAcVCA?gcvA^8}%gdJbBqu1a=n`MTB{lS z0GaAjN`*?IBYzKaBMK6Q9^bq%BAA%rCUOdEhHaWhJ@8H6zqdva)TpQi{2twgw?4IF z`DI2#a74ohP;f+G=I12Nu19$`5DV#W zT(L4anVBSK4ZGY&G6d?d++lK)3z(dQ1*Q%tg0vJjqi!G~wrx8ek3_t#Yd1vZApwwx zY1{WwwAQKbt+&>@3wtPgx3)K7+P19}5mpKoR%AL+>K(Z?JD<;5ODz)%%Ul7JB8*Q| zp`RhioX}dQTB^I1I`!5>c;o4U-#|O-ODsHFW1dF@!L0VmuV)~ z-~8t9>b(5?7r!vKwytj;-fU}|iWBu&t9R>dx9|oDY+iw8zJ-`-nUBW^QYrg&U+(Kq ze))V z3kBWZJ(%@yG3)Doo$7Qv9@h0*sfft7?#wJjg8g_pEK5C~Uzkd7jakp-|yS{ z@^UUkN}XO_&eL2y=;iWqcYIj4YXpmwd09%>RA*w|w@wk$GF#u7=rGNf^94bC+jZTx zbvZ86G@BW#v~9OAcI&NAOp|I4e!9F&b*^=G_o6~!VXj)a6!P$GFH?1`nkRD( zRHN*Th#@>whwx8GD4bXYSBt3fm?{{yv&g9r83=jqk}Fyf-H9bx!EngKC;~5O!60ZR zL$OI2q=}_HpalYQiZdAp_|R97M`-3AqK8FNzQ_UV6F4Yl?$A#rSde8Plm|O!v_Li@ z5q>loLBj<#;6EHmjmm885U}}cjF2R&cx{IP!SXjx3-AS0; zT!f(u4WXnakgh9;)$h#wgZ3^Dd=QF(7TF~UmKD(~FC~JA)jZ$~4pfB>U@l_#9E2!C zDq&mf%-}hpQIo zY|I_v0!r5@3Z8<9yLqWfm@?q4X2H0>4Y35(~jiN+|3 zQDsfU!zq`3)+DL0_?inL0fj`h<~9{MCgFn+i7}z(qnlwb=(iGL)-ha}dtrsME;qnn z!1+@xH2R46W;=`^vkdg(#N*trbt?}0mBglHf?VxjpelSQ3RQ_T5HbYr3i~r#}e2BN)3+C9=Qr)+QMi-5vs)y zz0b038v|<`oj;7CE_dRTNi%zcsxzyd1M`E!Y^g$lWDzzu!fHn`j{m(?$nYfwwOF=JJSrYiUTX1E|iXx`^yr>%HrUz{dpwXxvk&DWoHimj+-)!v<}61|e1m&!nj0 zbuu#!267)G1;Q_JZ1rs7xE%*#a33-+v!slmcX~;{NQ7{-V zpdgu7GBJa3>H(D@WbF%Uj^#W^<*|3=vq0(azO7f8{J_iL| ziCSCt){18C;!VB(}*) z6}{B*d_CVkKD>VYcD-KfJiq$nO~g(%fB)@wd-F2sdTB3D+dulnr|0({{_?N?{ZD`P zvxnD@-+ue`>Hc)Mf8E+XA5QzWt(%u=F@jBklnV>Z^YO#ecfGf1nzsFU?`@gpUTg3E z{^`T><>mF8SL<~Xj!)nI=x_e+cfb9I-+ukwyQk-K^gb_#^JVq$A}U1I+cZ^gb~-NB zyRF;9@$`#7{DZmb+c&R&@kc+i*dHHXEyo&eeZRha`}XPla{u^d=|;@Q)3LOEIbZ6u z+&@0_>t0p&EzU1zt$JAQrc&R1|L%ObwsjZMwzciDw*7KT*0jet3R%=fXNI$FLw#4_>d^@wAkB*!TV2`wy*MmwA4?e`sy%y{}ML zJ8f;toH2{7ZOgJ0RTbUW?bYkY^UJyIMiC!Ae0aFKfAuguzkGOkc|IHtB8AMId~dBc zD|NcA*I+8sbUvRwLQ1K%T5~OhBig=CQwfi4-G%x7{=U?@U9Y|MZoaO&XgS@TEP~zF z^LckY&Ihf9H~_SN1;9SN?gbQg5Q3?mO-h|o1fLkR!OXD2p z(I1~5c;?>HFoHvVhUY}e2;U6Ewju>THSQ3{dXVr<^Bv3*Lftx~T|Tx!+-R^#-;_vL zaZ=`lq#?gy%KUMv!F7X8W*85(C^U5e{y#FgH6o=0*=9`y^4}qGQ5^opL__|eSX{M; zb;q*7oFWM8eNZ2nPqpuRDFw4rpoo_RZc5C=63Miy0)`HA7xql$j$ma3R>okTkjzp_ zfe;|0!*LrT4g&jkL~A<;1nhggo4dpO4Q*%hhEIcfLaGrK6lf!h(YM8HJ%@5++EBNg z%dz=0Gk0@F){WPxcI$~1YR2a3O_(s=?%oaA`9;f^QizGnI#5R>Pv%2Klo3P`c9W|S zl=IOdO4eq(W!5JV!AjOs-+Sx5yTLHo_YP>HwGLHJ zj=r3x>Tr%Q?Zp~i&mIxeR5{$jixw4ivj&rZ2{RWh#FFC%1r|j-&CKTL3Ur5Z+ucSQ za>oRU4Z*_DH>IKw!Vz<^Zj!8SirwMp^^E4kM?w^AW z8;28hwwa_b$17@pkvi$5qImop@4nd5E2m$L{cDojsaZ2?ZHeW zGH_zK@mxXdW|ID6W@hWKwh%E{CPotig#z~m;^G0fgJf0iHx9vFlo`qstfNeX9z@9B zGru-{Y=Z#FlhLw4JzoF}^i0mGIK`NG-y6!_@uLA@Ksv$gRAXdp!D|r+l>=3j)HBR* z22noVZOJ_BZt0`PH>jdLGQEmf7$p}_Clev^MUoB6H=hU_6f=L^SBV6%vbfg+1~5rB z(6i)=nSjuw<`@p+2sevTGMH!t`N2j8MXn4np9|#N5dnBfn6ZYbBr73|CK^U|Q*T8} zQ4M0mr`D-N`18y2a+r}~zbBjdGb3E`4CSG&2ryu2{QJTDRKA`FQ< zf*uu?f9t(XWny7-b2rr@!rR{53@bs9U(RPF1L20)_v>}rKK=Cb9=`XkT7!r}oc8H> z_x7{0z3l6{JwCkp+u!`%pa0MQ)4%#x|4OE^x9c(h6G800)oojMd-tf5OoupX^F9}0 zQIRQ#+@i4T+q%^G{rT+FC3u;awr$k+?PYE2_0vz@hRM2Ks&K9R_Vt6vS3myptH1r* z-@JeS^!?M>TAP+BlD7_JS>}407nfaX`NOZix;sp-U*G@eN1xr@P4j#@UtUyRkt#D@o!4zWoKEvnN#wj<`uDp8U$2*1i>>?1hx7Hko4X1Z;d9@~qix$%OD&7} z{`~a(`6sWIdFj?K*9*@o>t3h1l64JMkvh$%`|sX=|GaLmUw^XieOeB^_rs|KaqAw; z(=shf72%ijOI2~}91~GU5jH-|M|ZQ{RJ5(T6*@iKy*xi}TPt0s84W!)~;T5}bpI!W7iDTO_zQq6|V;Q+2IG!z!e{Xw$3K$r;;n|YcE zCw8NH(**$qCJt82Tlpe?4984>0A;JW&ww^?cOVh)S>5n6>rH%kbHODdufz~==ZS2lAokCZeDl4OUOg#}QBJK~|jdk&*K$~{4PH0$7L zIe=1IIJ2+{*zC;c4<`@f+{Boeg;Yvr$C+h07cf+O(W2UWI{icxnYq2S9!3S|lx7&B zlPEhH>xj)dGl!FwV%dj8%<8>q-l1%$-@^SF5snB*=? zp@6P)aN}kss>H;Bl<Fn-2BIEaXa z*IFZWsvz2B{3{@N*A!nIN4tfC&c2BK{bq~(E1FVAG zk%1`Y#>$vPSr}SJ@c0~{?*gA#P@s<%J+J#5<(Y#km^LZ%QtFK`*=EU5~?A)Q2Ln41?V zd*9t0SxK!m%i-I4?^w}gG>%VFJ#C-dpyD$k3Q(*(G~3j;R7Fk3bzQY;r4W{)g_&{2 znlVeBwEMOJkwtX|GdmO>>5w!GRF%uUM=b@I54h2r`t6pV5aC)XrbCg;)e3wBP9SPU*r42t2nrS|Qt)Kw7zsZs zYj6N&$iT`7CMrdTxWDYJ0nbSIeqhNWd~01+MQ&@jlY#1`xXB1}>%?5%EiVZ)*Wh7f zk|c|Z^&4Fk5s{wjK-rDTN9{UJ^@t%ri^MIkp=d5VVZG<$cq17TN^u|ZHK0%l!;hgG z2LXUy=B!DB0(`=h zO(T3QGWk1L1bhN1Cz|Tx&pR-gQOQP0JyG$%-&}v+-Oc^`Z@zo`_ES-|zV9L5ef!XyR6rjmCRq=KfZteL~hG6J->giOt0<_ zr-#RWxt!{9SYH0}*T2c$Bd0<#>!ciS?(go8%fspL>f!$B`RVRBKitoKzr1?o;6}E?0kD^QKHr2x?pUmHXc>n$Tr+Hb}yzT9HJa%^{ zwy>ox%CxT6X`a}vNC~n!PvK6~&gbXHhX-Be-cj8|duuA?!_(8NhsVcPkC)5!a=p%n zg{W`)77>pRue-H%+eDODRLiz*%i$=h``(q6g`4$KRi?UK*UPqA?<(TNbuR05UHA2P zIAUf(vbTlV?uYx+;jrj(*IbO;+YY&{N)aY49LT0??)zF*raCdZMNFmG&_imAM_PE;5QepD|R{+-yd-I1sbnG++2JL?B z)ylW@e4-#16_!NS+)0B(ISl?Mz7;GWZ&5{AP~0H^iEFJ|$a=5SWahmaL<>+fi2%++ zjd4)$N3u6GHzFY+wx9?=e!`GaLiYGj8VYCrTp-h?$6-fXw<3^I(5fM(| zYveO3JXVN^h&t6wB(GUaiMN%Q^=5%}?`D-tYfVK0e~?k!$uxX)oJ5{vn}P%d3)iV) z3jo4uSzE^Dp(0tA$Ek_4ZbF%ElkK_TqU?@rR?YDN(KqI9K^#&PO&9na7(i+YoiG+j z;Cp9r4?_zZ4}jfq=_g*?yHSuK_gqpAREiqFXbP0q2D7B1A-OvM2b78cEY*$y12spM zWxbKb9TXOh{^p=y&Je=9nllf^ut5PAw{D!yW(0t8P(~+tM0Zo@1A|A%4@v?#B3id_ z?_`KPaWf2B-3-4Qp3;;q1~F0FTTs4kkWG5NtS&0OW#mjZXl=ZcxkU+sVHyuy^jd-?5&m5Iyw`CSPrEV5w*P`)7h=>twD!Jk@_*o|6^h+AX3pdCGf`5Y~q@ z-#XYXNKTFPVy<4xPvM|8|!07i45n_dnyW-rtL6KqM;Hdyy5m8X@ z4R1JSIz)cTLW-Ja7Kw0CQdKwWt*fXiZ|jEDXy0~G32)4W7)Jhy!dbYt9VP@byKGk` zJ}gJGCc?1#wauQMU+zvPVQKr$Y`t4D`%KKdEc3Q)WUG=ZdC^kdy?a-udb~TH zUtTCeq!g`YotUDtb^rRCZ!Xs>m-_98=eM7IO0BJH->)y9fAz(iPv3^yGELC!(Yk9X zDqHVfr;`$GZ9Sdtsk`8g?QYiF-mllIyQ}I{h=X39p5{Ycmcw@GLDVnjNy)9he*JL& z>fvwy_P5r%1y|9B)8TYD_Vs!!^69Jlhx@z3ak^e#I9!Os?bpBh)$2E}TW{;OPRnw+ zo?pFrEoFMSTqr_It#dt}Uq1cp?f2f>ZtL~hyVYgVBGi1_b}H4ooleK?va0g&{`BGd zceT*rFx$4BU(T-{9^SnEAGLT$;p?)(IfV46A>xG0yRWTwf1cUz3tb% zwcdN1r^THCH=2`Ki{MT?wRSz9eQl4Qe0I6Er|bE6yb}*qUe7OwyJPEnTX*+Z=4C2# zk!Zb9kOw(&sp8h>X+g?FcPlDj`NBg>nX0180uEYHhSqriB^XN(!klnP9vaBQ374YY zEr<%jN|-#@Q zNkxGmZb~;12@Zrn+^_}}#i%io6>y>v7~|l9IN+Z`T8jt9?dGtt6^tD!(yi4}$L>SJ z-%L8>2O{j9n7EYcy@dzvduvS)mV)e{S^@^@h#cH0O3~g^#Y;}25}bAlxE>6=s3VYC z0)<(_Jq+ll47S#TO07)XdJ|zSh1hUy;A=LAG0{q?7znqvb1pRis&v#z5=9E0i9$$N zIRq&3;2%UrK6;Ky0G=WcV8h)y1+%DGD83a+OIMmKfYuCeu#YGRq*v1*qAAFSA}bSV zVIa{$VSzmZ+Y3`fFi9yt7;8r1L*NsTB)xlgpoNgniO6WrC6-c|A!@z-r+14W%+2WM z$~h_mJeyYajBA%jFpe_LHqvJT#gw!2z$oDcp=4~t&753#EIv|{s1$`T2`m;WoShtf zUXb#Oas5vN-ZLUqD>so;9+6k zyp7-(XMI%trMNWrcZ~BX3c^pSw^M+kXhyvPnliIem6?@>=Pc{WNtg*r{p^JvF<~Az zW*I>ZfJG$ruXxQ;>jDE=#Hf&jtdn8A5~jdg1g#De04@t>$?Po;ckCJXHA&~;)8jl- zV{{8JjZ`%lq4K?qG03LdorfY!I6Gg)Ss*DZCAR3C>k$~O5unDx5vrmDLN`zePB@HW zsGAt9tC?95r67ey6A#G^rAyL=8Hz8|$`3Y!28M)hRfcfPM;w!nL<@vEL9&^e1gJ$> zx}nZcdLMxsG{izYxI>(hup2r-#y3og&SCf+Vwj#)g@fAOYAM^g9S_HOo+T5|U6hN8 zl;yB2-5VBb3Wr^URXLQI>r^R%C5ouIwcR%F2+6r#ufl@g$GwZnG|fbzqOG?epXP~~ ztaa5wL~i!&x8MEzXTP|w_Ze;xfHGQ^8Wha z?*7!a=HBP29#6+2?RUTZU7hEr@84IU&p&zl-MjDKzIxax-hFtM@HdZde)RblAHMr$ zsq*^a#7bmc#6+T2JseM8fAh`ZaCmrp;9w5wZGU-rIo{osqU-hA+jdx%-do$ZWnL(@ zusENuPSLF^F@=+PmC|hQ+g3%%`%>%k%lD6;eQKna=jV6dy-GEh?}W?Yc+{dJeHf(^ zNNQ7aE2X?VJx#T?zFk)*j@Fup2+K6h<`E?1wC(Hhcq%fV*K1pMo#wl{Wxu>^YqMrW zq!gj(_opSid+*%$S_*mNDBHCy$76SIeV0;%`u*KP!P~xBGZx}tZ@scak-hD;PGLO^ zMTa%xW;Dm|-kQ6MsymfZh>2B#NKu8(IW$Mkdx0W()#5m2GgUBeE{t(1h0#~Q95i+b zPP{txO6r_Q>L+n0$yzFLLgfY{r;NKXdOa|03hT2jTQev~b z3y4xdLs079T(~ zs}>6*$)1657gh$Ai;*<-Q$vC2LBgo(N$quNwS{5VM*pr9cm#Dd=|h7}7E^jS0TYoC4L82!0mXQ(K`2gUX0DtUjcD(kf>>F#;?62unAv*- z)+D597q~|t`;JmeM6$TVsbC5R=KymoyGWsVEU-(7c6}Qt?z~!vlwfGqODs&W5(~M!pKftMQvO6d75Cg2|rI$Em}ean}0K_ibQ8E zMYet2w>H;VMeAIHr&=pZa3&*>hfPamVo|-E&#FaLN~z&qs!oSPYepWe^-tb>Qi@(K z7YcEU!sKB=kVjWhd-eL&hj;J4`|i8@yT`xz_22$q|1baftGm-5|C68p{O4c(!B2j) zp4apF9IQuTu64hzr2WIw%W^zDEVFIv)8%qJ+=cgurUjvRZrfPaR*{-i& zKdk%p<-^P6eEsB;PaYp1gXz_)S94tsr$d>h_wU}+m$9cURkIQmc+}gfv z#->H@?@rGjK8VOX&lY{#RuS3TZr!FnO;bG{58rZ*Ov=!H5^J?TbW6P+I79~ zlS`2@y?Onx9164S+eX}Lt?#~ncRrs#`Rwyjg2N8WqzZMpsi=|_@@BoQ*9flDRLBW9 zqkECCzMFX|Q&lDoA#c`FK*1Efldu+bw=m-L>P3(yS{N>hDnke(p@S!P6HWgph)Vp1 zd4i4DP=+KoOmGacda$t1->bFv^zt)T}sV}iquKM*Z{VMfNu+dTu)yzq{%UKGRW`& zBAdmoS=v}`&Pc3bRST!qoJ!4f9YKN_ttQ{rYq1nG`}zZFE+vZTXpFcKtEKr9ou_s9_NvDl5)X?M?- z=)56`C^%H0WX8psq5B=S4bk4Ml#oh+s{gzYbrS775~p^to(J4G|VCLzOnT z{2aM5YmdH}D&n$4gB);Y_Yz%ZfhdbC)GDR(RHB{2gnIgJ}2UQM+)-<#7wH6Mt za5HNoSR9|GMR@PR-mQDMZ4S{sGxctm%vrcp>DGMTA$2MLf2#hh$C7MI55(5Ax7j&9 z#FXMfFjLe8|4|hN3 znAzTI(8Jp1d6iFbZ)Eyl$86hL-iFAdtC5 z4=KD?qU1pzx!2}p%lSI*?f_5HC?7nO{9Z5?@cef&3m|u~0qtgj4M#)_An&<5Trb>A(ri~22!t;E@5jr#0{w(`QKq~_!6M)}|n{fl|OyU0A10d##hG`$k zpK3T3Ac!&9Ey@r&Gtcfu2py#%8JP=~Hi^`_k^ML;4(iP^b9j2{CA*EB%VAuk4a4suRz5edOEVj|K+3y?c%1o#jo(2rQi{zwCk z{$wQX0;+hD3iM3mGCAzW1f9EnRQ zGMe@bcz8EbhLr0o5=lt!YHCFU0=CxjHR*F#tv32Yv>CZ$AIz@BZd*zWVHwf^mJA4)gr^XP&jdinC@>3n*) zf4sUrzIyZOd|6s=!cvM%wJhiJo@1iISz62;^yw-`)N8(Se@+S!^amlFSWP# zPmlT9Y|Fw#+uEmT`u>NzH=n#|+Iw#@l?+e|hqlG(v=FmHZ09r8V-@ag`PHxf{>v}F z`o%APdH?ZlS=RY5tJ*x3tMWqo(!EVZh=mcubX`_qHq}~%W$L{XlWP#+<#H|}r4*9u z$J68S>bS0#W$9N}*K1$5-Uy^l^>Camyth_mJ{%8uczTS~xKaudrtUEaq{zB%5#a92 zy3ptf4^fJU);6hCO_$51TIV`{^2G}&;@VH=`>9lf^8I&jA0D4-ohX!1Ot*Qi;Yxsq zsQ?8NZ0DzS>xekd2NsSnOck34F@pmGalRuRm`KAIu=TBoWK4Y}CV`0Py+n-S75BjLy)g@`fLF~|O*%p)|@ zFn9L?WacC6$mPg=1PXFQ$+uf;t%zt-MqnP~Cq-n|V|rGJ2ou?X2&N%5p>TDF!I0#u zGyjT!_E#)KV8W&Oc1vV3$8=%LkQo@__}HnHWMMrlMY(~P!QTLkT@`nWS|V=AX}b^b zJ2$v72zx*ZEIrU70wY^`#<#*j-T=B;R?VetW{7Fr!b7BTek*i~J*e6vnP+^iEQLs6 z&$vGb0eQ@&ychDsZaPQc5pI>_{@7NAb}W^tz{5%#6i(^IOVk%43(azZ_wW#|06|RP zn0JmxCQYWPgq{~Lx5UFze3oMF-C#3v1tY^qNNAwW7K|JLZk`br_>BH%|7~8*;2hvn z)@R<6ljoh zmnWc>kX(wO0FcN5N4wgy;v>y7`4p$12y*|SRKyK14~fnF53)xxEHZb40Nlg15OWyV zzbshBg%3+^8kU~@Jb^>!l1GZ?&zT%u){jPjxpUGY`B0{?zH6YH2O{t8Ovvw^hg|IG z?PiuaV|lzIaXJ~sJq$y|LCBE}by-^N=8oYZB3*R^5~Lt0eCK~KW4)P&kHS48CHFlX zayieoXb5*<1dL2Fgt6mC#GXinqvLHPAA`qkIS)bt4+qcBl}S>c$=vC=-i$^gdMZ%Uh&2>`$uQ*d z{o^t)(rSHgLU>N-OY+1mb{!r9qNxpE0RZf-3F1Kk=O&YPvF{noKnN+gLQaAtC0W`# zi;P|p2uQOBqiUC05~yPl?cKCoAFs_d`@ST{-+4pdmEQlY z6O*dB>pabAuCUg)iqsMjXduD_LuElgo9iP4bGdqaI)8lnfQD`wrpt1n@_mNVJ;dNz`bwZe)siHfAMoEd|8*%>G3!pJ9eTT zS@s8TbvUlexfJoIu&0ri%k!YE!N5)D*wBGx+5u$gS>H!E;`_*A~Bw;xm zCo^cRub0z&^I|=14|nf#2+(c0x}IgK&w@lEw62%!9DqVx%OsR^tXXTO3Wy@}7}TO$ z?||G*(Jk>uu;#rnGgu4rq|9kB8JI8TQj6IL%|n1NQ`-$*nNFSY$ChR1nHSDDMrSfN z%p=!7NBdXA3|G<8C~&Jg;m7u#)fZf`MOzV?-eo zLhlXJ*{Yq(l8c)h-hlW%vnN` z=X#!ok+d*8$GMsVF|lB4jfDdM9Md}u5xK1-51aA04zbUOkSzo-Vj(cO=*;aG+YlLX5C9Pv5t8bL2x5i+RVN(7Isk|eBAQtyN_hlW)?E)8h7cog zP#g|`<8_%+XN=&-R82p7ZxSB{QkVPL_g>JSONLImc~5pE9dmN{ViLNvlNBY-$VR9!?6!Q4%U zz{CTrn+#UhBY8W7=kh|_dJ`7^F|Kih0N@}6lbZ>11MpI7=(8?m&$!(e%5(!iFBuV; zfjC^b0EyJbD2Rc9|Kc+;grhbOF_orcq#h=UfY?!oXYW9aM?anK$S5AJ28lY40dd?T z0V9=i?8|wYS+)lZ zCIyKI18h8Dsi)sxekA-%dcbp@-L8Gli($jSFi@3jZc7b)TDTA}BfvZZIYx~L0uFCC z6X%ZTd+!MVBvWjbvKdIUKRLmi&LcvIaXb;Y-wDrW1!6wQ?z;qNy!?37Z3cUOBg~!$ zfXLlGUz1t9odATVyebRQvt@9162p(iphPY58YUUK(`7p+PKc&fO9>1jVkReu^c!T8 z9rNkwTx&lb=0Ga*%rqD?h;Y+z%@ANntwMMylCy$v3Ab9Ou07mKDMXbJyEaoRwIXA0 zjaj5{!bz89nGT0--7Ng}<*VMec|LeJis+_hi5A1QZOi3Sn2C8l9_e8truld*^$^IH z^ZKv<)&KjCe)-uy`4@jtMNB&gn_5tx=W;ks-+lAlo6kNk49n%AbuA>Vt(VhTSda+_ zN-4+Vv2WefS?2J5IWMnnt}WtpUJmn%!#r1}Fy@z6Z8_I~SJ%hmRE}3iw-p_P@y)9j z^Hh#kuK?q8S={w-JidDUQoFkQ!{bL}`s|C(KHT3wK0Um8^SWXQ(97wBOxpYXU3~J% zXLon^52sUKdZ_>K_ALg(Ny zyTa3l508KQ_rH1Z>eafeKm2faxVn+U)#2twJz8&S`tFDCua4KRUc6Yh&AJw;r7%DV zkIVUy0c$A;UgjFktv5CvoUw)Wc6D7;qibuYetUZ}9}dguf`kmTwXKwDX7hYBH|>2Y z(^RKYBm&lTMF2)3rfuCQAuf!{^JQHuUdP`r*TeAHIJJ?(=azPA?9%lKI4o0~`5oCs^xtp#A6rry;JSPB9VMrg}-6SL5cG{Z>0bt2f2 z5r`2K7HOTzqnj|1Il!zB8)}6ilg+^+aj)>8k^hsZ!g#&zI!BUoqds#w z(QyE{!9?CR1}9**4gscJa7H)lfWm3x3o;8&#XJHett~=`lfsd zLe9A`p-wX!GW0}cF@0rOSPA>uCJ=}~$Y9;Fq)EznF(&9bP-KD#c-ET@?llPZt3FVt zZ0by}vLy=5W9&;B8h{YFRCh0kNEFThU~U1Pg|IyAkw{FK@(gAK3K&sQ$PDhtQaEK% z;1MhZAPBHj%7+4c%&EHtWlxSDx9Oly#_J3uARi(Ipu`t2`>IA{&5yYn`I{%E9Dtb# z3i(kWcC}Fy4dH4?^jv!8K7wLAEMutN1so?SsHAHuBnGQ=TZj))Ofme&+gEXTR4 z;D8l^n8yg8KXb%xN5Krk^ykc3;&fZNgC{E$J`6RHUWc(M!7jZ^V|vnkzMIaod@beT zh#~o4kDCObh;+4_Z}Zv>0Dg~}NidrjBS!MW?%F2R3=GCnimQ+4%SZryOyPN#?|AJ< zd`lGkS!R;Lao(jXCRj(NU9~}Yp`G390c71z>@|N z;)rknQ_uVv8+C1(!bC)BTO0S*=t3TGU4z2*AI}%uJudL9+DA0gl;FEt_T%JBjd`rb zW=;TxgDL|67V!k*pJhh-l9#)0M2tk?7zUQqZ0tWF40dApgi>y_3z<9_*t|O_VL5mA zFs6dzA4ttlij)R%gCmZ5@LLGPL%-{UN!#V&u4d@L#A+#h!NfT86ClI@+MgD58#`-w zDgg#@-Fg#|@IVX^e$E&72>t^*5g-Q4{u#ZiE4OjEzG4bBDB^lEVYjUak*@WP>L{f z27(%gTR;r;sc<hSv0 z*XPFz7kc^X#o>4ahL^8ja$zv5ED8ifuU@<~@ZW#;jTFAQz5$|bZ6bB5)AH~D%ui2` zSMCTOKHPuM9&One;dEJ;qt-IlskdIP`EZy&KHmN8r$7JQH($Sgb+epLZQVY7^Xl>8 z>G9#RtREgPet1q~2QKm0%t+qPaE=U1<9xzu%CgHYlO28P? za|l3yuzX?VsXMO!0Qv&bft_hjM1)9c<{m@#36UIo8Xi*Ni9;(l-j#dd@%|zkR1CR$ z5lEJpcKPp6K7})jfej~czB@Bl5LwNb1jiK!Cd&4uQBaMRu{i~z_o$Q%H5r@buM-C=}%L?0t^K0D))caKPm z;O>+GNKwhq3p2B1Rapc8zDjjN)=c%rXLuT!a8HV^}cv za4r(QYZsXbhjuIjPMElqlB+Z#=OUKNo_l15IWb^Xo9AqR$Sk?ZWtd{_U`V^}E+89M zOij&qQ$gSeR!ORbSaME-05nsGAQ1x5uELxO2qfI=M$kM5Fp2Rf=7k1iN`Rb6+_Q9< zGwH-EbsyjnY9?u@LPBs*wRFM+7mEPURL1oFK**htzcqpGCl)0RS*Io*~hM zFo+1sRRhV~)UDQn5qV&Sc@|W_*z_TrzV(ENJoLWbU-=|o%VARlcX9> zq(H#7ZPPSm2!a_imCTZ=6Ninjkl|mwH#cR*)-=qd5;GIgR7%@cRrQG0S{?zyQY+_O zs9kF*^I^(2L6}?k>FJ4?4~Ii7#Voo$y?*st3adf@EXztP>$*aAwiC|vm;fz=ms*MV za(-AZr~A9RTBp~aeDck2e|2B@`bap_Pk#2Z|Mp+~e?I@}lVASnpS5*G;)$p0>+9uo zzATs9moM6~s#(QacoHgR7UtTUOml?0cLZB6D+g6!0g_S#xoNjFVlub#!+j?QPu&bNv%X03mgD?x3si(Et&BM&C zmN`9PfJg$p>$+_sNeQq>VdC@U1mT5*xwK_FUDi-zky;BvESHO#y?XNrn)TjZzIt9YLf#SMh7>t<%#wp|^suCA}TH6$j8_4L%XP1TB& zG#;(2+{~!fh)|6>)f8@v zh~tP9%a$Y}hSX0YdPFkf;|v%_Izj|JtF-gL&4YZ0-{2We8sh*8r(z^$#UO-IBqG8L z!oZz{&76@vT(v)w52;Y{a7<<*#lL_MAPl?Fz`~S~AZ)zm_k?R4Ssak)Ani?#jAW43 ziP3tVXCD)VTew;{x+A!?mBP|gRO-~V6W7YJXan;!je7vPVE`e9YOW%%4@^18K&B94 zr1AxkLX9LRUxUcLC)+oi3~XgUM6w(unIYT(EJY@X`nwLzd%o@fk%`R)v^HK1#F@B* zdkO_1a^~ag;(HcO%L1l&hiIQd5hAFJV$f9e=WMVGyUE=hpfE?EC)^2R%%Z^+I{>l_ z`&>>(8ApLY1{vY*E*L6?lqPZJZD0(*OAR4-;IipUbVLvts$f7A zsYuXO(^3G)BP%f(Zy5UW)GG|%oe!NoBxJjznM536xj_xlL|TnUnxvOBA_E{Y5~VmS zqu67An0&;3_%7i9bIW4*h)5iG5En!PB;EI6%s4E@UX15?faA2LQZ zk$;HHK>-*sooOb>9FgZwEkS`C-A3{5uzMqBS{a~Vad!kF#`HQmV0dKtDJJljd))}_ z86Pn-C3^!I0g^JXF?A4RW|0$Q%ojcz5iw;2#Jr2>IJ-3x<}U@z9C>{1vCC<4(xAO6 zic?ZWSQ*kR(7FNyGBIL=Gf9>rv#@H9Ar0aTg#*CM4xe}ehSbFbSh6;z8WAJjXXF^? z=asZv>Wh+h9+D(Ab$7{oW_TYX=U(=R-VG5LDN=SeD%n#Uh}q?l&$GM17*Y{NwJiyf zr~ehfZDdyBNC=IgGK`$+f(Xq4Y3THAoKShF5h1$SSOYw@k9pI_SoHVgO&q60KxFmR z5MpMl0Pg-UM1sVg)zVa!4O5ZT&=e%>?u=|n1tgcv7=W1ymy+&eC2=J5q-tfP&|pyN z1;fMg?tx^fJ;s_5yq_|8t7herB__pG!edh!h&GNE4<>*xHG^@(rLG{A zzp;zB+!MPQ$w;oo%$`}%iMl0Op#}ivO+?cFAoxjU0!c0TyXfDVU2`Pta$ zGN}NwFPSJJrBK;YnID9M0}~;mxiOB|sC@Y+RG``cKuXOmZpiB~iAW^k(dfO`qvWaK z!@3chgz?t;;v{i-}@mU{ap$GRSL8C2rM$L)x4L=3EpIs zCo`8KySJOV_TDxkIvfwG`gA%)M6J_uItRcs&GS6Fd+&NWpK7gA%IS0}QW1xMqLyOj zgjAiBfudcA0Hnnq9AYPbv}65%uI&&@7_OM zmPi3|MArc1hmRivd3LzIz1Hw$+pg%iUY4uttJg1Io|enXgAMW40#9C=tF1Bs!53g=-9-mGSa=Nr{-hTV}7hin(`Dgq$|M07?f4?^W$!D(~A1~j&efN{EzPg%k zw{<&QT^)~C9?)9T-fv&t5TYSm-&||k4pRY(tzGI=%VE;iwq=>?R9GHP=XpMqDnZeE z)841Ue7rik`m$V*aNAZQE(Oo$^E4IWLI_NR1$)=t+S9{RsfDGS&&$?&Erm!T=rCV( zZ4aj>MmWyLr^k<%^I3>5PZz?N=c)Df^t2)ok|5ExtlC~3RtRvDqPo>m{ zkM|ExPoI7E1(*hb_VsjnDrK4{>Dxukqrt<&)3#l1u5S8vseHhYyLa!91%Sb{PE&8a zZymG~6Pj)3b-i3ziuP7mhzT)l>#o{u;lmXHdf%jqFfZqGKuq%iT$x4H$L#2yy}LuO zi$l-jy@w+cWt9>Fn06Yu{_NSbwT~UdH0J$qAN5|ID?MRCs3W);5n@lnD3v`F29y|P zT}n~ysU%2iXM{x-)G$JNmLd=$I>t!Fp^5+m=57&4>}e7M=$hFYi9vb*dME*bBU2{5 z1=El#5dorOxLF4`2xDRpcXGz=irED*SexxkMSaNkYael z$RjI(0kW+tARVr_m&Zf?*(0kpi4ZdTl?c+SJ;X(r(uGuL5D`HVDx(*YS+uJ^zY_y; zDb0+2C*&XTI`aI=(I z_ulI?WibpmM4(6w2<^Sr(V{TuFo5I@5HZklU6eY4JAGIJ(8F{8B`L@Vffml$y*?x| z=}(O@O(A-KnlmvF6Z5mM1k(npo~}zI6s#BkV=)}Q1xV|o1@8m>SkcW#v?}6|Uj=l_ zh6H9}GfiMV9he@$kR=JZ!aYmFXiP#QNE9)KNFhe9;8^yE$XsE!-J2O85a4hb`bgFU zgcR$K8X?Yc31hUC4NZue6OxVyj}R6!>)Ior)&iMJjO3wNPy~n&F8QNIKEMzNWHFevi~s@;65hWb zVMZeCY8={K`Z>!!hq#$R(gI^)%?xJxQFZUnUaLs9kccp+|5TVIq>+Kmv1@4p26LJZ zP=5WPEb_2a*PxpYEr%s~Jtpsz{Q$s7-yd5(k33VeUZiN(6qc#>oInBD-EyVN>bbmQ z0s?>mp3ewKU@v{iY8?VbJRAbjP)R{VRwk!D^tn(or)t_us^t$FF%XH1>?@F)+P;$R zSsCFF$Si}oOo0$MW?n4vK-98@ZiHeYkM8$8mxod;8H0!b8ka6NgaJq(@BXsHZw8k- z4uog^bqH^qvbi}+!rX@XiwPW%fC+Q6OWhQX7J_64hQbm6QVy8^r`mekL=8xSX`Ks!^JI-|60Bp%SJx5fm_Yi}g7ZkOKu-4E}M52u%}Z*TjxOf`T{AK$m$UcG*KeLNPby7qur zP=nup`|VGD{tGPfZ~ni3{TKiAFaG?W{ulb6{`UR-`=5OI z>cz{~=AmI6ysj${m#LN_fS|3Np3XPdHqh&v@jOx?m_w!ixe8svE6DKc^kOXY(NX>H? zI3b4bS_{vlP7EKlzKmpD10mA#i$nshI$jhWFt8ODmfkk_gIFN|wAKWX36ZdOGs_fC zfW-RU!%Y|w!n=k?ou+{2#1C@IG|2=+Q+P8J)dT_HLJ+3R0%2evMBN=COf~!L-8zQ> zm=K_MGS%%2gtgYS^*T#ncz9(7gy@YK`_@KEP^QF0xSK~5$rjiE2NEWPmaps_&?!?* zGK+Zt4?*z0mncMp#WU05BVrrKJ=7`uP>Z<>qq!qM#&@O;8}gI}U>YGprb_V$=O%SQ%J&bH~N*mGlrIakTZpmdHTeg`Nvuvap-)Ic$3 z1Q4c=?OETOCHLuMFjFvK;nW$!^BW}JcMgCkQnGY50y7MhcIPD%X2y0h-)`Z6M3TrC zU;q%70c@K&6A6*QPs}{RM$&)IC5R}I4(1H< z#1Xmc4#WkC%{oF!=zT~9h+J)O?}!P5CQknhq5~4kFqGpk57EZZm;v#q z=rC7diQF0?x4@yvM&2()#xo{@&I6vtOjSY@$Zic_5u$NVMg$VEFdFsNnMXHMDg;uI z5y{m=3gJpb`g(!Wr%Gvig@NLSz);{I69on!NGXuE0H8dkm?Saq{q_g7A%M$ia7=DV zM2#>)d{!JFKrJ;=9O({&od7e-J6V_wZ1kgL9wT@u)!f~Un2}S6k|Q>0w@B{_GXrOR zWMa4gNoQr^kRyReVPG{E1oX(RmZ1%z{GErQ24TcD(X-fwN1d&wxnX$5b6IzbVQLv0 z2?BQ0)L6L%P!`Q*5*$63{_aM2gFVg#_l-8pks$Mm!+r16*o!Fv2J0H>BqNU;PxH!* zNpvueG+GEEB{~p2!7bS1Xp(xM5se`-1Z#-_Y3l$9TCit+1Vs+kLlETQ*$eH?Ohv>4 z+-x*Xrx0kEcQFqFL^N=B2M^jenf&FRGdBT-rzayXkZ=$nxux#YZ(c4OBBCH5H;sTm z55xe6Kw-f$-lNH&;4Z;*~7<{^&D3`wk9&OU8)t&Eh+;@NFs7% zeU2dBA&3gMEQYjayH6?|ed~>YfFLZo^=!`ba1NfNpcH!M^4*B2PE-C)+t!%TOp`@6 zGbWtpDFA}T&n#1|Q_Zu%x~lr+vK)@{wyy8q{qXwDC&J0w__ka`N;tM{b+>t*Q)y7= z833#)A{8lhDt+x{aeH-DB3|FTczAdy$Fi>Yw}1cjvNS~b>;Lc{ZjSSzzWLYx`rm&4 z_S=8{U;d?r9&cY?woPa{9_xBKKiqxn>c^|At@rQ0eG9==YK#8sfA~#n{ZMZH=5PM) z#qFE#zWw1p|N7T_s3^27z1CSn-#;!-4^K?+#pj=t-%~x-ufF)=H~;YK*4Cf?^rx=& z`pv7K|Kh7xuU~%rcz?W^4}84z*tWIQa#_yPVX9M^ihR6WCZ570V!N8Dx5agaK*9+T zFKfSEJk5{v)-QFBDDN4k3Yk|nM zxLem2ke1T$w26H zPai+F^Yk{zbk9W&ud2#cSgxc1Yb^G|??)32yiKG_Om_<+2#S(Zd6zOiB5cRN@m_xSKM#B;*m1m6ZUXrYRi_2Oz2h z?#|%Ql@NqrTQ|n2EbaVM1S*ELwObdNt#49h8ajI?Ks2l9)|#75(?lMvbvK(26GjJc zGiM~w2#&&gmDoV8$2s}@wi<8r43+^@Gk23AJdM<%r|x+#JIa9Fq5NdVVVuNtc`^AyH}@Vh0b0E`&G&vdE7|0A+TJ zQbZJy%vm3@*fekxLC`+P044I8wWJ71Fd&TFAsmU}oPu6NfEF&su@ z<{U%HkkbY@UO$v&TRROFgqa(|8HRzQvMQL+-I>rWVnhf6$;eO|9J@nqU*UPF6XGOu z6)xslq(+3gBVgg;BYkUZDG5D$AR+@H(8%1EDyggkKmbA-kU9*EY}kK?Yzi?vq!h@6 zCjex|U4Vc@#F^Zf!vK<$bfXd=5T*0M+*4AO5tive9suHi11KQIK1dJ_JI;$ZTJ}Iq zEu)Z1$)R%q1S#H=y|aQ+mLjQnb9eL6iaJLzANiZ5l862`OICv!P3{uz}VPF39SrTTBJsmRA@H8CmPQ=+$2%}GG|Ja8{B9qOY zV_(?a^CIqi6YX~A40dEUCmhk}&&Y9p&4|!up85UH`xE>a9j#sS-W^OJqN#Ec5d)5Q zMC{tL7$T>}OwJ*I1T~P5fz2)XsbLg=;Ze>XYHaP0GRoWd9HmxT{(3EiZ_NVa9<2xLX!{=Jj#cXP5Af8VnX9IW^$D zu4{)4Xl&J=d^+{s=J~)x$HQ@17gaBH z@`lkNmC!f83dGqGw%~bKb??1eI|Nc+^SO3fN`TX|Z|NF0( z^?&of{;&QY|Lwp2KmXVNmp}d4&t6=;eEH^O;JdqzA79+O6pFqsKYa6pxqb2Fm$vy| z|4;w%v(G=9%Hiw&?SJ>*|G!?mesg_&`#=1fzy7Pg`m4YH=G$*iAGb@Oux^fkC?pT} z){Vhe2yb14fB50y>)-uWhymw^yZf!L|BHX|SD$_IX}9*%U;LDzJ6K=Va+tOC^>m(! zc<-~+%Ix6h%b5#8xVF_g^zKOT@#6=%4UA~#b)KsbO;i2;{r6I@q)w+x`}Xa-kmLXK zpZ{S|{KcRB>2H7g>%aepuVp&U2mb2QSLMY?HME47+A5UK@+rw^wTdbqn^PUm&G%yrVOKb{|tS4R=)tvSTuaG0b% z-rujSQ6fc$kuG_Y)(>$M^9`D}2Z@r7q?aNoXZjYxAADw0-QUlu|>hn<&8o$55t00^eg8wdrUZ>#t9G3x7GZPbcpV95x5MpI=Gk8o8)lp=uM zwoV8ngy0MT;d_g@scCoy01eE|%_kx>Lvw}zQvyd1aJ1e_D&x&DjNAo5%=)&OX&|_1 z5COm>g_+#d)DaL!%snf*0LazQjUh0sLU6clYQ1MxXIRflbq2-=c6aUCmO9VgG}e<$ z6S@{qHV^IKW|=1<(Az@gXsrhZ3PtY}5Php6Y^`rfyoaiLK$&JB%uLNWv)FgOu{8$m=sm7+F+sSrtURNT7{EYGvPGvC*Utt%lSr+FF! zA|tk+vtJO?f#4~tL6WRL!`vlC*nBFd0fBN30z~Zw7$QZ@^7Z49IdT+k09cB^9**aG zvt*=V9z1*)HuAL3{G2fN@KQ>us0X~rJhV5E;#CgV%b|9*M1(WgRd*$-sj)C3BLaZ~ zqBbR_0U?=ZGaWLF$tiZUKdsW?oavUnXUGr(SZdjsCHsMBmoW-@D-IzGj_j%cmI6825sozpQvn1_NStDuWJ@A4 z#VfHwA{I$N6ZbnM0HsW+oEg#y2xK1i!RMbI#)kq!hX$okP7kG&M6MYz04+S+a@I8S z7&(>*fHArkBAxpYAf>>VLdM#SL}p4XM9FamCIOYFRs;)$yJmrx6dG}nWU5K?gB z%MbxXz(XGCZjn2%NHsTNvIu~THgWfm4DuhwX+ng^66A zSi;AyNtB|`Q8qaWVgNIyGbczSlFU*C^N|OyySm2BaD3>Nsu>U4=Uq@$%BZi5FJsK0 zVo1SKT5Hpg9V49}9JOmHC9;%imzNEh%U_2CG9v!?(>eI=4+kfXF?NLSA_WN;3n7F<(_RV} zreN-EGgS{LMIcg!faYC_Z0i;gsp)f%Ww{WG2&-yqZO8-ww{<(6&b5}_w{!&%(bn42 z=`jL40DCjH>*K9~6= zzW%TO@BeZ&`fzvo?Qh<0i~asLKb+3zTFbVsrP2?-f44UM<3Cw1Pv_tM=9^#t>boz0 z^3})t$N%*8_s56j=Ed!|+xhdS^ZWC8V|>&WZkfPHA$?m5u>{)PLm@0IA5Y(Q^(yjs z>F*!TOI!ZkfB5@Je#?cw{{HUO>zhCM^E8=TB28w23|&3KfN5=A zL}Z$362~4}c$hWqfFPXtvTCS+j73SnYZ<6o)3hyT^4hjY8A%Bc0C+S z5s_I&J1GJPnkz9e24jd2K&UA*Xy3wJqyRydxfVtdNoreL6A{-fm6MfO!5AUiJltcN zs$~00H&qRALFlbdr64GpLAa}eYa#Nk9!~D)PK<@fw3Rv`h4q%fqTrn~oS-QH5ed5$ zf^Z|x7e4562@eusX3T6*L;~+yqj2o2XbWq-trOuC*xO2`T{lLL(5?00K2H;1AUKjE zMqe*eVck0P6`{H_hRubE4cx3HM>H`;z=%=OaC4VZ-G^5_pTJf1QlVNT|Ij*EChNs7-Whh0ss`EOn-+6;#8rSnG#@{;u3UAzcn%E7vDK& z$T-XVTR=$3Y4$lL*a(0$x_|}}{j3fh$gz3um&p@&SdMp@f%81wJ@x*YXZO#56ihhaEX>27`~u zc`1k5WF&~h-A1BikOK%Yj`$OVK;1=3X%h{^2xKXdq*v_L2c%I86_BB~BANa-+Vpr2 zN*aP{02nTv5$v9ijg;aR?wPZcy`zFjO@`&YhlCMmmJy>ggi<)vOye;LaB$em>mnex z5s{L>cL2C`M2c~zKzQVJKmtMr0%jp(3u6&NPEp7BxW|Jv*gnWdFaHxFk&-204j_5A z4_7sz5D!ffL}d0EW(@@AVEqTByfB19xPz-6=348NyqI>)**E#4^qadyxRXdGTLGf0 z!u~uYc5XputppoOW_eJdg zxVw)vGNquvgxZ}D5j<6=01*(m5ycR#Vz`CJ=u=Nd&&O3oL`R3*?go935LotB8UzG6 z1p`3uz1EtfRepy7;RJMTC3_h;5V5yTr382_HCy$pH)aW^b={B%07QhC+P0b6JXfY_ z9?QBCVJX7-4lqZ=d79_L1Q6T0B8vCW-U(36O0A=LFs#(FZaMZ*E-+zsygIHY|H+qM z{_!t=`tI)00p7fM@ylQQ{D1s6|88xK2)_F2^VapWZE~2VtE*|69`B#-?;j9oZL7O) zZTaTw@29J)!}0d*+wU*S`Q3;6kFCGIdsvCo_2b=#4e-0W4=^zVp^FC?j8t*Uh(dtH zbk&%Or=g^&{_5?wTiclM`==*sU3>f0H^2Y%)yr2eUM|b!hks54_=8>|I^?8 z=8t~%bBlk#LM`Z5U%$Qmi#PWV=U@EtCsM4NO*FlJ@oBf_=Cu|k(uB#cZ_nqahmQ|a z7B`ftt(C%#ay~z8cB!QTMAr`BhzJy_UA0@c`7lpY4Nu6GTI;1&X6k9uL_(m$@u1z$ z%O#MfGOKGw%b2;VF@US1$6=nG*$VfqMdY*Q#QgH`!pzU-OY-bY*tfE^C7|@z_qn? zLrOY=qIZJurVvgP1kjrnGbw^#VaALI3;@v#TVsgr^uQ6D84}FL8GFaJT5DM+>(&L4 znFK6A!y1~au3#OZ=yC=(5wg~%!c3&vdQ(@$01-j!md02quFg`E%mx?mt2WK!nUD_z7U{ib)f+PnIr!e7#3PmzGSV``3G z5wREVhRkj-C_m@5($#?dyD#ZCFTMBY2U2`JBv;p^SU zn~cd}q7>PVxhgVYHs8e%0bCdX-GN3tP9P&71DbiZ4IyG6paTN3FpqTk3|~RQY*OQA zXH8yiFc0GN8IR2Pu3AgUOPcze9~DzXfoST_5Ss_2ZQ4g@(hk@eME1%7X2Cd&-#6DP z8uWYsBvgmE`#Ldl<#oWoZkAh$+W35jXcAB@uMz^Hk8wZw20$bcO07I2c|h#_Rpw62 z`4i`55gD^N#7IuZ;aV9;Ji0&cJUsLcJj@OOW7k2TQ6G}=wI(7&nFC3P7#3!h6A>a} z7NaKA>wy%59YWf3jY6bEURbr9zeF&fpuO*EZ~3JpaB$?i1ZLH1${pPuiPFP>KrC$P z$zvqOMEM5t2pne3U?vPwH7A0M8-p0rXQrg#08McKl2)WJg{rmIN43ddU{egum^VG{ zogFw5LUS)uv(!}KsBlL&Gqo6LIX{-DL4b!5a0Hks66LjX4;FNf3@9KRMDm~`@`B`7 z^C3Wji0s`n)18uz5nco%M`&OJ16hYO-VhI{KGpNNKjf60uyax>dDi5Il$DK$XlgkJ zn_2IgJW-M)>P91_Vc21jG9)aQS=gbDRP#8PiAXkNrP33LQlXSTf9$PT`R|7Z0h?Qf z!sZYO04zLKFLO6%VHOVeu0uIPqvqLgY(50LcRANWHCOFLin$Rae;Rqh4~y_f??Yy4 z8F2-aAqGCemqrLFxTPf*QY30lh@(&R^ZsEv!JINUi% zOr{nip3avq{^YZX3jR=icrw>*KO+rT+f@qoQ{iT@51TX*mf~UwakqgGdGlC?nRvEuyk0 zgqyLD13v181QAYV0O-Wld|CT<-+ez%^zQo)QdxU{db}JCS83=}>Dzhxcz;*xq-~v! zM|U+h5%Msu3@bQ7GrPNgy19MfF@N*@hmRe~{Ne)L6bsVtzWw%(|M(Yg-n`k~e^^e- z=bwE-j<+|rTieQ19b#>lS}Ruy3|B9O3CUD7Hg_$;hiQ6xdT1L6^He$94~K)BVE}X1 z@mSj$5|mP^h!syqrkV;?Ft2q|i>})|&xqVw=R%ido94sS?Tt25x668&r+Kbdm&*yl zNMw?-UM?6!K`f;$D;Mc&yS=_Kv&&`a+QUJFLoMSw0dZ@pp+p-o&+}}m4)XNz$*hN( z1)SF|Wd($;Cc?E$Pft(1wRLNy6hi8~J991N{P38zm~Hjj7q@QaW)B}fw$={UNAG<- zo!2;>pPoMbek3T+SUVrMWU@9K+Nia?t%!0&|=JnrMSDQN|99dhI^=$ z!q^=H1WbqsLAxSz1ephjAngJg954NJmxclnsYg;Y#AK#fv5=wlNZ{z4%UBqWj73O0 zaB>ooflDc;-laqyyYYhvv#7_t7D?G{WXV1GKK#;n#o!2#h`k<;l7S!KVI-@Z?|&Ep zfWb^|?&?WylTf6~%-xfn0P>w@Nc_H(0)0rgQ)Te{R}Stzj24JE@dN=jis;7*6cNJw z3?M?luFg-1`7K>nFQzhDoCfXg0 zxc33$Gv^ZnT!kV0$aNy2>U59iccH!FAai~oGG7ZIOOuE(la3+6hE*vLi8;*8Gi_`@ zm4FtJ3{94hQI=T;fx8hx2KvIDbmXSyX<5wj4fI^qh6kOJb1}72CNtfu;h?t;=tztR z{M>ZpxrmI(De(vAJ;Bu7N)fk6VSND5UXjf^Cy<1gYDdhU#+W!K&Mce4-JELf8BOEv zo_pkTEPXz{X>V0cbe7U^4PoY$$UQ7nN*OuBNu>>b(lH=>C_vJA;SnP`HZpq-P1CGp zNR-qB(l7wHdq#1qs(Zw^Me_FDO#&XHbHN;A?;IGFCC-q>{5&~8&EPp6br@|u-6HXaH5e51^{M?fsvv9dzsg;8s%ArsHS7FPSZ3*&gdiY$`tmXF(yvhjRtn| zoG}chs>516PWFfq>gs!iv#LdSDdIaAkeMXesScWt(X;fP!bjT$j)FiZqSjmXgr@*5 z1vw*y0Adg?fDmDs0)`p0HF{0LZKsce1|_s_M-XYM*!PX`7^nM<7}IMWc{}wC@D5?g z4a)({t=5`G0*8pMr4|zE+RSO55E1rNt37ir9V~BH%R5 zrBp)bz2|Q$B3+xg5{ar#WrB!Wg$NLNT~;Czp(F||3;@^E5n(YmbDiq6Y;D`@)qL~m zXP;>N$5P4<-`|~1=US)A+(^!+(~B3MRKTCV`SfP43~+z<(cN!ut^#~IT)U7Jd|WT@ zAMVb5S+xUGnG0Bt%^9Hgj_%vKc&S1XfNsW8nreb0d2S-3V|bW@r@5jOHtmJQ%v^&I zMflarSC0>8J@SoQzkmC%oX;x%6hhr^ZmPzH?L$q%B7h)5aHd&`|GRg?VE2k=;rFW$V5c1U%fe< z?_a%qK?GY{i&StgRn#=hYvIDhwYPQM0I#l&eOui1?(Rct+kBV`Gwunw5JZ4oTTX0w z@|1cI765Vwq5|4g!wl;*&BqCXw%*bLUuu;i^VMNJFApE@31KS5nti>Owr!PKu3y})mrGyu>EWSDWrRr>T-Wncc<6e0_xAV5bUaM! z`SJX8pY|+p@7r0p62d$myfq;daPKVx0tu*Mfc2!Qi2yK2(B0iSGrMVJUe|@pfzY@0 z`sSM0TknZPu%j?SxDY}_Zx=#d*EPaOaS*5OZq1dvcZ874B5oHZSkLEeS+5S0%+=Q3 zwk`A6y)6`>TL5?vij>xrrOtBLE)NmzoyRx|h~%WJZl1!_h=_b!TeNk{bg$MG09XWh zvhZk)Ncyrw+d?-4pXyY|+qP)i7@)5ki2#9MR4!dxSa9aU^s{g0NZT7mXe{eF+fOmW zGc2S?0rb8Qf*UfBTSsD`7Rr$MmL?40+R!M#q1#kvhY$z?R#N~FfH0>VYY+**-JWA3 zGJ?w8!bjvrDoaZhW==i_d}JQDJ3^E!cZa=HnR$q7&9XiT$E$%TQ#Dg!gelta)I_Km z5fMqw7;eEtxVNPOfLSCD5UG`ik;pd`3K<-ix$KGf;wZ{-ceBw=kp^7QVMPmwJ=ib? zBMw2B$&Cn*bJ0GE?3gjpu=u5;05huxfLV|z=RyETW5eL~LE|}o-Y_5-Io~v6h=!Ci z(YL|tunoTvIa@5~^A((PfN)iVjvFQ*11X%^37Q|>~X<1G~NH_$9 zs--dw2}i_eBsD~N=eg}RYb4Ep6C^VeDJ-xvOqdaHNGO22og5&{QVtamDZ`6M^-Dkm z70F?dhMF3{!(6jdHXy>C3xfe52n!;)M>mCJ{j%n5w3dwRANGbU9isO9C%Fs+NRL0H zN1KP8*kKrSfSE^^cy>;j)2Lh1+>qR|Hd6BW7s;lzQMGOE93-Xm-eGL~x${Mu4ctcw zF$m{sgy0Dz?p`~-qV(pN$w5fS-&5e_9~GP6GN zmtn*`4c`Uy-YE5?Ln)deLg3~R!#Gmvhm0IL#0TBol+28C!v8J6u5G zLxJdK>15j-Fp-rA>7yXt+vT2rqjVrevZBaLxi=$bYiAI#nFD5DN#toO#J!ujZ@3jH z7KSJhP^&PKyNQfEBJ15fgi0V65%u2MDkAxbi-?efiZ_Up+iMUSAzVWIbO<+5;#)3#j&V zeG?%D7Uuad&vQAS&&zhHMW#BLshYLc^BI@Q$rHMDVJw9LsQ1lO>pT^aby-qm&&1%+ zw+@KC^-I5mIUruv^CVTcAd&jk+ZGVDR$;z8JptjiY|}i=Qz69V>B%7odE2(BRt`Tu zJXxz*kbS(lS+;Yyd!wijN+0jv9j|YX6AOhSw%)d7t5dB!I{<_cP+J!wgboNW5pr~b zAa}BErt4{CrV^%Gn}m_Dbp`M+D=e{g?cJ93vi08En^&)?Eh%ROwW&(q7BCe?=-Rf; zt+&4Rbpz9gxo=>-yLoS0BG8=WToC5209+~ql98Gr5_9Rf&l3@VYs>YXf)N~CwOenS znd4NT2)HvM2A?kvYI-#v-JIRrnzoBDp<>$>0B1MrjU%l0G;)D;LL^h_0MS}&98r+M zqW5NbmKq~OXb989;?`M#h(R&oy;7?=5{oq;K&gTq00OO}1_BhISULt0PK3m1rE+vc z3uIydONmPB4FC}kD0)|77Dgn;z$8*z)5OCN0+F1Q`@CPS7#3mbV<#cl>v+j5%$-IsQ4m0eHs#)E=;;%n;S~-*g2L?VSm z!c1oV|EKK75o|v~Cr2NOqgmeY)-61zO9B9c-8c)w(b8Kt%&4YtA4RZ;+0631B2(h! zmZ(Ga*ag6z+R2={q-5~2s+O7ARR2IRpLodVc}+2i`-qj;J>%m6H!~(m6T0^7LLZA` zDUwjJN07%zT}}Ov4Gj#8Pu-uRVRHpUgiIBHJ??5hPg3&)34RmdB$KKVXUC!wBMT=J zk)~&1?v{rd$HdT%r2uMuy*-$||YJtbEh2*BN)2=(kF%(BS7O=f}00JU&V{x1vgs%It1uy^)hOD$f zBxZyWH9MA*@zVkTMD~SI-pAqYLFsG8oS6v$nNdfC==gbvh*^kGHC2fN0ZQ&L0|Z9Q zS%$QlySbDa;f0H-PKOzBsGlq>4PBWT4-qWvZm!}PGP%~8_CMGIu>Hwpa83MDb^%sBkM}P8Xzxcbq`R%Kh zHHYh=OWV$uwXj@Wd7Y2nzq|kKw{I7R%jJCk@bTt&_4e+E>-or^e0I8g+aMg`hxd0Z z@{j-Ik4vS0@h|>z5_AI?R*FMj6D&=@MgsXMyx=mBL zIvg2sYuoARG*4BeVhIsh*2UDPdCD|-^Dqw#Cqpx*iYad7QqOR34HO>=SEC0t1B zwx~8HJX~KT^x|eurzZ$I&d187_qFwPTbGp3lrp!zNh!-^)vZN1F|U{9aCH@isvTNB zf1H7arl!4>QZQG?E&KdJh!MfO6H>SbJ0V%uwr*StA%fc^^-$-hr-$AZ8GG9fhXaKy z=f!#pLE%z`z*=8USI4VpZR;H(q7(ubqP8pszN{M}O)^ZyfIk1p@mD4u%n%HqdUp zhqvBZ$uOU#HSJ+3o-`(MQv`F> z^>94cy4i+qUJi$C>swoEEfyN)gyE{>ose~NFemhIMc2Y5Ai{$fY+DQ z0;6R6lXq(|phPEA0t9oGIt^720!9UMfDs~6-jJd)afleU<`$Ko{D>u7g-n)0gq_x*b%FgJfLJg^%%Ec*keaX ziK-^kOhm5ELP#{eW2TXY0|I8fJ#k@zh;E&U6CXrI;er4OGG-iykD)*DV@JxY9x^lA zQP&hQcz6ILYk`RQXC}^c)MRITSg3c&G3G@XJ#j!;P8I2M$Ums|Fh#-uf-JX&oc%HY zHsvQn*Bt;6q+_?*y}V`~k@-J4(V7Dws)c(mEanmJc>_2Sq{SkV5E|L{X^+@n#RJ;k z#X0#jWN*euB!@9iC2W|eBBX=DJRDG19IUIAT7YodS{6DHF(5jOo#t7=pY#P|u4f7X zQVJq#Hsgv6jYBd@gX9<-7XXrQn898Ykds-)>ZC(5zx7^p2HU2@h(wtT>F$y~8&faL z2sr37rsV7pDJ=nzkA!R5ESOkLMG6s7kY&=pgSj%ZFzrN@r8OGoFnICW5e>-NOv`z@OCNF^SoZROhE?VX811w?R#FeWsCe47qHm zn&q*c?D2>LL$i2xFjLl&@HnV4*f_xqfGoTi-UAp+&whfWswZE!02!RmOy=rtLXvPj zJTKP}MA&jOHp@phPpctB!SRG-3ce4$P1+n1tQe4n+USx%fFhDRK=0i>m_oY~A>nm=2@>Nvd75D^$;gSsV81O;NOvq;)|HfX9E z3TDbx@_K1v&k&EKGAxWTB+6s|*Z?qgp^_t^nZbyd8f)KknQzMVEHpAUbZ08VT_Hx* zV*mn{QUWq;Mr*C9f@9%4dASr1)ovn`z?g_6=oMgy#3TW*ZkrTA%wvbZf%YE6v^wX_ zgP7ZhdO!vmXm4ezg-RGGIDk#_yl&0i<~lP;w;tfydsm(3qp2Yym#H=Ho))hKb+5(o8w{GmapG_|HbQ1%;I#ueEI1Y=f~59mQQY8yqd1a z5CMpT2&Go*7HzvJ@{>2OpFZ44Wfqwa^Rg`wK23Gmw7U{UU=$(^2OxJZC;&(R;Jr#c z6rMy5BD$#|9;fN_cu`X+(@Fie@9xa-^27c0&AV+~zxnn%j1qw#pDs-N{{F+N$Bz$> zYwuY3^tgQb`t_^Z7k~E;zk4`u%F?xc{r%fdKKaak^{a2cdH3e^>zOBQ+jOYwy0POw z{^LJ3*M3=EfAN_b1XwK<(KhR-etkS#8gu~TZJFiQrB{afaYH_pXR;QZD zPTIA#3lkF2;c#rNx7Iz7OCe+tM&v*yp~TU`0~y`H-Beepb(&}GT8kLyJWqXV>-nNx zBjS8MYjO+1fIdz0Wn0dtCq@(&my-Ejy>)kw0k4pOS;W=Vm6^NQTx!#9?nP>5GicXZ z%GS0NwQk!6Y0Ags65TWn2?rL*^3=m&Zd()LaQOc1+ilyVNPthJET{8Rzdz20sa6cz zmaT7>X)4FcbD`5^$!YOweo;BL(+Qa^+?Iuj@i>=Sz_qV^u|7?6U$^tJ)TthhhloIT z>Dt@2ot}!6STBx_M2d(674Yf(eW-1h3qc5xs%qP?)OBsW_p7TbZE4i8tE=O>u6?_> zst}nrA?QnY)gaQ=691a#YSs&}2(-3s+XjRap)RKguXQ%ljCRc|ISMa4``SH>tAx2V zb#q_W%2=Ye%bEd4eNpSZAP9giOD)pZ4iI1#Aprs4z(`>MfuZJZst^SLNrVB&6e9$H zL|SWRx~n}Rngizkgk`FwREG}T%}iJwbMDjLm`jYgJS=>)Tl$c^0s{Kb(-Siz>HY{_*7@n%pgztZBLqi;HglFK@ zRB`+mnbv9nfj&n6c*UI zpSPj$(j4CxAk4!-3MY@zyAq<>;O$dUjF~#)SB6g={WCm!OMr`k_4#+ZZ%pWxsMwq~81TimL&q($N;MDV2YEQ;0nAbmI zbTMUQPzqTSR(rn9at#`a-w1a@AnW>!nCDGo?f|6u10nWq>RP1uPVB(gjxYdJpAFOL z)f`Q4`RCrdyRk@Co>Fo^h-1>Eq3SWWQqYF%Z3kE*0w9ML0JP+|Be)2tWx+)RvWdWlejO1(yGoJVw#OCO_1e!#*t;KKZ)RaiDOCda`7kH0sj9=`nfbvH zt`Pv5oP+1&m&c1S(JmAR8sd5z_Jdp&IdycLW{$xI(7?#^2*cc|Q?WxB9yXqQ9NKZ> zFaW?ZB65KcQ5Z4R7Xw%}^MO5N)O!Nygpm>wOV)yiv6)8wBqEskv%vrWG;`=V`!dot zkfZ(n1mZ|<9W`O8>Vm|%0de#irYLi0u?Az6=mBIBXnrlCQK0C%%{Q&yL1Es6 z(g^RmG9!z!zSSZ7#Ie4*|y5Ov|gnW0(eyB zsqmr5%p{aZP5Qaf-GE&V#jBh{MX;Uw@4k8a>g9{g^f%x9{;RLPczFEy)t6tM&X>n! z@sMAC^S)8sefaS6Pf!2o=f8M(JY%gH>iqWo``edqro#jXrAT49KG53NPhY=om*vw> zU$DT<)l3XBGPxTf_TCS5T9);4etda*!-U(m_1>${RI6%p>#a3}Ac!JjZeXs>N+}|| zt{V}xuIuGeOPvo_O$`u{B+S#VMl7itF>_#GlEGY&m}%Dz5!yG5XxnCP?iOys45g%- z93Zx>Ns-sDUx9^gI!}dJT5GNKfT^1?({Y+DVCl`>du!XaZip<>Tko4PbDgW1p8Hm6 zAtZHI`U%jNXq#Y<~uy3JFai?~}~wpuG< zFvP^%w{=XQRY=z2SK z?R~D306yH`A=p%Td${k~>!Hl^As|kt^TWr7L#-vitT)$f-I#E@oYu>FJWk@YKCY_$ zFdt5Lk8a_*&2<7$_ZA+uZQ$M`>xWqo!z_U`>kj7NR#?D##@DDBQm*oZfNn;_-oiA9 z3A?!(nxTiFg@37Em_R3Q{Xy=&U+2oa*28gc3)ECN&&00GPtm>GPq%v1^z z_O+SXXxn8mcZP@n)h1;EZCn}L%o~_9PHo#*M9qbn&9z&&1)|%~B@^PI%vd~ZX$HLo zc(|L;LAPMH;LarJ8xbzOE7XeVzJ;I&OlFmdoxB?n2$6QpGc7B+0>Dg#YXr8|_c)FK z3qasOo`-hx2x4-#AH9R=2Qz<0yHBk~4ak0WyCDdhbO0e1)uOWfVyadKYpb zGLK1#0|7O&D3NH-M?&NX%vhm87g$7qNQvS7s6+3-l57kBV%9-KSQv~kFK4F+bxuD4 zQ@Cq42uC7ucipzwOUrV%MC9-sCP#5qq^62S%dDxh@PMpihplnsapqOSjQ@wAy)h(* z%ham@!jN;NkRVwjLNnX#oiwB^2?FO16yR>ml+_Zw_q4|9h;=7(8@p+MyQOMRh=Cxh zRYU}x+zcH@b`@p@s4`0$iFRAPBT)Jl(uJ8zo9(Wn44@rYGYnfoR?JGK7L11whd2kE zh!QVH#NHZb2Tk6Jnlu?9qJ^0$BfDDDMo1~-wFp-rLI+@CDXD(jpTNi*o50MU+ClD} zQ~~!ar*HsZ;a&ci_txkuNQt7ESxRa1K_txntewpuL|_>_psD8PmBCLL2Q+A(eev}y zs6$eb9N&jAvm<&2oX{8I?&#V0GU>{=RFf=hsz+B5wh^_MzrXNL9NL5hG>uF)=Z4t&_WNZBy!VEr;WQ0?jqtfKixL`=CnQ ziO?-fyHqJsf=RpTwnc=yVkSlhq=_>RePPL8DxE5szm_iyLcG1bHPhZz+a@fvPM7mj zt@V7l^nMPciSc+i{P6zmo6lbVv%mZ&zxmDYCYiqc?33Sr_l}uW^(S9_b$`k>cwo6-iuJ_By zzWesgtDArFkN>R5^tf&J52uIK?zRil)SQ=Y&3)V2q0HnE3|yx9Fk9#sUwrnP-~S#3 zk99gsWu9cJd~t@WbI~uD$D2>S4NFm&?=XbeO9!@3y2I z>Af`r02TtoI!|s|>qJu4ZR2oc2BfrIP16yOvK9jnSxV+eZ>^=CPfY_stqV(-5|Xe; zMZuYfmi4@C%Uo-b66U3HZ(U<^w99(Fny(%nKhDSFB(tsOr}eV7P1WY()wXWyx+Oat zV3}}QO4+sz0IsiZB0{?oqp32|x-3`M#{f5ta1fGI8WM5qZQHi4IvozlT1gR^4%SRY5+u!_3-L7BU7Szkb z{l~la9&xcg&65=Fr|skEY1=OIRQ23bHVGGA*UR1g-PX3N`6`xe*_I-uZ<}jdw>G@F z-TJxpWfhh{=xrSj9rNYh-gTNLA#8o~ z2q_f|qIWFR*KL|6KOZGmoed z8FVeB%181I6CT~eOj`%U3<-#cwJ%iwsz6o0N@ZdSWTbGl&Pa&SmQ&l-S`XolQ+3nq zU2%7_5Mg5GI%(U;y|t4A5Cb?A5$c|KgS{b2MXiN7qJsl&BZfgZ8*rd7xiL$Eg0D|17j%KAtkTFG>IdYu!p9?7Ewmx&0rcc+>e-s zup^D!JBIB!4a3OP<}v+4S}!B{;~zEODVE?-3~XJ~-kS1L?LDj`(U0Ar=Co(qW9F6; zM&t}Vat=)`6(*S$nOW)Fa?+_rkhldMh(P1YhjWL^L`ezdQrjhnP zA&f{-RzP6Z1Rpl~D31a~LI?uLLY?BRi+;VJ9#y@3=wz%XLO(IQf&@)Kt4b>v2> zstITgQL|;;GNvDzS@zp|)JJ6CQUd=m@@-+5(FtvsqjSv*0Py@BV|2a6ZZk*;VnlS! zY@Xp+HS@4k%2EDRrruMKg{4?rbvRvf@DvCj1Yq(QFZ0w&nW-=nA(OaS?AA=cAD3x2 z^KbzX;Yb%I?CzGaeMVx}X2$Y|(V3jIK*I4TM2I%BafX8?0o%Ms)4C2w8TSm+k10Y&9YI=&I5|9pe%MUD7ZUC7$1e~YI(AbS?SZ2xUhg`UE2YO>PJB)vJRNK^t z)Gwc5h#h?2QF$gzLR@>#8tUPo`7vR4uoN(`i6MgOz31&Qv}u`#suGGMEZP?Ak@7_m)=!X|S%j&Upmw3t>w4Q^GX1GeTnA ziMeqyj)a9o`p2FU0N|5@X+p=f%%PTTpJ`!@3QlY$e5rxnR-YBO!)` zK0DyjTnsb4 z3o<=jHX;80?FT8-+uwbAcUl`0Fh;n6IZ{*il7@b8x2_8ktlM_~_%KPiwB>NDfAL@Z z+3of5c${9ndTG|AicoZ24)eTi+g!?O8sSnaxVEk30GCSA<-9-`5QN&R7caNIkT5a! zO_!|+bFDM6wB7;m>iWh!TSp4BsEuU+h$E1hA(`LZ#1a95OazS3m)5pzuG3k!2yfS`*5A|4+f-hTfUEDrNDPcuQZWxKk%d3t(!(xddVS6rU%F2IP$B9(-_KYe(+EZcmXZNne#-lFs4 z{r%O=l|%2owt(%jSZ{~JM7k15wDs|PW)d|=fU6>UekhCuz_$%dqc_lwOd>+S?Xpx7 z1Y6G!L|CM-)aZ=^`enI1J=LjBBFQjE7!g?4JW;1Q#O-~(5MnKb5qjTxYf~*5f4~S~ zJv_im*RA(9)hUl(>#fMhIU|7lpUo^Z+!VpxjUY+}Kmv9MQ;l$Bu3Yoasn?9uC63uN zk`)X$NztUaWV8`w>ZOIbDKdsx2ampPg-|G(nr|BkQ<%F8G9eV^TB{S9b}$!rwaz6s zC=P3ZNTmx2q4ly|BtDE?X@jS&%?0yHpZN5?R_p))z#hXCMl9OMd)J7RvcIn zA_Refz(@cC3H$@{M*{?gk>o?5M2Qt!PArj9Q*1W7tLmJy_gZU?@m})b8FLq#SDZSv z_S$RBImi1x*S77ATSh`#z``}CB9bX1GSh@xI~6d!Sk4331K)~86|vLDK;k(7j;eS86_gDsDzLD zTLm~oq_8h9pKj*r%wlMUGb=o22C7A&2mtm^M2Ni0BtZcd3(`=aQqEPUgYpGF<L@FSRS>m_#$P&);^TlvNk-QM;vev^r!!enZP+)bdDh09}o-6D~WTE~=Z>$Z` z8e*9V?nk1kT$=x?A8p@>j9QO!4>+OSqqP=U;aQZXEh7*UgD*F$!kvlX-CAmym5771 zp=DggE>Tq?#b+04F$L@m_#7%x7!7_Vi-bj$BEzE)_+CZFF-28m^&=Gvp~ehYV>rPF zlL@nI0fvC$oRUPP$x4-qD4)(;L=^OyD~H*QeI3Pu6tP_(KcWa^)1xSLU$lb$37)Ew3qA?CpWuY;K2&13?@nHxW*tV^zP4~=f z$W$({TNFMS3`=okjycq{Jhr}B(@ge-~7A3`13#a@DiO_@1Eb~C;#@{hsVp8kAL`!txMJn zPqi}XRf+288T0viJ;vSVG|}F7Q=trHb#l?RwM&*CkK>0gKbUH3ed|}M1GvtY+pV{) zx7{Pgh`#R?IXo`b`?eip6cU$T0#>D&4S$Gm2#Lz2ZK5*f{Nd$` zQDwx}AHJIN2%q=cZO-xici%-M3ylE^LZ#&K8287+%(Q9iTSS811g`1Rk9&+`h|u-2 zg?qCWk-eLWdW0%RgnL+PydM6nB~>*ZkE~dwO;%Uylpa1S>)rFSHG7O36Jbe@itE!A zM^Q75`Jk$OyNU@h^)BP~!p5z9e0khHefKyHF6qiVWQ_ag$D_B-R4U9tKBnL6_VOw! z`?ZZoQ8Et7QAx!$#`HORYi(=7%p$L!K3%T6k4IH*m%X<}#StD6jb-@QH`}+SDkJ97 zU5ul*<-&?^X6Q6S94vr2tO~;@_b#fO9xB{avR1;fnyQc}SENVu zCarHCAslMTTr($ES<^^TsfW56c3*fY!<|iN6p|8Yu&; zO5tNn)=Qe5g@maHSB4jh-y*p)5cEFsDS^Ya*44k&-cKfM;vJE+0kpN2=i1i z=B1OATEZa=goqym7)!*%qjC)ZNoA`;pej<0x!$Y=C=^?jN?2yFghyf*0=cxdQ1_&h zTso~HkiHYKxTl8$OPubC_#LV`CrZKeOq_vcTdpj@Md?(g1eVkPPU6U&%c9T6e=hz(jx4E1_Q24 zLp<}U z=|t3fUrI+J7#NaBh27%~l`lf`+A02D5ivLe*gc3jGQy)Z5#bCnRf0PwQA!{g(ON^$ zCtf5gmv9i55s?sQlrRK^P=o=EUlsyL@9_yxoj`n6(F$OFlhiLQ{#yW)JB&WWiuZt? z2PQUCVeta@vIq;TOsY$3wti*66|3QklBufHgwA^6T5TrKF0!yfZHBn$IZhNGDXYA= z!a743vJi_^xSU|33XiI4)>fs%ac%|K#ue zgU6TG$Lm2%m+RMR&lT zofU<6H!~3u;`Ev^r&A?$)32VNtK{Xl@B5{hURr;>eYx2Fh%cs^Tr<5YTW?jh?Ttw8 zkE2wd_UA9-j+^Ore~j?n`=j#9@w$D{EZTJQqB-w>^;duS^ABHN`^TUE^c(5*{OVEI$@E-= zaI}mt6;)|w#GLb}R4PV6iW1F?s#WC4!a{7SsH4tsX5Mu-T6WM!5gpn#kEJ3C| zqqi-}$9UbiXVGKctu;#a`S3YOMPd@OaXVf=*tx8?W?Jcw`y(^=ZJ*f#vlmQT75h|h8lPO%F7FU9P2A5;=mQ)PG-Y3qnpz#(PDETy_6tb$;`MAIp&qEffc z?xw`Byu7sVi-jkZDhx;`q)Tgupe%y5FxSkA`cPeNK`3$|Ddkd@;zGkb6`QCCpV=E? zik4&pE)L`wenORSZ>{0r%_=yd;XMdX=4!1)Br`j*psyhBQ^+gPGNu z@mXFAJRayeCVnD1j>F6_IbjMU7^RuBAXr_$zMLBvFhPtaOI9AID}WZsfGHw8T5FUH zFkVedz=tv13u_*e<#y+j>iqF2VHDC&Zo{93Rz+@K8tENc`S@;Tj zK;WZW@oS)NigXD&h(%Y}AQB5BKnEc@e4*o1(1Rs|H&_}0Fi66T>|0f1l{qIBo63SD znW;`wFWx&NgK=$K#EWRc_5+zQvm~WQG+D`TxK<)&<#G?_*%e**hDajQXjzBxb3vsP z(TYl8qJk2ePq#N`Q8=RG#Tc#+CL(GknUmMI%NY(VILo25rc5Biyx@E#rmB2%a2EO! zFgPX@S1UP8gsT>;TU8Zln_*=}$uqsB(eS)_c*n z$s4xw;sUsSuF}gF$_R#M5tWEa52hvM7vYK&VKK~*tfaZ(fr*MD6U?ByW2=CG8`Rs< z&%jcX}Y6Mpdygk~*1~ z^=$Dt(}8epKv8p!{*qB|%kCV9oX!|z8Za}8W@WJvYl(vBYjr2DiDTX8R1_X68X1Id zacMCb%GZb#dE;bk`cTz~go(P@^5eljjRgHX2|*wbr`>#oSk*Eo5&ihYSvNt#WiAu} z_wdRiGE+o+5%Y+lwRi*kh}Kr35dyy>vR|~MdTZg~hws})D-5BU8HfmL4Z0mfp8=s! z5i@usa+Q=3I3dCskGQH$3DTruagW@8S45OX*)%y)b|U@)-z;}F@x zE05XQmc>8(&3AOr|MGwR&qn(1K7D?Csjt8NS!O)$x39kbxNq$*{_?Lry!-ggyN|=i zW#6R8NcVn;@G(Zux?G;T8WE4jEr znQiahZOk&~7&FYY@B0`7g=cUbdW0bt%OfkSw2EVTdPD{hJ^Vpc#OWR;Cci0NL57^MeDtiuGWvrV@yP&SCBD> z5ADW9DlDAyK18{js&IuBGn)8q=$DQf^tGd+n#RUzl&mC#7Q%Zf;rsKEs^7f~T!6@ijf5>lDW zlo863k*qSOLrcBzO6KKN1q@GwkrWIAq^B9UX;uQqkI-IGh8vRXmDMZ*5wuwuYOOWP zjAil`@pLLeB;FekM#&ywW)r{W88!&;Uqz=+Mct1X6>a;z&TGYa3S#8PDE-qdh_e!N z(9-1iMBWLie^-)w6{H;JXmRRca-}P+@fKUkkM2RDCF!XG00*jvPqhY}`YaJa4XdcW zNv5lcSczHHFfSF#iqQ4STnqcJ1OSKGB#VjFOjfijycw7WPO;4zorQ4S6J^949+|ss zwWd%)(q_@>L4)cf98)%VLZ&6qHK~cNJaNP@a#q?hmqIf_#U-Y&1ux8gfyfEDSER#2 zN>B+Psui?DRn&s9*QH43jYLS6M>;>FiHe1%JLX<&GO+R+`U{cKp-cCX(C4TLwD?t| zs*aTkufQ-pFG7d-bYjEs3RAptMv@jYX;WsI$H07Vs-!ByzBs8u%XDD)#?S{B^x7IL zvk(r0$YBhhb*?<%k^ui#)tMnfMHLy~hCuot=v~g($D(w9>z+Uxu85$4mx@T$=uB*c znvaQDK}`teJQh}7Ob}4*IVTI78aC=$^$^6&(4G>rh^+=V{5Mr(65)$URQ;*xQn!O# zb5vy+aS@fG~+2m*j%M1#`2YL1WPuYrICZ5eXqM z6p|xo@2jeU)R@ki_qA$aJ>YXqK#UL4dxYI5r45ULfud(!Hh3h#*HUOLQ@oAHvAP08FtBEZh}C+~;0Rab7USukl|l@EhzPf4 z2p7hW%*er9m48JQ~=Rdu(@dK`~RV%E$MQ~1IY1uMeyaZr((856>V#&NXX zh-x15dbwuB<9<^Sn3Cr)%PYju6+ptR?KSA%{OkYemw)_=|J{G}U;dx}PyfyT^I!ar zzqtJJ_ka0&-~I5r=iWcO|LXI1w_pAGch)Xy^6ATGWB%&>)5Tib_s{jIOm?HKKOe_K z_a~GRkH_t2-+cV`=U+V@`|FEcx6LCXYremd@iB(5et5LG8t-|G5gGR}y7O_DdCaZJ zbbr0wZ0}4w#yno>V*1qg{!ab)-Szo-9tRUYT`!dJ@#Dw$@4reCQ9I_KqHab(W>!TX z-o1PM?O#(>5b0%q_x$cRzy9^*+WzSGfA7!!&L2nCW8Q!Mi=W$8(myLZXubKb@n_j`H>^WK{jy?p-s^5xU>)AhP- zwKbfuFP~q&e0mwj(OU0qU#%9R8TI=5C8|Dr{J_FKbB?iXorrFa+xz$LxezB8=`kHD zJDz@6(|zCW_uKt(f4V*s)i_3LT~)>$8KF{JZ`XbIIUl#%^}5A;V|i0*su;W6W2<~i?~^tj!%we-354R;(XbCscqDZHpMW)*AO9o zT2oRAW|2zwX4*~DU4`9eMj(?+mFGop%#N-(l}Qe|(N;YDTInuw$G(B;xz+73WdJq@;={ zMWA>R2^0v-Y$jB!(i!6o3!a&+8OusrhGPzj1)sTJc96~1)J)+-iWtlUXpAYDjfKK9 z{AjI(&t`o*?pc1>FRfW-r3Z1f)<|@^yHB&0EJd80ENUw5ZbVYfRLHd9Qli>S&`GBv zrVM9-s`l-|){>P}OK8p;;>;t!;$r>Q?fWnZ=w*LWFD(9P*&qi|`O$LR>nd0aw6X zDKr#WRM;3RGQ|{mSD8p}ET%-p+&Yen;!I*GdSeBmnUM%iN?GYHiZdQ*CAG?PmXMjM zidR}7U`i$en1zv-QLDyMj4&nOz$Hwr_Z6$!8Z&1_Rq0#m4iOWB8& z1D%AvWesVGA)7Li1cx|kq-bf~vnqd-2UjfJ1Z2z@V2DXXk-#3)t4NelmzD&Pl0mwH zM0{*AEDPTC2su?)!g#}L{T5No2@wfJeq_Q<$)a;OGoL!2I@t&+K0Ag>SV(k5s=%kN z=iUN63q~fbT7ve>Dni>*t`CPR;$&SI&ij6jwaBu~0JyJbZVQ&D)T8fA$ny4#pJqt?~G za=wU8zdx^%i=v`TcuUsml|S~D)B2QPr4aJvxWK&xLIxC7)-n}9)Wlhdw+EdP6;+bE zBbx}^8PF}W=03ABGK5a*O1REk=fy(uI&)4q^jdF=P{0B?AwmBmW$?`Rf|%~6$e5PU?_s;=NMy%m~}Ohh>Gw{ zE_1qB|N7gXfB5<*fBl!g`swfenKJ%6H@aTOP)(98XWD2U6A70|{>G5(K@6AZ%^>IvL6*AL!xtUqb(U@Ld9>Ua! z=Kb-0+qb`Q3MWM8E&^AqJ6hXKE@Q4h(X1olAb^TBxD^JN~ZSm`ZC7!dCbRA8P|P( ze|-|6*H53C$j`p{c8-UzetG?Zc(eO)poseE>EfP`aU5e-aTaa8x2-Glam+Dh?@h$U zcrU$9VJ7eIme7KTQf6B4^ibH>uo`s3q9^tc|7i>dg&W63u)^XDRWFp zVr5Z{2(Cg8O3cXgZsv1r+r~@^f;WSxdNbs@saRwX)OmEF9 ziYoiQxqDTCI473AHB>HSr1q9Xb9nE~Ov6+3tV^%hwl?MzAu~~MhKvgdKr))JG$Vy7 z#t@a(#pmq%F3Lp8V&U}|529*avHURQzV|Ba9ab*&no!!zF=&O{WrbU7?1RxF5RgN# zvNT~7EDayhj54W6x#v8Rg_5o9syf42%G8eIE=;0U5aqH`EKm-zl<-Uv2)-#%hy`h3 z8fqmoR2YTL3skC8VnD=-MCKx1=b$PllJbip6L(#BAiJg_)Y>VJ!Ed{S7Q&R_luXd< z5xIv7v532y3NXqgap1K(EO0lgs4(8Z<$*@uIi{#lmI8i@lp@Mh)tnBo4HtudAR1rVI7n9G-nE|FqgxwRDI-Jz*4zv&gj`f^e9Tc_p z-AYz1m>1uwInc=fk0e4tlrq!Pn>6NCDYY^Zi6hcZ3Iob^5y>Rhg_8pB@4d}A9SwB6 z?1r^$8B8Qp3&pD{HTY;kU?dJ+5tpL8mMFMMiBE^B!b41i_rjDwflV#Ki8vB1+r~f7J@KV$18{yOIShYOCyr54p5s0N|rQrYAlK^U>v>6&|RF!PG zQWB}Scu7dQR3VoF1*Rgo)c@6*t?(%`=t4|QRltl3OGftI86p!5V2q?$!m3Rxd?rM6 z`XtshTAx9}l~t9pW^SrP1ShXexA4I&i4h<)vNsl>;AbsLrdq{Sx>9VHnr)>3VK^X& z{%b|}!#OX6_^V)2*FT`0bB$%9lS*E^j2brJ7g)W z;Lbx%KU6Mj`yxWY)=8CQBnXp?L?t191?XQ2aZ2kw=Sh!{&q>S`$+|qDO+S|ga0mh*(L zfl$#BN39~8I%j(#(#ix~Xr>~{nLa}}wRaE&*jHQE<-uoR757P0y*E&WV~pO6S<1sb zh_Y|nI+V`bfK_S-Q}7vMoSC9}d%ab~);BeE_ZSnGO_Ya+na=60n}`S+qnXASSl^qq z$`zDB%3gVY-1hz1BaV50dj9G^{0INgKY!}I{crxa|HXg%-~QkJFaOOySMBdVyo-vL zFQ1yo@#WK}$LpVd^YKFbzVG+fAHMqP9nn;dZtbZ*y?gg;t$lg9GfP&O$o;rm=gYM* zhg$YlqFiDuB4nG{_n$w1{o&ny*-@9I-F(LQ>SG*pKKzHzua9H0^k4nuuWw^Mzk4Fl zzxnR-7?1b+o{vYbnA2l;pL5HM+n3wxr|g?VrcW}xzWbPSBpngs7eD>!<9>+s$1z@B zUzzj|fBA>se)CQ1_VoUe{qdUVJDfMb zAn@!XA>#0)N)+Gh`wo#Fz$!Dt&^)ID^P_?_ysU3jG>7kfs|q&u;XY?X^lf|G@4dC> zr|08w%gf}d5AWZP+wHf%{mt#=rF*{6GeylanQNsna_*Pjd(X_snF&N}Rn9kS%`C!SA9v`#Rhhx#!{$2= zT{I_)O1P_W#1J)AOR7x2Ts9)|$c$1GR2cqv$V)Gul?JNotD8dFk zTuGTJ!ry?o^8i`^*N>;aO4KSRZf4W*6g8YI4udSfU8rPodJ|KtLWn2K#;GTf&S*3xE@o_Ci3BR>{T3)j zFwjF5f%oGP=sQ{}NU;^<1vk3SNc!G&Ul*5;B8Ym&A8T>f-`y*ENEiP!72GF@Atg5zl z5)4U{CpjR1(eV++(ygue7CHOCF|nL1>n9Pj!nevS8D&_E5dI@)43p@ZDziwkh{)8= z#UMQ(+($lU5iw>RTp$pJi3rhhn~DfmX*1jyOPW$tm7GjcEbJb1ic-u>OFT0j1|WtB zhE}?wF|!IUJ9%Z(o)kr@B1=QYQc`boB(85Fnh^64(c4itsJo; zDt?oG3bJyE$ldWZ6C%b4PHFp4mR8_*9RFP2& znkVM9GgqvU`dhkj?4g}3EO{Abh?feg)^=+8l5shCS%xBOQt*l=- zfFlCHf)uaKn-)$0Q~S$Jzih_^zZ!MAN=Ge&s-m#ud4F=^@px}*>`uRWHNKlDsQUN)F*T0 z-rBZVW?c5(Lc&8ubZbR&*{+$KRX_XbH>#WwL?WuubE;J9Zj(LxhmS9>$K?-yIfGf{ z_5RvLKE2-Z%8z6I(YN3J@bY?ndb;%f2fz5m<^8+={O|qQzxazk|MK$k-1{za`|?6m z%RfEG{dUtme)q#CX8!rlfA*jJr~h!kO&dw$1U>FzN-gNs^g;1cm{jYMr%jxn?;{BYB~sUYxS+c&D^p09Pv5)oN% z%%reBMm;}YxhT_nYm{Lo{oT9c_4T{o{`S|u`qkt0&YY&YH5F!S7U8$stEpbLJ(KQ_ zN3m2yLFZ$!yi^Kow``*u~5aeIv8@!{Qv*OynSxHOyhTco?sX2M)1X4dh2HB%At5kMK{ z9NomM=L#l{EEg&h9b>-UzI^@F$4r`YR>r%hCuWXt$OmwB0asGhIg^FlU6{oLITcXu z5!HU#d^!uEW=)u5_@WtWwVC=#fm4!|)-tD?X@vJ?RW*@RxL}4f(-UvvCFwSe>CLPy zEIE{!n~q}^MJb!ww)JBSlyhaqS(#YjlTK}qFwzC2Dta@dGpQ;=2`r4gQ&vYXlJbj) zD~gRF?XYI~+D%%xg(i$-l6Q@T6=~h{!N`e@i zK6(#~T!4bibt5#*!+bAL|6@?%~Aidg@ zib>5>Grcky(;g!cPl`FiYncNJ5OHE8%o0-}5(b}NK3&yd1)1)`%9YGw_;Dh_9O#}W zXBHODNXlZWrV1JA0@s8vtAmB;t)(UqWh!pC0H_-5g`&N;$dn><2C>8=Yc+dUJSJm>TNUl2Xmm6FtXX^%l4UTr|&>W|c+@v#hQp z%+PpI$TfsTn(`7+OBS&xdY~cbf^r0aabfTlionUWj31R50Yf|y5xOclstU~4(#jDb zq`t_*D_4YNM5-ZE-mO_=L7Y~Z!YJV)Xld|{%@hkR`NE|6)XWstI~7^=z)9yh38KVA ztCcWEWmY!ROhHW&fNHa4wI;#GinTK{Lac!kS$KafbyOe1;xe!4|LU{CQiBogYWvkry~Kz-vz)kqVbuC!(G&tNNJgVmbeID zz*`p>Tom|H&)g=kArhI)b-E4;pR*wCehv{Kp|x==*UCyEz|{v235dgrqVqOe$z01H z_qTWmIO}t5HMJs8@l3|4v|5?#eB6WWt##?vNi_-nLS|-VYeq}>qyR*PdD}KsZPt!? zZ>^h1%xS7w&cax6nI*HL@)R9bRbkPmN>&kK?X5;V^(Ufw`SQ|KduxmXILqDLXK!te z5$+;t&D`U79Q$R@bkE3e_i1p9nQpxe&zINR^Sgc9uc9)KgRR##`}6hq^TUw_m@1`mg`1|N1}v(?7}E%isLvzjyZ^K7YPkcFLeAiZW@%^te4t zRZKmCikgb3nV6_aF&8s&Z(XfdF=dJH^}6kQhwLG%+{1meCY5Ddd-r^~y*%DOU!Lpw z`Q`RF<|g|4KmW;34i{!++qV7VS6|P__g}rY*0|aK@&E9@|G)n8|HnW7m;aw%{pN38 zUtalY`_?SC#x@=vQJ=s2&b0sJC*S_l|HVK4_S>&|i{9JJ$F}cGwe5RES~FPfx4u_p zZ{7V7;Zea%b9%UQm6=3@njMcrm0D|3i1y0AC^T1T2&7G*~7 z8?c3<8goK^I>P}w{_eM5US7UDT`zq@p_NnS@wj)h-~0K`b9&ZvADs2W_um~EQaIjd zjFC~{zU@12J+g@G7(VALT7d`m+kLxS_I*F@$8p@dH8aDLoP}?IV~DHEFsF(2Vv1R188qRANTs;za9MKdyx!WN-@t16#^m|C-} z6z1#Y5+0QytX`>zzM_I5p@_yfh_W@Ks_{7P_xl<~E16kYJfgVHl{C~8MtouFean^q zho@RF86>!7%CZW=i*nmK;>AQ{jtP}fR&+B7YJF4DvM#uFg2XLk?s+#n+TKC zcpMNhLW<2S#Yt3Kvm%;fvN%=kTgUvMs*&q>wbtf5%q-m38W0ufMMn`Ds-!WePvzc} z06pXMH|x9_SS4c$8)Q9M6_Lni06w=&r&Y3~^Y8(dV(Vb8jEp2H;xd)2LUU;N+lg80 zZ^aVgvX~I^>@gQCQFSfu7YoO_0!+f9cpGC0i6$&Y^^n}l64#xboHW5B4*9j z8ax{j0eF6m2g)A7bV^0QtdP{(awUR)(M-Xja}m^oNYpvarNlp;!h8xD$V`ifnGkVd zO$+u}23rzmbkZ9CVug6pk}4pQpCxJ{rkvjrBV}Qcun?f`vsTb&s_2(YMD}V5RRoZ1QHi2rLeDD02m-@3#{Z=nTC1-jO^Hf%<;2W6*9=^*eXqdTyl%y% zXGel^30id#Ilt_{_-w=R$=KPnwWvY_%vYIdWSSW-ift8si%5DDAp=;jBC2(4^Ht^P z(05Q0u;KH-Ta{{O_gPXh9)hN(qBG_>YQshqOvX?~kLInyDmkh|0{~x5&uKzHe}$`c1_**jF76a_? zvuMntmam5pZYRvDW>-Z9%Xnnr>6!WQ!&j}_<9HCWHRH_Zr?20C{IHq)`d7cY-|tUP z`*^)ywl*iF$JRD?M{N*b;Ny6Rs%ZxSSVb>iULhOUh+ke`s`C1Dtt3&pJbjqQ%#6O- zIOZ5*+jiob?qiIVpho3BpDs;=iDTPz+ie^VQ`s*YQ$Fr5K9Am7Ywg3w5BK{mJ%p+< zRaHz>6z)#dN1Z8 zp_+5JwtYPArLCmLoGBDs)^`UEfxs3_|2X%e9EN@(RcLfU@+K4IvJ1+I_N~QwA|*#UdP)Z=4egV?)CVvZP@|STlgQ zud1R`>ReT3EmxEdg*!tGvY>um>kvl0v&>i+ZKuf8=gedgc-|n1HnXC_Wd=LNoPHve zB2c4vxgLZYI$PjjUepAaxJ zcynaXulQP`A~v560yL8|(hRYTRE3Uf{9c7gI#)C+Tq0Zo;VJx(BDCBM!U`E8jPSyO zHs7ozL-#WTD&bjHEd*bl!yupg3UNXt!^FvX?i5N@tV>uwGb%`wY2g9uedbxcQKvKw z{md07elJQ?$RNNoF&v`&G?7}hchIFV`X6v?W5_4Pqlxe$REyid(>cO<5yxxB&2S>J zrSn^54cO+Gz=0Cd-0*Y&CXLxN&z3mYlq2JOyf8Lq_!b~nO-d@Nw{?&>GR+j*cAeHG zS$jJ~O}yCTB`hO9S0>PrmC;&*uLY|et_6(LwRm6%$rOJ_m`~Xm7*4d3IaOh9E?Nz4 z&{jnyxQ@((-VNR+4_7eI5bg2#u5kKQrHAP<)-3%}#JT1%GgrLvIV{Yq7!iaP1%hy{ z6x|}?7$YJoiN)XKfMbqkO-O{<)c6eRV`TvRnNCd0k0=O)4Z&GHC$bGA!+o0QoZgxe zS5}I0WXxmid*Ak+5!Tuq^Y(Jzx80hVS`3g#6cNo@NmNl%n3(%~JnkYrJhuJ%>C+F} z^?g-%jID25-+kZagGL_Tzkc`ApZ?)L{?Go2j4!|b@BjPfr`97L_v86``SkMA&6s)L zFViFD+^j_e30-=lVr5R|%2KiNgo^_jT1txHkog%auLKN@;Y7s6HH(O+`?hb^dL|!! zynA{Yhi8?l9ekM3#}Ch{S~)*{eBU-HmMs3wUw_9wzq$7R)qnm!`>W5N{)gZEZhwB? zT7P-DS>xxY%Rm0d|Iz#RPtOccfEB4$^6Wl69KujYt5xK2zlZy_HF$-J z>h^f;X1z7+`&HyKsB+so?#+dI2z!J?6_cu{+P+`heR_&;L`n&MBeNC})(nKw@i?rT zm{gVzhr2O7KfeEGXd8xwKE z<;VAVXO@>QpSNxI%=@^p=y42pN5p6&?yW_}wqI5B@fgQ(j4>;zZ*4w~Ip*Fs5gEt0 zTrMG!6|HS%wl(c0bIx?%w$4;d`Lb<(9Md0rYg@P8^qa4~`tH;3rcYxXgP221P4#iR zHEUFr5lhwJVWvk+@Qq)+|LXN|qoT*-E~0H~5CRdGnT_LM;%2I9FkC{3)YLdxS(OEp z8Z(b$O=mOPFMCxU#~`ZKY}+?sjtGy@Y*S?-QBx-B zt+DV@xmT{FIwlo!Pj_E}4PvGe;${Y9hKj@8RftNP&N1;6Ee=x!rj->e;yxWfxEYLL zqAW_ZTIW|J#vBvvD9oU$*VEgGv}t(Gd9Z3$QX>4Ym^6|yVS?mhVoDZet`d%n-r7p= zCQfn@n~z%&@J<%$M9QSf#3UKviD)>hqD1=h$)v$pb&6VvkQ2CCmauOV{9W)#3sYsl zK3f$5;7j;~h07KKvnFc|hnG-g6iW${qM4N_-3@FrQ8C<`R8+bE%VMGi(4DA(z0Wj} zj3q4-vFSrtq5>?vnsO2bPzY63mKpcVC@&%AB4&c=KtwzyJMkKpm_%!RMQ{|IgCN*4y22BeBmy$H&Yt{1lS;1~L40nbK4hiDyLd(Fod zru`}GPt9Z>s|4B94x){r({|(9oL?$mwVQN35}KhQkT*K1dL#|pYmhXvQDTn5(tur@ z$7U_~SY=oz2W`45U#U2&eG3BmFf+=T8kEly z9p`7rwXg6%(zX=NBV0x+6X~2@w0tC8CK;|=?Jfc0g$Cg`L!!A}W6MO+VFkH@^oPPP zSiizsYD*8sZv+TeJN0~P3Y7BCuIX{PKS`vMn9(P~-)Bm^Gxyw0FJ zxlP>TgzCujnpSE_$Q#a3-^t4!|4e?r4W*svD)8?txRFBV&_$PO?XnvTn^qiefmfl? z<3;xcYpdUJEfCPIAq~i+htrjn?~tVb?5q{(xh88wYPxkQJXTSMOL+g`hn+Y65xsOrR<%QKOOf|)iVndmcy_1w;p$iRTN+M5K}LS#s5ymm7(hc0imtR z)*txXyR2}tvIwZ}SI)ODCzoK2$Pc-R*fCK3&x4C!mLnR@HNg^Z%RCsJ51OoTo>d9D zE-dcPoy(n;|D7;pBh#w9=EuZyw~H|DFM^jG2N2;Yj`J25Vd0o@+sL5c@Sx9sC;j0O z$BDK#EBcWqQa4vpkom?Phm)hL-SL~7;?92?7c1{JPjg?k&VZZ7y|XYV8tH3XAO{99j7%EMDKS=mxh%ZpD z*Y4UaHDZUgdjCgv9w5qS^vke$X7D09=f^gZ8KK|21Xk5#Js+EzBKdDN)%m^MTwnMj z4_!|#zJ=FTaGl%n7yG=WN}SkN^g@Dgx6z9k`2YVKPMo=F^@m|7_7m6f%A^bYc!@9i zOkodg;f;aQ=vUUT$q6gwAs}xan#w4I-7y5Q6u-v6FJ2oXGy`eJwScv8qFiJ&NW~v z&y?WT|MEuR*(<#x%(UHk>AA^416CRRWEF|Dyg+VZrBn=4KfO*m&1@=~@uM*f7+(Iw zIXz7}(cG!#E8b_{h$VmSK(@c^R0&T%-XwEG6*a@eA}Uwe&S;ncv1+fba4KZqZHX!w zHwRyk%}~o)uVYK2HA(EuO-ziO%)mC)LePfpg~?a`O?ri^e*Dp}68*$sprm|#cvPgi zU>2W}6&x%!Ih~VXGhO%dw(i~2n&|obkI}Si?)^s<4}>&c9u@SXoX*~05p86qoJ<>{ z&#g_S;8*HJzw+#ov>}=#-IT{pY42EAoM43SVO0-Iip>n|z;2GL zjmK)~x$LSE{MpqRFYiiO>pH6<@QwXf^tOxhk%g)4j5*9_>^#f8*U$Frc$jic%Sk0_ z`E%N9Fo3=Yes5y+>p}T3@@dY)rHAyq^dY<=WJ9S*`qg7XfgT8)3l$(fKF=<&hx}Uh z&VztbY(?%!w)8uybkMfZp+IlGVxpg3oH_gLdoUk{xXX(Fn1b7|RaKV+l<4n{yUHjn z9Sd*6rRI^<7m0&Q&0An#-tqRae7H%Oeq=}pmHJaFTr>XqRj~Y+tG%a%1JPT2xW1ig zd$}Lp6>)y$7aVePCbNC~vv+69y}hYyE|fNTxM`=?DZ_{M#A`9{$-`=e!gs$zT3arC zIis_!=!>Tc-Ub4`ii%92*==GaDKmm8a%m3M4{IF0|K|ARXMUG&a>OG{6SWgEgq7W> zY904rz3+H+qaSI={MYy2dGqog>zLm`NyO66&3`@1*KfTeE^gj=FDoi4!?PNi(pg$v zs1wbJO&-a0Kr7E!J!P)b8Tp>+h0}*D0Um$3<70mLnOFTo{=7dxIp1<7Q$|vuVVp#>mY@u5j9QW zH&3`;GMdO}Mb1fS%3T|%@?allpYAQmWNI$OfL$&=dY%M?qxO+o^@KCOSqwko!PnIm z+tWpzRL7;gyNJ0a2|h`E)0fL9T)XA~aQt}j)!Kg!DOGZaxYN$Bw6)p}d5$Fg|E&)0 zS8fN6@fu-qqQvB63Ux)uzuj}cjxjml0<5V6PT4TI^|9zS+}*@H_41i+HLYi2dIL}% z74v;+7ym{S@G2v)FRcm=8Jz>&$(^NyNDJ1`6^I|aB@AXoRj?Sx^J4V>|rW36S)%{|>XL8S7FIrFmz zwY_((2Ajw>inzMj^~E_VZKpY+a@2C&-h?vzT%89Ps*Xtt)bmr;KC&Ovat;q|+!sJ* z%al@f3EYbPD?geGlNDQAE2ewLyf2QhGtAyf*yRxb*ls0LL|l$~O|^i^$!g8aorG%Q z`myXR)D-F@DP>O{D&b`8-(hj)EY6Gx#L#@50R%2MC{0M|Z}e0Y6UnR!G1lmTlw)8f zW#Ei-7$2t|4N%&gn;h(15Ol@pDsZIyg-il@J+;Q;6AMRaLP4BEjMd}>6EiaD4B5zq zG!vRL0U1>Kwf|AY$_cH-hHMnA9_z~J zAKr2FQA?Pi@nD75KCmb{-y8;0n0JXKuVrD;fxvI*#VldXMn_(g=4(5DP@!^K!5o z@o0W!y3P@Wvm<^NdAKV0v-ygUEOloC#Dv{H29+AQT#|kmrCC0hC?S`ESSwVKNJi0W zo0^UoW~JRqphbjReL%1RFhf;maU7fU{jmiF7|JmEvlRbzif}+WHN4j)A2z61rGR_y zaf(~Yr`GQur=kDg;?;{4{J|KF<`v6-1e;Wm`9}W|7rqS;R{=t|jiV7o`|wut!H*DvbxGpm}1GL@kCYH}hN^ z2!U}6(+B8Xk>L(+k?a-6JKCNd`H$e_J;=r;pr!Xntvb*Q+&Mo6|MUs2K zO7*-QJg4UEX$IZ3%e|Y8^NXyf-?ndEy5bKk-Eng>r`mf@x;p<{u165j8C;Y3zXO+1 z!;9q@hWs5zDu8X1=hy?dnlx!{Xur4$7osqF6CDx63BbXgb7eyuRI^|X?%%9o(|q+> z?S{T*NIx-u<&&kV-@cIzGx>q3Eml%)CtLB{yUU+;b9JSQnsMa@8$?q_z3t7<)1{8( z<&Nd|iyI?}$L~BV{sV7wzRkH(fk}*nE)U2O#5a;^*o+%C*su#6rV~2{A&W1K%ZF!h zz{KFBc&|_s3tN$aZto3F!S1)1Slwzg9zDuYTlL=3x$PE9!k7IAMxtCqd%6<->S}!? zXn{D8x{rl>e~Y+2*}h9m1Fipck&HZT_?$Gp9I4M4L0`O-_ff<7{*9~UV=yqHqk~pV zx&#sXh&Ka|cGR<9wBY+WNL=RD?X{be*93LgY!qbA=I?ae&2y06L@QZjZjRVsh$=O5 zyKZN`(|N5gwZcI(TXIz4CvnoI_3l`wcax}iXXX(%(Dn`gx{Zd0tc%{;krwe}i*U z=w7`WTrI@gnvL{Oycdj}csH0d8iS0#3nxEU*mwpte?F$ZC%=dfsv2yv27jp^IbBWX z(>c&%SYrkFc37D?5#)l2QT4^$4_tct7`BhK@#;GD8~)NCZi%ZtH&ZztT=iAyB^y>+ ziCz^*NZCi$7@Gl0D`$e)f1}N^&1Dz*I!<+`2UbbGyoOjfaN+GMaL0jbl)A+;NwJg0 zx+vGNAy;Z6MXuB&9@W7T7lg-X<-1DRH zmRm(GA)3yplH@q*+l}n*9F5WEE-Yh{oMYlm{Q%BaEv#7*Bvdu~2_5QzBGjTx769{- zCl(o^VRE_H-+0?J*^-~D;-yF878`Bgr0*XUn0H-%@_+gZjGHK=2=73n$Ihl6D=2vQ z?s+&p?o%UOFbUk^sjDmfZV*&Z8e*d93jfINb7F;id&L^s=KAoeM#n>~ay5ALLc5-z zV`UT;@(ZATg5VkIwc|Tu>TV2{)CSxUBjVKkSWi!u7MTJ+dnj1Ba}PMArDL4$cYIVS zVtRs0kWPm=sF{Ce5OYFffB2PPA%XYaR@})qhako8KRQ##GQt_Lgc8+>(9+JQD)2RS zi!6ieQP7H_v!e;mZ|!zQ_6~Nii#+JvFuNYbDOU!oXYw}^gWZlQYR_y&AL4}sZCe_@>E1d=u-<}sD83lnV6d(f< zqIkyYE$HOQ*xAaMl~8iGqMXE)Ar$iMV+yPQNhyTMW%}J`pu2ap`8|-|=j`eR*sw2J ztzhMik`+N;zyc7u@8sb6<2&E%sAx|Zc6w#SV;e31qPKVV(MtK4M|8zlT{Q4hT?6|c ztE{pKX2C&G!P(QoR_V;dd4y8>N0q+H@dO@!o1T_{4l8&d;isa26a(i6Alz&;kB5DoA@RPk&!&K|L*bg&K&hX%W zXnBY0j~}%?&)ts5{)u`hGf6~rSb98#o0s|}*>|u!7d3d3J#KqJH8yvWIPRF@xQ#>dmMmQ$h8?U}@QzA~)eh-w^^I|s z2j}No*LW9hCA{mRnM`l{Q!eC|GS@*HiH(Zh_5ui_qj#8#kX5g^@CYae|5*ot08{> z6x5IUOqU)-1VB#{`Y_xNC$Im~t~d#K2PkbxQns0K=%U6l@tb^7!s(!d+{yC15-@9- z`!}kaV&QosEP0i;w1XJnOhpsvTQ9AHlN2_?7Pz6BK&4D<13O@?x(qu?zpudGZuxjR zhagws0&{A{nI#VOCDdb#IJ3Tlbpj)R}#H zg1xoaEDnUM(q5YyQZNosDn0L?Fx@D)v(oL(htp8q2RN?0>b$4@yvK8t)Ie^*(%Vel zDPa)Ps&jkBpK9+;;Nt}Lx3o|A=mE6DO4hM(Gc%^FTM{xYxR?Uw$w4!v+kmqxPza&`+3*Yj@#)OdImY(cmGu2=|LmCW&eX`C}HPd zc{)n@sQR3XJmHFZhXV6uaj@H@sD(V+d-{HJLC`xI%6h-|3a)FN6V;DgSR|a3aS`&u zDZKf@W!g`>YG1s>V*T*W^}Q^h-z2w57kLLI5LK7j1s4KUAOCf`Xrc2TlqzTuCY87-m<{w_|G!^D zBr1Pi?bT)Q@L6aK1JNx=zE$S?h03Klu*j_|Jzsi&Sl?N6n3epf=7>v@z-q(3ByV1P zK8Iug{Hc?y&5OX^gSsXnld8?+ch)G#46rsjm4{|uRx_k5okSUmQMWxKJ*V5U2+zrN zox|Lx(xyai>y|3AmQ8D>^)e*XC-x`6uDXlj5Ld{<4*Pg)TS%TF*INmmOPW7n$@H`L^1GQzfe8 zBvFd<5uC=>Q7=+Gy|sRVMMek+aU7JQKskgnqE%@Wq&s)rlAuW&irb}fe%em)5iJb< z^ehL^KbHJF?-=&CzWBZp_9yqv+b+}baa%9^NrT(+`)`YUs?+{NstC~(63S;k zDUk_uoF``Rf?BFMS4rI>!O1?t3>&_+X&3^CkcC|+Y}rjsXc7cUdsLZGd?Jx@;Kx1| z;3*YDMkgA^@=jSsc67`H14lvk=J>-pcC)>(HghK@f&YGA-|T+tI)~4{cw0)pI2hCJ z7VDi~jT~oQsgMWS4a=vzMxJM=iUBu@GprbiF5?|AGoxhyotI&0smW>gk_`4M{ik;t~TAp98Gq2yL zJ}qtQ_+(RDL`*tT8Dm8Eg9gPbQkO{Mzs<xs*Ow(~ARSfjy#^zhU?@H~wpgKM@YQPr)3q;9}C*Zo^Y;3Lv7wq#gCVaZe}w zIq%>U7_lvSiX=j^I==#-{dv$ebS5tz_q&6)ILmd*3YUK=o1*TKY+hp?SLv$8Y9Edh zxXn1lrDmvCtXe86E{!K>SgiljmI`jQ8YqAUbo{}iu?Tkt@WS3ppnJz@)0G*|73 zDjeYVAfEj9ToYmj6?8{x>@UFDYGp-CfPMCPfiHie`Mj?4z>20r)O{!lD=XGuZUD_; zsEmh$Ou@-Ir)#3+4(a?8z*H+apt*bAX84~Xf{vbe^n#63s}X=eerAH2K_l%~=_`Rh zdkgqtU4N{@GaVp#4q_v!rs1FuhsN-Hf3FJY!&_`@)dUUBm;J5@Muq-PqKwor!iqu1 zQcsPfUJbAS^yBr|n3S398Q(!dj2P1!kSSu)Se+~Ar$E9*e7%{4>|w+x|IeT7UngXL{`NdAQCz-!eY=r z%yzGrY+Es8o^X~rO*BKPFf{g;dQ`;&#Q_v8?l`f=u#*^{K>CbH1Ei93idG_=w=agC zBYHU-oC3Xj`P0|yK64-wZ1vueFkB;&&-Q0CXD8f%Oob|ygen*`E>`RLC%7NJj&GCBWEIU83qrDO859akDiUAT#P@tgfi!OioC@ z$`q&&I|MJAeU2IK`mHjmUVYRg8hp9_3H0vIGhhdk>l66|H106~A#^HYZ8#y$Z_zt!#0U@^4u4yQ)X)cElu{kd>X(|a%)G^4 z^QaTtp0SkXdCma~6T*Y`EPYR!$}2@e^`H)hSvMuQmAYehEMlguO{@apkX>AJg)zh|1%Dm1Aayxrg;j|6Hv~J2 znujk{&!ns>Z9X*C!fNc`kiCV$=2w;;-3H-Qboq^-ibswPFU$V4V&MZo%2z`&p&@O= zX>?`vI{uUkzmtu>&dG>8Q;)0>&&A1 z#K2~EPwyd?PT#pa)o<~%Mti5;s@+5+3nK_K7H~D4m+CJe&Nd$|ox7IyKCq98&(DO1 z3NJHczyXYxAHn7)aPpp_pp=hMnDC#JzxiyEC-`ZT8~fNdpC%OI7NjHo`hO7^VyE+K*DJA`Ld!$k|rd;xEOqNAeqA^Wf#^ zek58?dq>Bg-tf^jOkr~kOAM~Fx%y&<4}y@$t#X-n{HOM!*k39P(zjEMrSpImK_(^@ z+V0q`8Tk}kFefA%Gs&&PuK|)e0g-SvA=qV7Z%wLEyD%XD;7P$ePB8CR&(Yf7Ka2?C zfX1cfrdE8oLbn||8I|Hjzz3K|zp5U*2SG?@cT_mK(1XUn(V4QPI`SDRUpRA>rt=s; z9y$57rdXIsj*p>1PAP(&yGHu0d20si9W7}2k89E$z~`a9g0?7>2Fp`(Q4)2*;VSA| zcW}Lqs+<1-g(b$;5soKYUnEQTpFYlx-F_BjfLs~h^7FgN-og<6Yym$F$7NO-!N%Pv zXJ;`&H?ge9;fIX`9%Bu;G{=ttBO_Jp9!?66Da1?Yo9zYE-aDBoxvqRs;td^m{sktQ zh?UPkirVk;t10Pmbkai@53x}4r29dDw@foq01=XZ5c+Di=x-z%lA5fz1WLvqq7mAk zhOqoP^Dn#rY>lG1%^`1^VX$gK5e9>eYgLW)t$AmGXi?uADBb0)snvURJwh$`Dv~_< z&U=)P=lwpm5}hdqi6axA(Hh%IJt+Z_0KgcvL&?henY_>WQ~v^}k9N^Lu+bYceO{9o zeyJnQi(HjpF9|hOGjpUF)K4@2dCxxO5%cec-hWbDU(w$h3Wt&d+39Ae?mGXocoEf% zV9mxKYL{#H^9KRA@A7EppBB-g#zSuCO5d-jZ-EE1^^800f$d+S&?C4AyBpVdy6Qo^V>F{ z3}r|sI4wSrB>7(3DRniSK@+=tvfALJzdyQ;?$1PqVJEQKs>9LZb+8b;620GHPN=cT0fa zU;z~sEmSmkQ<7bdBUTYE#oyiVD+WH-jlXLM<-A3=H$SGD&7U0epW*q@V{AWUepu$E zl|{*S7>j3OMMgj7t403gft<^Sk%Q~)?VXkM<*>i- z^EN=t;G78*NXxqu(Ghgk+s*idMkLWI-2AV3b~fz|^ic(}+q!zD3T4jac!KD!P9B&3 zD5^PsvJb2&G*lncykf(Dh&=Y#o@;smp?CZDTZ-6SOho9lZ{F3n?I5SKwyF?7zy(yr zlg&0|&8A@%&HBZik6-x>gzKyoDJv_&shGy)&>{=58N{<@wQlc6jn03DSfmR>=C}wp z@jCvCes+w1T+i=9Lx{h;wrW2*8|Ox-!UFKlU211&e7R&Pa4u7ON-6B@OXOiDk?FT= z!`&PTOPTM(9~?DQj?d@jvf(@TdT-W8=m5H@;mdF*HBNjur*Xq5do!MaWj=r)w*lJl z<*aWc`bVakcUqLqfMGVLbl|;?S1(KfFaIt6U%fwXJNDFx3=anxvNSEVSEm}nR3GAf zJ9fwQbE_~Q__}YGt(58HqXEc7YokdzwvAXwv<2Q!z+TnaG6dTRx)a1xFRyPdLRxJu zXTTMH(EflndV> zT3qxV6RT0M@09M#i72F%M`ZORV%}_{T&3SUWWBJiE@gZk(CU48?RzhKl?~H+ywrYf z?hP#cASDHRC;Gh&<%eVFa{{gr#^eCWwBe3m0W3PR$}C9l)}N>!tE+7ATcBaIm`ENP z|EZ%BJvZ~xfI>--Y3GuNyJzDspL-xlSV17Lc;A-s;W??o>~OtQSC=1m+n(%yD0}i> z9MDjP@#gScJEnPtRq1uJWUqKC-chgEzp2xEQGtA0ps9Moy`#B_B^_Tj>^Z+#u!eO)Y1(H#@Q=c9R{x-fU?gFt z>KW7n@fHjV&`tUdWK2=c@z+={HL}PEX3%O94UnZV?%}v3?tNd{24B%3tWosuqw{XP zRyT2$E>DPgQliFEv1UdKKzyy|E2tjM`SwEEw?1ycp8Cy7qqUuY)a?xEaXGUwhso@{ zR{r%;X~*(<7zH4qSNneVrRWHK9+2WcBg6nbd^9_t-#=vAlhT#hnc}HL^n|loZG?Cq zsn!g^Xa;46QZ>@bYN@b9l&Yqhqn+RM0~e>lW$X*)O`|>EOa>=w+rm-&^$S8DvO*xA z6F+G#FU84HIEyCTsn*sX1deFPb_xfZ8s4pa|jZ;_oh-2XfZz^n=#xVVJBK2h|3t(8At3~935R8Vo*OAnGkLj zKv?g9_#>AwxdzYFOAo<(uMfptpCUt)HV)QD?pk_FOiIfIj`Haj2 z=06e!!vuP-()^GhC$nw%Z0ITxh2gwR(DAnA+F88JAK$aJQQ|-C99?Zu>dK$Q(Le;T zy;d_*icL$gZVFV!)~AIi{DFbJ^N^3Hc=9ze39Di9C>#6Gn0w3LWV(v)mFVjAE1!@(c|9a8R}#VzXy#Qe>#H%JA_?kdfR$QLC>$QVzAb1FGBZ5132$Kr-KX7-&G|zehQ5U zX|q`%;`j)1dBl&r8bM_7fNX7TCBGGZ$GRu==`d84los8wavU2H!25Eh-YQxlOw`Cl z+0_13f;Kk4X#JBN*xtdl>N_h*@+fcXMXNzNHcBL1feS*xZUe`fK}`@ImJq^fg!1nA z_;~C9z06Pgmr-PFzqp$%on^LXl2DSlBilP8&RsQZb6Y4ftqSMM{G|}EagAh_CVUqx zc6#kFitVjT1yxnmrTBJ-vuxp-fnK=PEPD!1;n?l&r;0k!z0UqK@1K_C0rH(kR9P5wT5ApWb6aLcN31Bj%O@B~e@w}g3 zdZWK7OeeLbX$O&?n%}6#=K=xMH-NbK=bCWUw0ej9r6px*{G#cflawXu;~2`;fE!ff z;++u{;w~F-=6)zYd$}mqCnv|qPi`zc+hHtr+jomLP}r55ccuL_8E;@cnw|A zI6*F1ILAH#S8uVA^W8GC!VFe^qg_iB&0`lNyH9y^AzVxqrbmFaW5lJ#vF5lokBW!C$m|~W4ncgJzd{S(Jd;nY`BYsbnz4`ufCM!^O4@b$Qd_w zJO9KY;XBOQho%^JWR3$1*|ZO9&PYKf96-es%S#?JJnZBs`kr|vyG)hPa(fK=^ozy; z)f*Q>0kQui?-xgV8q7Qx%of!V5k$?%K}2bM&thb2tMX>&E@Dbj*VnDf%gy3*nTf3v z&($$MSAcChqkK8fW89~Q1iEJgDDlEE0A$Z03ccm?Tihh&^xyy+5OD zfr>0pH`~c6<0K4V2OJQr!@$8;I?S(ydUoQMI7!xOIU0ztN}z#q0tF%I=*XY`r|3g3 z@es0~qMzB48-6%@_LNkU3>;M37mSL^Nr9eghm`J`e-^WH6KHS8v#Z3t)`iG2%+-Gq zpwyk7$Yg=a29aA0{iyG~|D#RxFyP}XiHdEfx}$hi;szYa^|%aF=mo zX;O?8TGlc^S~2><80MvMk=m6lZ9N{*N$ZtK`yVqcMu~c#FaqyMJ*Ajpi4h}ENpZ%@iltLYJ%vksxPT@k|9U;8$$KIWY zmEn6%va-o!ALw~BvlMgLeN&CBH<&bm0Z2{ges)H+ztb>_i7}lhbwV&_Mf-$Mk4V8iGCld|V#pA?WvWz6N#b=_sb#D(lG zVXCPpeNw0*b{Mz-gr}|sMf_V?Uq3!xU(fCOH_GNeu;b&^Kex8Eb+KrBEzq@;ECN=J zJU={NhnkHNQs3IXv>z3g;oFeAuUWFo#WgB#2-Pu>WhaA(vP$$yL`KsDWCPAnrrJPn*TK~83of`kNjKtr#=^Xwz7V185t3h zBD#&^D^D5GsdB%Qo1JVR>M+c6@k|q)HJAt0Q7%Y;Bxs8=dC@x0;P^U(7MeXRD~hp! zf^an|9lGeG$efwcHzZrh$OcP5G6-JQ%=(EC+Y1u}PFVUEuLmco8}H^j(_{DLQZ9E` zgBQFfxs~=Z%AxFy{Kqpy%Lg7UCG{^7dvj!3=QOfQe(HqSY`N?Jq+`6Rp59I{di^{q zDrV-NluFc5IWJ8bg{ITJc*I9kO^95-&xcDnL{t1t?dYoEUGMIZq))Feed`r#jxA3+ zJo|O{8>89B;pO_NqJRzTKv8T?|>uo;*7 z=GlBBX-%6;O6y+!gIra@&h>Tp@~O|`#=k)f6ZpH_?qY-~{n3%I`TyF$Uy07=B%_he z9q~ird=Qm@Y9J;od?Z_FKwkpQw1YlvId_$HxF31B{r2&|OhK*rWCfOg!M=QwG)3 zUmcs>21yKCJKN23kQlO(!>3a*)3X|CWiy z-{Q7>eTXX#Fanc#$^X2(X-?c~nl?f2btN?8!8X>87T^~Jk_XRH10PM2R6%-N<>9x} zYGraBjnz*HCX<)WPOqGNmos#Fdkc_-uCJ#{9EyIzMvCecTg z<|O-jJX-bzeGu!63V;9eDlfaqH1J1vKnxJ~UOV6?dkR;gT3f~bQC8l@gs1Mc@j`LR zab;8SuhxkmwH?Xi+ht#nMmw(?p7(n=s!$-yv%*>xtR@ycQapSi z(cF6&Pes8i1)p>m%XDco0%;)ACL~mlD|E?M*0`*Er6{D8p@1|^4n%zM`4GO0dB%l3 zZ|YN)HbA^f?%ModW2?z9IiNq}=2>EGPu3hZo2^iH=+K^|?Mb3R({w9x-%QCdV)tS3 zqjNc-LG)i^-|{*2mw z1xS*v^rPAkf|O0J7x}8q%bGKw^ETCqSfNa{%q#fpHQpeQvRCdFXfJo$HS&`I@(tLM4j2a2S}C zN?VV~WMCtB$wiaVzbR3^;`6zE`y7Gk=|p6?i&;8E$4P zTrm=s@tw^OOuq9Y!H9R9YKf4}0|UtMsOis|IXeKg`4?vOAGhz0lBw;H7-}t^-hRkw zSFngTc#?8yDH0HQNyUmAy31`#ZEGXhJRW&*tT49fYvujeCyE3Z5*$7R%Fu5_Mx@02 zIDSjCn2#j49@R%)Uw9_H@%Ng~dG^%Dvz#g@s?E6CsJ!S}0|1*BW!mC=q%J|WVkxp- zcdvr_zqfp6oh*~QI^4>ifGgJrChw+10XHq&<&XHaKHP)+`gQ4?YqY$)to-Ry-u6LI z%ro5j@%k|E?aRl+416OiLSdRA?A>}uJ7DtqnluJmZ}Rj#!NE9?o@&9X*@BLzBm8pC zeO~I@8%Kg(YuubWdK3h1&!b~H;4DnQ{JIH3W^v9c?VfJ$ki+x%6#c4=?Lp;#6=S}|V^sS+!c4;LwS%6I7p{C777GvD0B;m50*N^+}AEC1MIQ^6nGS=Ma*`-WyL2jt^f6AL6H zrNL5=9;C*uN|eScB$s`KDmo8?WMI@sGWk1G_&BIm)MQlNd9dEVUG60Catj%iz+&!9 zZ0Thq%bT49=Xx(PJ^Pz==EwlZbKiie`b2G{6ah~&TMz@Sv?KKWR&oO0e!6)>EzU3HD&Gsx*8N9=Jh25m3bX3`i_??kxM!o*D2DRNZr$tz zg0u?{LuUC?cHl>TOR*jjk}sp2iZVfnxPwpcZwM;pe1Dh&&W6=Oe!!;%n3%ps9%d<_;OwcYd8w8f%;37Fml$ia**+r_c&srJvH<_ zrCH8sN|9xeP>-l$4InBD;eY63`RH}f&-^r`@f&>tNYNwU+ZLjUbq-^G;B&^Pv=J#=uNW%|ImKkA1FK}x7K@W~POqq6 ztVma)Zt>_o6nFTEMT*s<>t(NrT+F{y;%O)ndEI%LUFP=gCTl>M`2l_J1GgD z6frgJrZ8@hNQY1^gFz`&r)uxHQ{|}f{Ro7oIJ+(O2xfBe3 z%R`Ep_2n#Qe%xX8p7a;9hKBgAz1OD)upvXbf1P7=l>+7DI`1(5`5S4Gr88bP00$U3 ziJbbGBnoNKd30hdkfe_1Yj8n%&I}5RXr*GnjQs)_ONdjuUbxObQg^Aiu1k&h{jhgV za(>fxdAYMN@~m%9dEwEMqxns#$FW|2cdl;^SIFA;1nYBD>)7>N}WF*5N}O< zK_}Ur)M!E?Kkh%@Ou~bBY&tCz!yE3$ne%Bg=|x{13%{0O(0pDyDXyxfu93vaUe$!H z5%!epEjY?tRx^v(KN)tzs1)$GP?GON|gBpg7-vnk4u0 zm46hvLmeH$_+hjn7!t#M@|}1J{&B1dzGJ-Tm^9W8c{m`9D{XBhBGVnc{*h`@r&n0u z8a(q`M?ojyp_eB+e>XqO&q#)Xr~mpU`4^;Ka^jsmqGd0`$0pT2{h8k$5exE3xV*kz zzTW9-pH3t(>JIK=8sfC@DD)?LdXb>rEjtb4`*VEk+}6?2fkS!(s#M`Bp0v&L@bcJ2 zTUe~SW5Ww5_Uo*tk2vF@%oRGFZPs2fzp(Cxw9~QAUgh5Xuo97y!1XO$J-V~skn;ZS z-d;aM!>i5v^4Kq&^Dgg0?p+(GmM67O`{L5_9+5Y1SAlEP5Fi({Gu;djlpkO9YYk!p zhu`dcgH^6wX-spcHse=)+dBwy-1tzOr^htk0NTsM-xL!>;#Xa+1qaKptdN-A$4$>4 zhZNnVKdSy9#XYyQdy4l}Aht6DfAys*7o4u%+}J=M{c+PK3uN7BP+{XL>=Z3;DaKwW z3CZGfv3{Ngss!e9@M`k07Hzs(zO7R~`(;h*sQIxGL=#u&Vmj&Rq4Nvd=!tat^OJ(Q zoLNvBTv(r_I=D;4MHD91tsm5~J}q(M@OqnSHANDQX>-E`&d*)@;>*k=cWfnLZytf_ z-!lcqUP}Ahvp*tPQP2Y*0H^5s(pGz}STVm_TccOLmu8TdDg zZHw7>V+Ivw-j{STKDUlYZb2U3XXVKVpp&HZFYLc4$IUFxlR84R3+u{U*Bmb|j0nr~; z>e?&MgOYBJoJbvLby-Mb0KF@6U@at_^R2X1`xpV+%B)lQXqPl$-n=nt2Aj46C(H6b zV&|s`$C~A87SKasy(ttCa>>b{W*+h6v(C z7j(#2jxbj1rvAUbXkJIdFI*PSuA(Kqlr{<8sUr=XB(slQL#P=*{u`soK9ngXE-f3h zgE|K+IHz8gNVMwtes?#sq)YY|V(=H29RD4Sro)M52!-k)jwk0XivpQAuU1@Ia1=ud zHcw!Rsj&eGt+pH5#apF+UG&rNo^&8}BIz~Vs1L1Bba6xfMp2>GJr?E{ljJ0|0mcAQj^S2L zpJtza&OoO3_vmwNg`NlMb~?N^wE$kFQGFqt>e z{vSu@9?$gS{qdPgbKj7Vn2|fpHIvIQxnG(~Zn;Oub>tR}CKF-E{gzxlbI)CJmrI7+ z6GAj{%UweK_Wk|2|Mt)O@jmZ!Ua#jf6}|Y{x>#53Nc&*3384b|+7xpg{H0;rXAy}y zn3OMV`U<*NhJG_RhY$|GRh%~1Z1$XXzG-Yu&RGmSOp_e`;{5UktT@fjUE}tL$FqoI zrKI^`14RlO`Lj>nl{X=;OQh`)r5#mMtXY*MLbz*!dp19L^q3BRL?%_Fwi-C}qiF zsR*p8N{GbQo_nI>bxn`z+1L8rNAy!VRM%A!>t{4Qw&Meht~w2kMUFz^lmIsK?h~Zm z>B<0w;8~>zxC_xpH}O!WKcuxC&~<)(S?4`FY>Yn_R=f?rzWyBaZ?8>#yYbI9-{HE$ z->Goc>y7EY4r-@5?%&}{O)rxdY#vux|6X6}$i2+noR5T5C#~vk`P(=yx_eV56z9nW zn>)9~8%;mY-f{pJHtOp-bo`}TM*r97?Q1cRi7A!f={=xk!Qsc7Mx@r?l>0PVn0eX8 z;|DIWU3HI7g&L<|zDphX>#g45oJ2|OY5e}qWrgv`yOAbelB-XTu5S86?yD3cf#Lv{ zi%>ElGbY4XyKgmw5;o$G{7pq5Z#SBQxLk@wG^_%ydz3@QH`DV)Exgga=m$5>J4~)4 z6#lD%exE;|&lT@!e9dV~Y&`ut@7pOex${KZdD%SNMm_1|&*mFYrs4Xa(jXo!#9Zpa zL<22W8$DE^JSiGXE6##fA~#nC_Mv^=zNIrZL8k+&`D-t#6ovbB+oH8eApX^brgq7Q zy!DwQl`+6lvw<>banZ=E$!P_XXqN;oN|Rl8b(Ld`uf9R04_PUiSWNu1WRE72PiF4E zh2{kKDJwJxD0h1S1 zE>U7}hkAC+Azk7cHzw}CCA{?#L``R%K2f{?thlAoMQCCNB~5Thp&NObyzF)cr;?Z3CY4TSY6@RgxVGP0XqT1N;MQ)85ogpsm5j&AW+kgp! z{g3xhw(Mb8kh5ON``B3Ol0**V0J3O$hh_?fG1i0$>DtF~%dw-4bq}fRwM^}Tj2SSN zmf0mwr8u7mIc!e z<&9Ihwq=@khBrV3mBxZF z#UP+91eY+>*CU{jPyyOZDcy%a|5=+pRY2hA53Zpx5$R=)7iBzxGI~*7zONZ{PaW-F z@}OvJo(BL(d^R`>YG`-_W4uC@$d{^b!q~lto^Ecj2D+}a)k)B1K%BZst%^n$_rz~vRB02rGZR34 zY&6;O4p_9;Zl`9Di~|H_`OVsRZLy~pAsP%tydUxoiI$#LxN=6_ZcYy(MoYY8rP3I2 zG-mZZTUhbj6*Ia4LJ2jlDT=|y3UC{31F5SCrmaA1wtx(du*W5sb7Syd2mneC!SHLX zf7rc0jUa>5{p#TgH&bjR9Dxi5kb|z({geIPSmGyC)ozy9iYMv!?6czx%CxQ)8KQuH zc3$(3vxASKKM=WNo;0Aut8$lr*8AKq{HFUP6{pr%(5c3*3>08n;j#%=U}g%4Z+eYj z9x6@;xw6UKTtV`h#c&f&E+@k8TjVGYaJ;d}2J4urw>k5>?^gO}CpF&=x4uZ}j&-{k(Tz=`!Rzu)`7(jk?g^;z)RWUTTc-veXo%*H4AP1MH;HH$p6Fz zBmV6DJU-bh#jsdx)-N@AwTy&vzG>PChtPfA|IPaBwAmr@TwwvXT;~ng`?0S6pn2;X z4}`UtvKx|eze2UQmEl0G*}_4%tXoX#ry{BWF|z&lZhkbiZ|bKRAj9{1?NT?^>p=p0{<-PF?BA8b z#8QI)hMo{77v-p`(gU7_7bs>T9-6xt!nhs?7N!C5Zkt&WChxqb1>vH%wK-f%`?kxg znru9u3`4+VZVR)vDWKx2ql$tZ$QyTs~T*@)M5q-n{zX@s_V zDpoH5m}*D{yg7-&m4706;5o>J%ZAjTTWB_=ahs9$z(1dxBF2bSt@fK8n$Z?F?8@N~ zHbVuJqf>4@zTqpHMJ2X;v&kX<+%$7j`3imA@aoPkdH&%wLLi*|^Gwsx0nu9hocB4N zTTfp@SO`r$J&OK3?#&#79t0~0|*fQ_+BG~n3dpxdaNw_YGOpZ zrcNz~3KDQX_?98WJ}bAYH+hI;)QAs>79pjSJL~Eua%FZvtmL_{Q_#;-(lEY$Jc=Iv zDkO0S7AV$;pA1TNsX=uXcRbyE)ad&8erc$6rk%b{$M^R1+$bT9K}DLT4hS2C)fUzaBR-~EcN0B>aNp#61rv- z6V|~2amr6R{_~y1OT)>nk2`p?sq)m|bLXE|^0DCcU_0r=*KUq|G#w~Qt|+n8=woYR zsed*f&JGA|?!!@H)&_UEaAi!A{KYC$mR=GYyRG&~#q(-A^ML5wy-@}{HPsr{2(E>DiYSZcM>GmC7m1{R8MN4owX+SQn z&*$BJ$XtE{xdJUnMlv)`4bYYH1}h&Hiye+UtgSDoQa~}70m_54x>JPXq{BfSh)l9(CV8Wzk;AJ<4v{`ZNj4hU&QBO(sc^bzaxS>5kE&a;)aOxE15u9G zb42zgeMBxkng+aODr#!*(E0Y(YJwjgp2Kf%FP%|UupX*@zK`d+H%xR$Jina%_nTF7 zJk!+uCSAReZtUgJpVUzVIYix9%pw!qTUx*8@Ne4T{dwk}zmc~}LcZimOEy{{!*bv5 zcr&#xu_Bk67LB^2lwt*vSMvt^Z)r2s!|C7pX+?W`DGIY?KH>c8DS!(jhCXJ~Br-p_ zFW20%eI2;Cv`dk+Syo&V#8JlekKv!aE0D`hRlC;r6Tgks&rVP>DVLXvq*~Vrfe{(} ze;hK$py!TvvoRd$HWkJZ3%|}E#&^}9MlAW%)L;T(p61QowFpRtrMmbVC2VgIoAhL|1T^l!uaNHWJ(iP5rWFRf0+<)E@C_8;2 ztlFZZVs$L43$qet~>3WB8BTzgf~Y598K?1u~~-*FO%u& zrky(s9}?(R0O@J=D4*Y1hpaeF04|)P6sw1d!anMAQe;SZt}Yb|sZa%Iv5wX)60AqS zi;}Xs`2i78kQK>>G+nSJ&8|}_pzA6OEXeLX%b>81F=#ue>~Z#xX7h^%%nkI^S0}wb zIZTszxWejnH!GLbe$>}gfyK{os|&4Kysc&sy%sEpWO1F63m2g6&*J>}^9lY}$#l84 z*}V_wsKtnBuZ`XOZuC~sm8K&)$@`vAnv(Ww6OoTlH=%G|nEyA453l2bn4B1_(r(_J zLQEWOl!67u-peu8OAZj=yoZ*+evqy94Nc_;s{K2eSJP#vZ{*%rv+4M&i`&{-oxp43 za&Apl*0{N;Ms}*+T+Q=xN^YGUy$7{%$)I7~tm< zpW>mN`!Rxn$IqId7dz3B&LsTTegi&VFqxp*s`ZGAFkEvar-dTtLa4gy4g`T!wVH$0 z&sg4fFLob9{jEP#sE|mid~d1VPr{6y3u^gV#p%9q~@ zn`CKws_P9qB+jF)L8c!u3bSx(4X3A%3Fr6m5x zVCO%y%lrix*~KXQgU0|aQAqD%+1DabK&J6+qa;p&X-i6yPdcB6uH~x7vZUX%Um$9%*L2`{QQZFDoYG-AouHY&z}vmM!lPB~Ui~RM6vZI?M6)GL zQ&&j-&vttm=ggGm_Yhu}#7~A+9g&wK51NPVixYkwy{Up>=a2TrllC5I>k}I}Ecn;; zB%`-|bX{FcHMxRVl+OLryahS60L>sl2&*5fG@Fl5O88oW{zy0{fva0JS);{OG0R;= z6ifNw4c1L$pfSvAeH@DiR79U~!+=AwbkSYK4==*)6D1-rS#1mAX5zf1!acU!8kr-{ zxy>WIB~);D#0&=`HE=2+^EtF@azrPAWw|=b&1D(iM~X1EpddeFK{QMokk&J>cZT)%%!?f9^xT+>YsnF`QbwV;>_3`KLWBiT z_l&dh@MsgZwHp-gl={NkY&Qe2kWjv_4N4|cRDyh*WOz4ilgvdLeDDj2oNM{JjY2TEj}ek8OORQg zv5CAiw{jx$hdMS<)X`DqyJ6+K0j-2EEmIV?TKteM-jmeq`){`M_qBgVHGktB{#~j!ZP86~B?HYz%4WPV zjc|UK8OzDW4^NsPXzj8bFDSbn4Oucau8ESDWAaPTkhGiY=@ED!iYhK0ShYX7_8E?0 zL-ri1WWIw7(!E?5Q46OyD?6VCod=PUp6#~{Zyj%)H%ojR;8)ACrBFW1fsUAXeDM}O zdCpOs%(AO&+)S+26xAFE)hR}Zs0+B@@Z9<>J~sjn)6ChDJ+h_1U@4|JoIVO<5Up>1 zRkiqKHoMaS%(G&*R9TfY6t2#l>>t#F+z$yqzi9D|VVK-OGbr?P4ebB@`(uA+YbR{S z{Z~@8BBap!Rn`k42g_~e7jTIDq|B8J%WPhUUv5z`>%3VD+9fJ{?WZziRG;N-*99B7 z$?@3D1)H0F?;Gd;KKEoj4bA^i*IczIow2$fxseRJeqUqFaztKEkx~VkdXleGb*ysN zJIL$!X`m2~OJqp<$+xmZZ(m;Un-Vbc>E*%MY3DydNa~mK48a8lpJwrovz-YTds59zi5&kc8mJEw&B%hQ-+3YR^aM{XfusBO3TPRe1{J zy10Co4!b+9C|vwT5Oy-0ul3z~K7BWJFS2uth`2*ZFruw;*Qv05Q^#F}-fh4xF6w`| zV_SeP10Xo3b5YfU(o(gMbQQGkDMJfnd zdR0Nh!Nc~@ig;&CK+wHCuj1DflWC%69Gcd+uT&hiB7nmpnYb@Sj2u@iVRK+ABub;T zlBLvdc1@#ZmkJEw>W)wp008=hx}ZQAw+F+dl!oET6F6F!VprHa)vd3IO6s@5!z9u8 zs011}DrFiCA*0^ANKWIlXnGLPt}qn}Vs*y>BuJjmv!YCms7z7G1kjX8$RBMlK=t~ zhF@EcIt!qWqlRg}SA_4a^ zK1mtVB-^UZK)Sp5y8B$Dy{-F~OM;`>Flim`xZivT!y%r-1cr57Ll2uCZB<|F-r$0C zo_=>N-@wJZ)1P)d-2-MsAHyrbz<8vsWF%_LP#z{%->P4v`MuWl{?EIER~)$;Gi;yj zD9^H=;o8ZW>adg#Id{)OoTKwAWK2X%T{p&=+Sm7eme0iAjqZW%kuQiiG@eVury`U1UY$1NJ` zg>;3~rR4_#)N`~yjb%Pc^(Oi3YX9CSyYnA0M(?TuH87|x;(e3J{g%O{zr9PB^__p8 zMV=ow|KwqDD}2Nu5JQEWUeM~siM5`AOH`1T^c*~ zri6p|9pdr`Fl9L+Oy3%tqv%#l`*ZN#r|tS_so2Z;CPuw{0jkJR#8@0;c9?9I!{PI4 zu=4aA!L{1bd7jHu>wERa^Y4?a{1`p1H;^b4Y5v>E=|DJ=B%@!24|(=16>arJYaL-g zuzZ5~A9MFosrgB0dpiJDfAQzceMjp4H_9b>qZ%ti{_Q*&v|E)fetJINalcuN=6nY0 zmuP(}2bA=U`r^kEIjLIt*rb zDBa&7yx7kJtrS7L*^t3)5Eek#nm=#0@$`~PBCIN@eDKZ*x04QsL_Yha{i(^>;Bt(D@;Yzyr0K>c5&lhIRVw|goVQC8P6ac)lrpA!J;Pb#b7ce zu5N*+BDzjBN5zyJxFxVhNoOzuD0gX0Kc%MDNLId2#r|lF#Nt-!@?SV8gX8r5b!ED} zT%u{VNa;pN&&lPus4-&D+3+7=)1hzr6(v;{ZbdgH`VxJU({LHX2> z9IF7_a$gMIa23y*d?bVu>gyxR4cAIqaZ^EZp~2L<`T5MjE{=VJK7*u~EV&0FA1D)f zXPTRmf)^|q&gAEcT$ACb6e%|tNiADpaU0DnDtc|115Q-HFbl#!uK+Or+d}rXj5PL> zw^O6X*ckDHPc7Y8O{6#lh_(c_m4xm1crK3r$W(Zmo2`R$7vQecCk5Kt{m{>5_bPcv z+nffOteFEruk{o7ASPPTZcqGaiaF&~3vf%vv?wkR3uKclY)5*aZ{)#wRRe2>F0{D(po9Z1j_FlPAt<`~Ap zj7Hw*e!1LGKqD>oj=+y^!cdjvw)~N)E)6hd0a2F4L!m+zm`4k^H8iPp%EFfIXj|P6 zUf`Zzsa?jzL3e%9S^TFwgf<|=`k}us0NfdTf}B*T^<5@(S&n?j4IVM%7%h9T)pG!6 zrSNJUdwT<4NA4lnT@9AQ4Mk}k;oYB}rbMc|@~5ihi#Nv+iGEJP3J;u^TT{BPdSwUj z6kUuf!5){_;{8x-1y$*Xzd&AHU$W)IjIQ4fDljmY^o-D)kQPqOnZ)|b^N zGvvx<{)XA?>}Qq_1ed*e`16%>RQZI@DpG!3SH?md57wI|r4cee8j9`x1jFFAJi@40 z$;89C1CY)d5yNeDm@)Dlac01o(DENTbX#fws2V<*_AM%h;8<=O0<)T*iD+RqkO(^Dc4sC6pvLT2uKLZ^F-YXM`!#om_+|meQ4uX+LKMu#^ zCi>I=*@t30w5~}*;X2nK)EL$w-U0*+rWJu!y*N1F0Y@ZSB7lE38WlCUFtYU&hf4E% z;z(|@29i)tmgJ;*%im~G?;qYy@k^2m1`;Te$xDi4{@=wd{!1KyrU}6iZ)C83o9*LN z$O{XzCz9kBmIhQ#x+#=r$dxmy!pEDP)9TwxXd>eMh*;mNk6nRB?cS>@W!wzj=ewSl z6b}utsq?I@^WVRVif0o1YCA4}_jQ*C7Q1Z)c*TA_9qQOBHU|6?^@CKWE9H z;YkAv8RSS5v@6~m|6e0+V87*8MpY97Sg~r_fcSAS;!kdT8ZDQuX@M3_1tFmMQddk! zK1>H^$zuaDAkt8|B%o=)q_=6`TX~EP%#^MPAO+@5Z@}aQpFyB(&kU8R!TMrZM|}wv zp2QzYCc*4Z(uRiX{!I}XFuoa`nkpg@EK))Z@vJK0Kur)sdA6p$e#)d4gF6~iENKx5 z1O9e38J9mw(u+!6^6{p{nK`Yo{Zm;62{OX$29RI|)@i*SWf8Xf5YzzD@42q(i!0?L z^x!wo4V=Ub))GH_z=wLfb1H0j!p8$H?J z;^%S!OJF>cVIo2+)~ZB=OIZ-`Tn7{dYK=8!kAD29)VAeTyMi<1$nSxfjnE5XH;1Ps z&dgqumn3#hMdMH(=8V!;%84y067fQ!?94@V8TfS*8B=@nEqpi0^*;vjKqdpa+wX1~ z2}NtyImrbBfluAkG`{<#TYl3Lgtz@?6*}~NH^LY)S|k7#U8wd63)_r{)jcfDN_dzG7 zJmZAmLKC|s@s69Jc{x0X&n8yO%2M%}$=FQi7qL#Vv-DW22PL|mFCTS>nNcT)GuZtN zHlrK*oz%d*rq%$W2C~N`XZ`4*a`rE*JJAH#gofn4fLm>q+r56Y8;Xf%<8IuSUwnT@ za*PhLntK@6L=#e;l&MtUCM4u|y2ssclC%L(bN3lJVDBO6I&LoK+k{U~QRqlKl|m0R zU%Gz_p((!=V;5Q7BS2L0kdC_f(RxA^3&r_a&9i}O7dWgl=?z}iIeTvbsJO;^rX(c~ zWoKYx=4K&mx^u!%^cVehV}tvDI00RDPI^+*aJyGo)V)R-f_&HhMJ4lU1f`>?ps&qa zX9}m5Yoa9B>#tAQ{IEXzF?;(EE$8Gm=Ld{K$XK5l-{VODW5RGCBKy_b7)D?%e9~I~ zAsZA0%&Tvr1GuoPq|E!deXB7F>S%BGwe>P8^%U1ym{PU+Vi=W`rK$q<^zQp;YH$WiS7;Kl?RH-+m292RyyB#LS_JCj*VEfjBM+tJx8FG=zFj z$=Kr+Bl}@yI|pJnSo1>z9ZFKAKiBh~?7*1rXNwB30QZZ{BW|!U;4qT+^v>09HO&rX z|4#o+{`+PSZrJQ{@un#<5fL;$KQL?Zum(Wi=;xO2=JbLOqQAVp`X$>DS8+T*P30-} zpe^S$j6N5M8$b6{?qHbzs33E{W*jCI=ifedCrt zUzMWJlce+W>mgo^N2|Cd`r$H@0*3GN^S-4n34d!3kKe!bxXFM&sdG- zdtXt!5j(JB6KD+MThGcnsw_%iW_Ct+6bZbGmR&$lWcqo(R z*VCPGO4(~Zml|&GJ6rJVa&qPg>cgTvzU5R+Cj&jQK(5`Znr6LKLsjhKX*S|kP&H#Y zzwwK&;|KfLHs|Si9v6d*V9D9^!pYW0(y!A~OE1*>gpf^Fanb2c7c|L>61fEBmV&zn zl}lUMu4;bxW=}RhMRwVg!$Z5a%S%h-Ap$t_FM=?}(|S37544v*BkIvPC!{1DWNtA@ zzl`2Qd>ebaBz+skQ(iF);8TgzTOu|%Wb2r*5gje&k;;aBz zAmcS>`D0g2N1ik5KM4IQs&UHuQSzn=mMSlBGq;{2>SiXBvvMLk#6SWi)bsP^aD_^Y z3C1`tH8bkV;_NO%xRIdN5qTBtgcn#g6xU26DmOxnUeHqKNWWINyDq zd#x?2=x4FX=P7rsGqZ>7?)~_#n|vo=H)>?GRbl4U_%#+#vP7^DXzWn-^+vXX)U${r zBnZP^nmGO9*CKt|muld78YNB@&5FaukA*ab?UIhob)T#3J zN@nW)j?Yxl^egX`DmOvLJB4e~&ZfLLO;?mqYTSTL1}&KxUW=1pPW4d>susH?y&_-Cf7_4zkr@IO(`3J^ zFowF9@S17EM?!BXs;R zP*v@Jh`%O-MLBv9)LM`7bnN;U1zLgD)>!TGsLGoZi=N0|S+7Sh5(PcL=}n`%dY{gq zyY8mSZJsmy`o9r=VTGqFZxjImA=o`b2|ND7i}&wZF3a;@M@rY7b2ZM~jC_7PeKj`Ym<%e=Pf8`1E zVcw15({>%Tx-0`Ms5<`+`+eJhI)xPdk?GC4WGqk>n0hb1;&nXeI8N7u=lqk%$;E(R zckkF`>~*O2mp|VjKM%i?*c!41URHdxsaMg{@cM|udm+qT!WjWR^LOzjuK`02L|w>` zRBVRL;cHiWMY`ib_t=>?sGsIff`zT)J)B{5f#p)FaHqSXCFaw5q(((Q_@wuI(B^#e zZRfAe@f=OOC*Ulxhm!I|cxV8>s)QCzZaLpfllLYP*+?{_pBsrGWO-Fxa=F>t1jT1i ztZYgD_LnZIzj;-xUy%!*mn*R*Fd7t#2CB*u zi;~4=rC;PR2eJrrt!^j11t+QX^c`` zVVKzK>2zHH7T?cXVrh}*f}$btuE*gg>7sB5$c?1l>GHJ078ih^k^uXtn%5iX-;=ZE z^Ng_=BEzrqrfG(i9@#6D@OO(=*ONK$^5u?d54EsC)Xdf96;yuWcPL2h5*77`Ql?UvHY7?~t1e(=?_C=q*FJqDUsv4dp=Ez*-e$yn;68M|8pFJ+n;>S1M zOxwJKPUPx%vpMnk=hMsdp+8+lHvhzIQr@pUA5#jx=XA02>upku1)Kz>+?>A~l( zk!ux+<)0chqx?krpmxqV=4YBdgLzzsS52Z#61xERzalm71i+b--#@hy7aUz;3o^!a zEtpa4q67}+N?xRJ^3eqqz*%YIWeo#^tu?R(n3QTv4KTFsDcu`uLLZAwbOpf(|4LDj4T9wVz67mMS657^!$6va-4*C4)O9sjw z6G`(QPYK~#lo|Xvm}5mxU|7NGE=F_H#ezNl?1oQ%DNf`@!NYQLLePmoS_ek=JsJsq zm;Iw=148BI*4i%#^UR*$aYNBeI&GFPq=wL_xR~~sOV`+xh61`4L8d594Y{A`2-Bd0Ks-| zxW_*w8tWRat$^<756QBibLryk+MYZE$GyP14<9A;kmYG~M6c-~-T!(}2(v_#qiG^V zVgmuk(Lxdr!Gw!TLyJr-ILe&ai16jATUZL9pQPj^Yvs z1RuC>CA1W$f@`6cT&RuZ+!_C3d}D&Gm(C|+NvF4j>`xbg56#2hm&u7g8G0o8I!4!! ziIp94+q@o^iifUT zUf$F9&+AFRYJWlmme0%IU;292xv}AV^>NUoM+uUTaXl2jw{zT&THWr9{QG47#9N{- zP{sJ@&z}IQz45k@{r%P0tz&V+fz@hG3`?+A6K&_I-cB9-`LqA)@84e? zk>`JY9-Apm*$@Z))kY)js+%bn)97=P&T}zJU6=-JCW0(AVPh-jj*Ck}kT3n0nqFlE zmUGC=)+c3&`(cNV1M=y=lj^MKV;I9VcR=|63RYDT`6Dl!<`pe=B0jXYN2Da-zMPAZ zO1!I@2112okB?7!Vj!9k$)>;7e+0~(OoaPZy+$uq736^zNxcGvQ#~j^u`)^Fr-a9X zcnK89SYeAy7}wU%rX~D$%7XJOb@TQj>bmx<2ulcz+e*#PNNQjpl0XU&?rW1#PAHzp z;^u#Ns6x@vv=nF8P>dgKF|F2%<*lvP9q*!I%H#243o~R`_lSHndLRx^HFL;|GDn*w zJ$7pkHc8H-7_akqXe_>NP$D!C{i@9;xk=t_wY51F>$FlPH#Le)u|TS)qlx{xo<6#G z7*dGTV~$gSPS?cQ6mYjmJnkSnhyIjGGy1Ei8QT{_d-KM1C``^?siXIX-~|!->0tzp z+6h2TR8|?6O$&m{gws^-$cw0hL54R9gl#@K`?R9Ddz6Bph_K;96==q0BZq^k6AwM; zK3S%}9Pk_v7Z)SYVpy8LYwd+7F0hzhiig$H_pcC8t~fAru9<4%rVNS7`Cw5H zCds6Nag+O|mYSX7czqbn))zn7{?Y>ImmHFpmt(GE#-PFvbk2$P#g)&8Y+(tIxr6PoEZG zT(`KK`gm8Ajc{n`*P4?J^Xw0+X~JlFp7mVR>1ry-QFYbR3&|^pFy;Z{`rKB%Wl1)t10NJ)7h5XFOxkGQhNVm1|ty4$g zL(5~@bYu2T&u1grKpcliT*T5MwTfqz*tIx3h3E2(iW7RXN3U;T-5l!(34nEtIjB9` zVtr!?enR&I33Qr4c+;>k7KYlSCB#s}NG5sronO`>03?fBwmG|lgU0KZEachwt5KSZ zK}Bf`?raQ>N7Xd<^yvdVX!TWJ{XJQ)`FFw}*6HZ+4bvU@4YfN%_Vy=dXc)5p$M<;5yO7(|3$i6$_8 zqlx&l-5Zi3KL;)$KI)fy#FwtL4Ldj4D-#FW@fRye&zNePn{Nw9 z#&}T*9ydTofB!x?-&+Y=Oj;GS3q1L{ncLB56r_og`cd~FV7Krs^|I27ZZ1q3Ym1orWwb_j5^;kMi1lXjv9!}pqI1EO>3WYCX+^`}?FR8oJwwGp#o}f^9s*y83rS=F&Cdjc|5}kW)S!>)E#?Li%S(Ti?65fYN}u19 z(A@(I%UxmUiIHX&QJ6&}(=LRLn>*(MNOls3Ng~e}@v6 zr;*F=SJ7S4{QJyGKCwYu;LVHmM~LfLu6f#Pf>Fc*Yc~hO!SPQ7)4Z(x22ax-JiLGH zfIXxoDim^3tBG5t$<^&%6Aw%4$5@?nt} z8gr(e{5c_ z`;XCGuc4*7FJ*fTc9T(mG=`v6{zBB!Bt7yzjHYO?AT*+(rw2#R-Mx>Pq;-yNbg;b> zh`H&!H+U)wm2H<@|1<;~mXQNgOHPB#uJ!+lbE7EK&**jRxGm^U4KF*|D#Qh*(dcqB z{VcPB?_gXRzi=5VBpFnSN71I13(}bijeP125gP?XVK>Ci*dXux%mmXBW$>@gmag{l z>idPDhBZ{nk>XZTYk3aLAIDxM=SDo8SZlXC$;H)NAj$GE&QeK_>AyZDX3CoRdPGg! zI5J;!{h50m`@0(R$%|VCs&i`@Tz%5e@VrD*BKP4>>w<`xf(KKGDDcWcW)+Cfx`q3Tn$RaB2KSm4S!Qd z17E4^p=x1?!u|07W-C?ALgUH9sqMMAsWdv~pzfg7@vT!;?cc$$`q`?(OMyC>DB7^> zm#{zcRjakt-dZD8`11V&`QgcNJkNdH*L9s|f~JC~ zt+lr;-2V|z>feM_!%vH&ilCw>VW0K7{5bSL*;`@Q`-T|$OFw<_QmZV42FGB-6kBtm za6KU%rpQFpjFygA48aD+7$}K`r}shfe99kI-^7Bj6T%Z(RAXON<$&)+I>QKbJv}j7 zd`xdssXj9N`(R#va%-8xOp}Cml8W54mux?oG9~!e?CewKRYf%)4uAOv_QAg`_meOG zN{lD?<@HYp#TFN$wMU3=yxRitC~yej0z-3i__tqkwnv+X(nR0fwYxvi>KE}`{QEKT z$%cLc7)CRCFL}hQWczsR85hR}VZ2P&dt~o4l1Q{FM>tc(Z-w5R0=gypR-{S(tn~~v z?#(Vte8d-Pw7smZn!H>o*mx}$yZy!Dbav=bwn)&^qp+*~LQZ@?=k=K?jK4|^0#f@NW*6si9H?BZnSb71>R z?)MEd8X?yK+kGa%IA1f1RQLf z1i1!_8qg$5HWIuzl80R7k{G4cQlg_I+0f+~RZMZLM|p9j!@e+5tMFPNx#kuj12HdM zuL1U4LfuQEJi3?9vhkon#6hZG%)mOUdvTWm*sFTul8magndhwA?@i3+fH@ zVIV@FE#Lrk?F|Ejj`<5wKlmBcgQmx9xX8MNq%L{EAM!)e1!34 z?)F?Gd)#OI(j^4!MciHY z;>g+!;?uNE6|yOx>`Z^){k^AlJp=-%PSOHAfN8F}NZ$GeFg(c!;k9P?3K{H%bi@K{ zFpr5ZI0J!%^j<1d(Vw4uJmyk$+(Xbh8O&BeIhqhZS z`wkR6%)=yH%=o%Vys`}}l4`cvVumKQNGYW^y(;F)=nScD#ypVztftYF`FPB;qSL)#6QYPPEIeOE+yKMny^;DKqj zCvmJj;b4}yF|ie2y7}>bt=?W~->%kYKYZgsHTF7S{-%dqYv#-=TO<8lx9bH=X@%8G z-(j$yb4crA5&%DhkBUUppz)e~ngphT_uRZfN1)-wnfdJ0b&TLs*IW0NnYd#Qq9lK4 z70RqLGHc4Rr*NBSI`(~cOxlf9_e#9OkyDLgTaF?GMt{y2uJlSo&26och;AHv5FJ%MXE zsK^WDkH$SNrIv$PvAX}nYNEomBy9$b z_yNsVrkVAN$>ZR?yHP+6=7OxFF1gAJgYxX5xNtRhCazbFF-163?W?Mm$Lg0^=QC=^ zM%PSS9NYtES}ODCZ-x+3XR>Yr4C||~U`)xQ`H_fJ18ZMbX$hN$ziS|)@>dTOA-UDM z;{OQFT*pz+SpWL>k*^XdihS_9>)?1oYR#`8wSNwc_Qt$JI?y3=#UZ~ezJk)Q-i?kw zTeqkI4K1sg%aXnquUrx@Ksjo@JOp*~O zTq*E5TyGNf0LGujfX3X*BdPB(r8DjoeVc|Fe|k{rm<)k*smV4Oe9-Y&oIPPvEzkfB zAiBj_)<6H&1u^kejDKFNejIY_&8Ccwx~)B5hZFE6^LV(H97Zr%Y;0QG)a|^wNDjN1 z9lbg4U;K|$lji$xj52lEN9RotPr|EU8U`bKUJ4PD>OX2m0be+A16uL{vK(5ZOWr!~ zSZo#f`?*L&JWUc>LnJqDjvwEw-CP{pgfR$yc6F`1&5gWCkh@xI54-$Z#wmA2WMegb z&W|cr^JE9lB)5%>&s6}3mPf7KM{^Jq%5+_fw_EMkqm$5}09W56VI-jUXC_y?M|l-q z$EYsizKy)tQ_2rm<>#eJtr2arhJs%v{AC=Ey&NyI@s@F2eZ-DpefT9?<|jDY!D6ij zMf!_I*G2#J`S}eNfS$RABV*HlxCgij@6KH(;w?>86meI?z_WSuTG374w?Os2AYpg5 z5km=i(YQk(M933f&+WB0__eiz#Y&-mx49gtDe#T8)B2OaV4Nh z*vuIVl0fhHx#GULx@Dh!>fku=XQuEl&}G*I=BZAXll(p1d$DnLwvue_nmxUPo>Bde zPm8E+Bf{bL2*bmVFV}voNGI={G`ilE$AwP)969o2J7}6cIrUNi{Q2W%Tj12E1(@2t z4qyv89$KT`yjp#o{$u6(*Qs$+5XZ)f=lv2wopK56p5hN2%5fVAfCYwynT$`F3BHc= z(!PTVIOSNrY|MN}VC(<;>z%Nz>zY>^NB_KGP4){7)(!Bo>ulKNl|AlL(<5&3Xbi=3 z*OQZ;M>K`~+-pyGfSRgIGn~t8lXQD|iuSYQ zdRID>i<8EyiBcxdKazcH^=b;?W21TUx2=JClRRUS57qdw&z~PSNc~G%(bt4z$p*nE zjD_1o)?8yfI6IBINh8RKxKnR@!Tz4 z1{6BEam8F2SAKc%Cam^<&Xa%UZC;hX^_{XtsOaz+^DgUHD2;63){l zLrcZ?Y6K(uoxgeh#fCgwgKpf=j-nKy0Vr{}2{!mpQIW%#X9QDPEr%?-^}))=&>`#d zU*Xzm;$%mSwOh^!h#!V-}lc|9I;i zW~yId-xUvo-;h5VNCbZp%5{o=i{#{wHuf@Iy2WmzlOE{S@~z8d0+phMlL4f8 z<|?8ma6GrU{Go1x=V>Ln>vZ;itku5_WCccf8jB+dk#DMsKP~Ssrn+^wa@`kIB@P;> za2uflvCx;od7|exJ?Av})L5v2JQgGol9x}P;Cr8Gd3*f&AP=}_NsEES4{R35Y@uKL z^oR=>!TdUroajk9a`~z8`~DZ#&dcT5(awzSqrai^PHpYz6EpuwVc*x6Qsb_s&I(cZ z;89Nr$h=!N4<=q62a;Act1XJq$2JKJ2~@2>LM^nlIwMwg+mzIMg)-5%jsrsXJaBe z+l-^19fBu_Y+Ri*Sz^Dh1?|~_JQW@qm5xZp>=I#)G~rF|2kISG%)Nk;(9=jga{=X~ z{^CLhpoeus%Pn2*3H~td#OH&zD{^Lv5M}}f4$Q0$a$k#TOANhcK2(jAa~Q&tRutd} z^LfTLmBKGhF=l~tB&u^gv>>pxLLV(i+ z2=q;o79Hz4+T!lhh+ZRrP<=&X>()fkJYDny*WPx28>cGL_oqe3ULUq(f)^k5OJ`@L zQ#mLcTU+Uw`ehexoYzuL`eV&28kn2qgj7>!r-2X!rOehbU#f8{&78@irtj6t{Ta;O zt7dmcSP_BWFhv+)mxK%Fv~W4771%OumS%T&vXc#J-knDI?2`x=+d_04Nx6n)OV@iw zVY`pQZ?A7QxTa)QG6~l4fTfOVN#VQ`<*cjkg-KEq0E(637`L66_xIkahJX1pOiSf` zi=o9XAPG7?ytWQfqiG=_5-lpLyha(GmA2ldA|=z`z|~2L=YdncbTG-nXJiT&g*96f z+5V~BYg+LJQvmosL#IJ*j^BXzf!%jTGM9`x49?nI3G6mbD!PQ92BTm2N6Ld7kVvh0 zzGZ4wwK2)*v@87a3AICBUao0hBlEHaFSkBKL;LZ+v=rgeJ_-QzX98O-vX(^h%^GRy z5d;{2|5<3NrI->shB1iRT)-eRPDBivrNBf2i|%u@F=E$3sjYWVsYGq7xywt_rD&y& zyWqZtLT`Ji`c|6ji;LOAgqdFuasHMerw475c)u6K+K_XEAf zeR)9Mcq^E=Qid#FpW&5n68$Vd(uq*b*#79uyWy)HYLeo z0F!Y54xRJbHpa*l5BsN0v}84GG>9jrAZDV#`E3+Pe{znWwuBlQ6U{V2#1WsT{*amN zpDVJ3tNDon!H2ZZ$aw0CkPZ|af2m32MHzV&X(M@p;4FRr$`qdzKqMt_1G?R}zhon_ z#OkY|kq%>pUgvp)$1Q&ACpnZc#G-|Q+Rr|bTq(Jo@X=Pe`=C4;W`@zsyKrcov&O3~ z9M#}3JCg5S|6r7rb(MOlU7Las`JqH?FnV^%3^@6aV+nbWOko1d3iWzKn&Qd;W&{UF z^pNJg6;7uyckm>pbe>CR-k7CTp><+$|n3TD7w#AQrU&Vnk4g5kU^5U8 zqv{VQFzIJ*G9;`U5~j^-Jh>vtD!wByf#0f4I|*%zoOm%>uJN5RhMUQoox;gFxsHOH zp~b7=*c*IUFxf-pb+qFF#))CWoDy+^oqFYD%nU zQJ}dAm>I<2hv#$DDE$*MF|Y>3-LEbUMMnWmd( zd54ckM4Z)GHt1IW;gA~HIU&D|AEF1?CYB?oHYTRFH$}a(E+mA(Ff@-f5)hErkB;F9 zhgyW5uVu^I@FgCvFv!)_#^w`VOYfUkW*f&H*14}p_wCAsUiGf7NGpFZ6l|CV>1R?OJ_1456Q3w95dNq&P0yVxq59TISm+6bhK8jHbib1qeosXIp0>Un|Gg0`_P${(~(5=+T{GsBQl8B{PLwnk33LAL)Ss=m@Q9fRIO`n z+gsrh5kPzXqT95IirxB=^u}(}EDdD1ycYy+nEF}LFty?67HQ{(&2n4_HW}YpyTg&_ zVxV0$Ne!sZV%*?aYA_qECtF(cYB&T%0|X^&YhCPR62$8f_cA5V!!r>!S~Q}Qh&09u z9J*Y+e{6#dtcSh6!CW+PsMiT-)0#K6H(}aT;Yg$W3WE@XdWWFiWwmgq0+d7n4Bs@> zz2J@vjd^DYi+G&zQeB1$Oql{wD2W;GDcF~l*)Yq*K$iEBjri70Yu8cH^B>t2{K+2v z^{&n#(>#)wEK0#uxv&6Uit&IS zXA@?&NRN$fC-43bSdk&afY_&|q|mu;EhljUsA7pHTe-S_)4Qq(-Ujumz8E#cy>5YU zW$;)?P8Jz0S*PM9PCnU#P1K=pNp3readFV#-lRBEup5$+hgi3igAZ6$n-;@GKf0&= zcMkoj^<81Wb55j&;_JMr1ee-B79|7FZ7?9t_f4C&qg#BT?H$q-;;6i@{&kLl4w#BZ z6dC*9^6L*92RZG9EwNu+v}g6I*?t_T8Vx<9= z6q<&x^8oQ2f~uE7t>CZhvc({nw^{0Gk|va_C74hq6b#K6C?N!QI>8JoMU3BicA^R~ z1I@9?h)_WwTp8+sH`Ewk81>!6H2XVIP%TKGR$#J-eh@;m)FlTt=ljdWXs74LI5pev z(u|);0P%ZS6PIZ75sgUD%=(V&T$u8<`(yrrSM_tQGWF z20&4};6Wc&d3kyYQU=KP0RZq-L&=ooZB(f~v8Vj)7Q8qaLCs|rvnqWdAo7JPOtZ}iiv|btY0s9yCYhfJvJX)Jv}Z#l9utv z>Vom7HytyJ@*fIH6$=$}ZVfq2JS3a{XZsr+Yt_GADz6yJyo4r`G+4 z!}3->H+Ws>PALqnYYbGGFbBfTa?)9$Jn~w`!qOK%%yxbh=-fj8(If@dTY>Vno+kGK zx{qU5|B`{@PIXLE*J{BbuSC?+gzX?%CN!SaA5+#2wOmb;Wl62jvAOKIsvpY`ng$04 z)0bO5eAq_Bh8{!8KQ8Y~1X8oUzXy?;{*K$bn?*}B6B9?$BY+lybKpvOeTQ&EbtUtD z&-NWtJLjtrG>q$TWzllE=)_md>NHMr&!1~^+nAaaY?T(&ouEKMHW}TJCqeh&-p*Jk zGo^$~{`J3_n=SY2>$1-Cr(yWVHz$i%{Wq(H?I7>l&wM@zZlt$y3;oANa0Xa@z=-)B z5q`l_BD2R$YuWnu-Q=bF6vF00%agA!ItUm3av@s;mE)TW;s|TeW%dQNq386>+4(GEGn;JYwXI1EMJt z7tCEr#JWZBEa`!+-6o(!VNOlU8Y}B{aG>g}lfbS4Zwd{wBJ{vwYD9{T;4tZL-|~ckmBweRi7_ke-7*Sg1ZI ze{7cBFd5`>DcsuD`i-IUN;x&CZBZi?pfSs_-#Wl+&x5wk&($?r77q|kIwpYw2~6^Gt!~7Fof6#IMnzovk(qh? zDG4ZhoUg}2t16i!Jkv4p)92`@K0Q7QGcMJ7enz%Sf*=-bMU+fg?7fy!?rPi%A(nx# zQN&0DOiL^zgrI6cZDupwxc6BwW%NDRTm$Fnb*Om##>Rd49ovs(M|(jHMGXRjTiU%3 zq-Uv6uQs9G52Hm$VUl9N=?I=DT22bkdakGiUoBnhW7pBlX`^OisFogI;WAYzOz*qw z_#-w9{_Wos#W&`%wNTfna$^w&UKkJiR%8HeILHlh{#qlCmQ7evCDH?+VKV8$b0`Tb zdw*(uJAp0LyW=r7YSb=OtiFX(f8;9V%1=-LVg+F-2v+WJ(Xf_*-PM!b$QaZ-_nifFz|7GY)ue{vAgi`M^9T8xI!c;wlp z*MJHcNF$xE*|&5n9mrr8@4;ng0*b#9_<`ZjKQnMRSXK}*IEffwleR%WVGcg#ic%f^ z;)R0E^`L}}rj>ll?}-M$H9*vwqX?03kOM^2L_Cq|;7$Lr1F6$Az`^eIgHd+*VY1!S zEOhWa4K$aXZM4F>Tn9xx6$n=(1}33Xn22SfB^f_JTav>dw#+s(N4xwWt#B>@pv1FR z^>2DMaBo@A=UazD-rYiUlstDZPVdL}Q*yutjLQM&xt<>3OgRHbKvavMZC?+W-H?)# zJ`8b2iu<-fc`<2~>eIz^5jI)2p`B!~Dkf7yU(aEUbfGeUZMrclu*RkiSQT)jMA+2V zlXcxo_ECA-16WUd>p$SmC(RFflWIr_gZHvf12C!>@o>@TK{l^{^p6$O32MHwh~~*| zhSU&VPAoO$gBlZWBh6iIujv>ob;47tF59xvm09$IdnacpFmgrGTg=R3Or zhGqlM=blvcnFzq&%~(kh@6@0UiwTQ)fTAmSd~K^GE2~UEIv(`eNuFGEO#qPIMlKJZ zbfMT(&s7XZm0bVC)iuVT`#e|0Mq!|W1Dj(G4n)b6Ur5+cWbGmJR_WPApj?uE-YTzy zV1bcixK#xS39#YgEFp`$uy|*a>USEbtcjK+skj~_d_5_5vlDhTd~kE^e!a)=SnlTJ zaVRvZ3lwk2`chW9ZFaU9v(v#jfcbBUL(sy7Ma1p$tzO^e&P>uL2Lb0rRmXcNjc#H1 z@U|ri)3N5X~uV|7C^D zQY*caldG!nJ0Rv~%iW2Qd8rv{8IE~7ty8!@VGxLSznGgh;Q`=0iktUymnjvyPB*{L z@hA6SM{(T5?Ud(TII%Ao-=kaUA#shnv+S^s3=lg-u8aoL#l@G4@TLD2L!4(_A#ihJR+2H|4#ss-|y#*Y>k#?*G+wd;O%HPqIz07!K|NzMdkP$b%7|LsoAb^e6yo#W@@zkL2OF{qO5wdyYNwB*i zt0;&q7n85!?D!Kumi#6FV7hsZ-hf(AfjFmkdO!~&7!!~x13-x?q5wv~B5P@!eeSm^T7vwq6R2T4(nj*5yzq0RNAfGJS67CS{L9w~ZxIFQzH>e6nz)13 z{Z~{H`-^Lyl!JjJvaA-G0jAYb`)8?9f z;SOS0Sd~K1^-$EQngBnea5{mRa)Ae@%6Emq4_tyH4=F{ z8=5AeK~_oMQyfVOf~q?$jb%xi&xO`V+(xV(29=oqULy(y+`>LE5fn5B2i#_-e$PmLzE>dd`PkTIvkeNv5v~xpL9S0D#b>>gPzw zkwmC&)}uNpsCUA=c7hPZ>etLe>HqAIVWDji_0RQ=9EyP}T$&vm(m; zJ8&1LaH^Zp6xu(7eAp0J)6vBAErWlzB1)Ly&z**YM3|fm0ku1|JI~Vd9&eb@)Z5Lz z=xEWh_*ybaGNJX85r*-Vv9+)^R*UTCq5c=F+bMA?Sr%Q6FGjr{#t%$$hSXpY%d$zX zi!-gfmr_9t^;&Roc3U6C_(McOWjVb(j%iFai`?#!|3Q1TtE?>!_c7~UO^j?1pLn+z z?k)fE3Z7`XNa#G9C2|zPP7XqkjyOZF6Wj|dr)El$3h4SrswR#!mo_~_c(%#N$hlvJ@SPBXUQ9Lm4>KE8`@S%-`AvidFA_03(yi2tDkX+i zyZO&G9G6Mx_QteB*;F}gWeN}QgrTxRcKKM+%qF}ur;Jed>lcs6te3{eTc~WCt?Ys9 zD}+60DG$go=H`eMa|Qvl?|=Re9>rU=XQU5K#Hy3 zZoi{U6IO$7<;Qs7ZEI0tce@ympcZ|z)GBx|-DK8mvj!9)!Ny}=F4mO!(seKRCm(w3 z6;F|#B(I!}1Gf%!)?;1WcKqd@Ed5@Hg9FkFPrL1Tv=EystS+mW)o>#FuLl@@ z>1Q%-wk<(HYEkf5>kGg3r0LnoP;PF%PT36eq-SC=;CMl_TzcLd!Slinvu2=gTBLx|d~D*%Kmc3XA0Yo{SN~l^b{2TUMMQ0dA9+R@Ai;Q_J0lFo6mCUN zD`?sp5!VO<=@K@$nR|vM-z=@8R7}lsr0}#X3cR>8@e=sDNZA8xiJO@_Kn9Zq;5t$n z3AEBM5Tfm#ghEMbGFaAX7&1y)b*q|(_v=x5;}#qp)oiRP@ba536XFs z0ch+Wyc5FQB^4sU?!j_D{ZIe_`h^Zn_i=foN<*W1P$h20uUb?-((ilT0vxjxC31}5 z4^XU8Db_s8a9}7?_dAf9WQ?kW?ZpEPxLxFwtr)|kZPl&n2nGKA&FXTgyH1A4Fbw_e z!L32CgM;(1%U{E3P2pNIJ6;DE>5`s(q;xtN*G3ep-d+0T$Z$8OQiRBCzKFDb|5x1B z!?+)YjbKr@ooi)_5@}XBYCQxx++p*kz+aDdwk&F_Me@{R==^ac@Z+}1&MM1bdp%02fZ_j-kKTC1#2N` zz5^+vDQ>eoX}i{zC2|D*`)CVX9FZe5Ov;F)pN|P^7gAs%XC& z@|-3%IDiSsUMiVTNtqf;vh-=OrB2c=wOj8V5?A2>Q4*+s$?-shDGjVp5`@mxgygCG zgER2dBV4m?mpI0eM6ew_PgX^OA$LlQBuWdCg^Dl!rUpr`#j1<0-A_Dk--P*I7@thAf1+EPWixS<}}x=F7+s3o=_6B4RCTdL%$@S3_!dd-Kb>I4Bi^ z$u2P-PK0Q`jhjAkDYf+hW+3XCULB8gD?f)P6(<*qa?^N&VISTiO7LU=(~8O*C)b8# zSs#8^l3v#~Zg{H30P0vgd(e>E#Wbs0FjMBL@cO9p;%aMrH0hONp#hZ%AaKrbJh>AB^WuyOK*v+eUX2i6$& zBw5ITIdP!Y(@tmE)QMTe1fF4rRRaddQ+rT(&NiBr+&=w8?90!}ws%I)4vn9O9hQY% zuZ?pyx-xd3d`bG$X7o@?r?iouPBimZVl&(9U!TBzTYv-U8kJmsX~5 z1pI1!HgtEk<6{{%zvFeNK$VP~%wpq8=(8zrMqUF_AQ@Ym=ZcHW-4G(JBBothj_Om6 zh=H7+NtzctD&Hdyv{#_uq7b>c%$s--0OH{vw!V1 z1@L3HB2jHMRdz%O+6%GF9Aco$?bkW4{$c7CiG|&wAMaQZB(x(Bv=$U% zIfHN>YOq^8fZgJNf2po?W(zJ%V<`FL&r>~A=h{U0rQ>QkT1E)f4z?+0Rd?y>TicTM zId+48eBxBKe|mm0P6(3A*Qsi_+#`M`M~CVFKQ4EiQ=Ocg%*;$@yp9V-*M(b?MiAWs zY}E$`RRa9tP?dvq1uX>6PH2odAaQKIxZykhQaDh`W!4Yo>gU@HAm9t8hb z1x@q2ora+PR5FN@M#%%kx$M%42%G($yY{9jZKtkT=bVgEczi~62+R@)>Po|P5NY&`os$~i&4&5Kg<4z)?l^OwoK^+n~fmVu^Kclm<5XU z7rsY>Q)F`6Urt0@hjam1a4aN}!`$zrk2jMr$JBhKO~?7%T3m|Z=N~noq@%-7#r1~# z&s1Ek8CQATir)4b3}Pe4+ma9{<$I~pRjzaW5&+NW?ggmJgR~ZBPeu7d$OQ-tU((vG z-cMC_XD+C)gFW1;;i6;-{0fFb62jXeRepxxKp7VfVt7#R&Cbo=FoqP%yHTU@#)=|O z;pi`6U7>P zwJMJn-OXwbDaVbZmB#^X>*&XSF>zpx{123lu!aIvc*v5Q_erw6JTG+<0{sA84l-E= z4Wn^IcTnvNP^zV(M$SgO=WJ^Q<|_^b)o>5D*K+A);Wi+h!b18SU8|Vizp7%gh8wa+ z4+)Wn1FkM$fLF`qJDTvY0@h^UpW`RETJCx#oAuTFJlva1Ox}yM`}_4zrPw&EZ@^%; zk>9JzhrWrUx}wT_rC+^o3YJR(-H_L#wxVCP*fjn&B46e$)=X#&@?@R_+mAH+{C>iaVJM;ir zSu08eJ%$<7M;+DQ3AsO#@kHcqluO)>5;(o0H82WWZ9=5!8kZz`+EF)Ds66j}@qz*M z;b*I#ZzcAerGlH*v^JRowXe5LJ%Ef_?k~NZkBg$auDtoz8E`XSb6qS)(B$|6TBydG z%WaptuAJ?Jolb8a?sO6;Da>X`@4PZbwSekbtYQ?{Y-@&Y>8$x&Kkd92z9Cp%%Y~i9 z7M}T>pKO@8Phw(c0{K`jIxj4*?{msZbDuxH+0EYj!lK|5N7bgx>83ANbdkS>OrP{T+?Q ztOW%l)uEC#NbdCd%gqKgAni_fbhLL3AokD-@>KLKzUfh<=+Hv)q1CO@G(K<$XQ{qf zJNvVDUo8FErL>Y)0D#o)-VFn1BQ4j(CtMPl@qDttrCSYpm89Syvw0OjP=K6DrE-*! z0Hn^WLKUh6ftfK094$T372aptG!lrctfUJ_ong1w{OS|iPzUg{W|1qOw-E>y2;3>4$-O?9)!HwtAd z@kd+bjlPTS?VLkF2s-bZv*9Ku0)dd9Y9W4an3(x#{s}8P8ed3m!kh4Q^XxbdW=VmX z7u|6pLGD!zSo((0<2+8MNScriVplf34q-0$w88M^vP2o;O^2o4bJZs2q@*CI{hAu5G!$>dgkxHu`BI^P3}R zmF@bkf4JjSe!4{;b=y6RTsB*Poh0e|Hsz?==DXHG?6xoulJc?PLjyFk#_^%!L`ivD zAFhJv8(nKl)jQOBt1FS5B2CK?xC1IzR%;(uvLjCs=8>FGULPxS<4s7$c_$9;^xm@J zP8u97(L8cqe_NU^)S${25Dyx~r!`ohBV;Xa+^t~vSd9l^UfaVQPN+N%(+59EB3S@A zC!+;ZKhU``fg@_jf+$@HyN&D$UG`zJ1|J3nbdM-0>G>{wu7N+Cq9{@*c>6oR+IW0j zkvu$O^^LV65q{yY0@G8LJ~9l72Ti}R&H3e@RRDO z-oy$W)BtM>W?}kx2}(-5>rq8Ucn8pTSUYI}AXq6QyvSW=t)Fz^Fw=-$i7ku!H_Pgst`#PRg`r7yiFpzJ}3fXIEkwVD3_FsXUBQH zqiuqH#A!YPEf;CJA|7-@YK=Ydh=31kJULo4ujwL4am##&|DH z&JKJsCjB;r4%!v=abHt51IQYe6r$8?wteg_yu*SMOG?7+)Kw6PSQV%W5l>liLjg9C z?!{phPNiTzqkaJT_e}}}3y9Fc0e`46gd1m%#h$*wV6zquu;2B>y%7!&6Pcu{4C9AT zKJJ^*o9Uaun(Ou4n=84?&TlVnwsk|VmfI|^A778(91ragZmtRmmZ2x-7d=lG+e0so z_xjzh57$m+86J0DEypTeT`pc<4~-U1Dor(T_RC$hgHig|Bb(}O_ zAFn0k&6HAG@0f*M{ms1@>^w_;Sx^{yww65WIn)Xhd=Oog3|;L;koz<bWx3V7JYf0W9t9 zNkJ!)06uwv_jN~gceYAGcKbuxoFNJ*!`~1R{k*Kn_SiNIY3swBio1aG^DiJx2nnyr zWKqcR(fR9Rf{!fOp9Q>Z5@Or)81^$OJ{<9Fn006`=#g@cm{V0Hd%q44ZZY%c&-P05 zg3g9PIVnVmp*(F_Q&o=0X3ZqNqBn;2{4U!%2~F8$Lu=>JYVg%YS69b<>k5KI{KQn2 zYa7Nq^E(y>2YkvXKIpkXOe0GC$-5R9;2)}5S>dnG4!`!e;hig14TN_q_;-;A7GiAB z$p3#56$R+Y7Y)gLT{mh#l{k;y-qy*-D%4@4>rm_QLKr@G@ng1TbGDi8V@{br7uSBr z*MEBw)CkApqv4(%(wH3Xk7f3o*0NE`=+iM*X2+H!;l5^ROX;hVq5oh(K|#EKJnpu) zz1e!8Ote>a7voWCf$HY>BQOT~%w6{|-Y%+@39~p;$Fg(8P}F1oHsid(CU^ z(OL%w2Ly?!6X`x;W>lpdKUY0{wC3_8TrVo_gSqaGNxA)EYecDh(-Zx-d%w^h)SmUZ zJu!QcY-{2@IW{9=aER$+Sxy@xcZye2v?+H~xf7IF)Q~x`z^`f0B{|HAh#g0&)4|Yqk%Snf%*FO0?a&re~{)OhcJvL zM3hxp#9fG^;mO|z4Bx^Ydx1%$>E5QmmYMEJt8h~&(yp>a6aGqxHF3~Kdfc}byjLWm zc*jYNXr#?U1)DiqKg`)pon#cs>=4!a>0YqaPI?4IJA-86n`Azsq@U1*opC`(=8s@Q$)reK zNLno|JLcOeM`DCEmGJoM&P{;Y{=TFLqX8J&+QQ|KAua4+o?7-@>BpN0)SOnxY6`h% z7ZsPns2`MAV=b2~;^00_BNHvC3V$pClg_So&*1;BFF>)F%KS5#D0o z`LRR;IM`#*1a3Kqq)E?R$GRgK$kk5yE?Na!0Dwq(lS#4r4sZs0mLyhg9mSp(&0t%e z*px&i`5enQ((u08#AiNge=*I^DA0TmNIJ2Je1D|nZVDG87tWry3#<0h9FxM`%Pg3# zO0$kpU0OHw!u2M{8+MxpByeO^SIp6Mzv1MyDIjMR6e%y~zxwV?!mjO|>oEyRgV`U< zm~ANqj}|}@!;2Y2HC@@l8OUfKKd@!n@Fq`G)!T;mGK%O;_K)?9hXRF~lyf@3UHuhg z7!*3y!Soh*lO-kf`mYD+su{4Q9_+Oc9{67{yntj2pzuyL?l~BtX9mhM?JDyVy0M!H z&FbhWR&Hpc=KO2~z&6q9xgpmT;@hw@54r`A{=)IA$RU3QF z_Q3!E{a){FF^q)86mu=(UkQrptFoJAx%0)VgX^Z7ldzMU>!)Ei{=~)+Vee+8^YVsp zJ-#&rf)Ogp5o?ziS(@bmUlB z`NYwZNZN1)1+-Y4+Uiy~KMMk|cKgYJ`NF$WF{9a{fS2x1L1dIL-Yl4*CLhpt5in+h z(sRZNto_0l*~(O9I-5H!gf#6uBZvGrIvN`D4_A`|KtAvRKTpA;FcY+-v8IcsDAo%$ zxB1=8VvTl-slCv|>I$>UkFIodL~aJrM{9OQXk42Qk;>0L`FDCj6un&T`7aXwTyl2I zCNCrtH*$NioS{*uSwH)tlh-zB3(29h%RjTQ43lHgvzvG2KNI!wQDxl{04>+fMbm&6 zQyrY)pC&ZIL28j4ywQb)FRSshdg7BuTOTW4T!ykm{o00gyR8&$$|D7_Ulyw&ToV8g zIED_CV#fVm&3TAaUXHMjuOQ^QhmnvRSiYnxnwXrkTwExW{Zf+d+BWa}qG_*sW_s<* zVsfjX;;nGx!liM1-sAuL8N$bcsr5C=Z67F3n8mf=tC83&AJgNp{!OeQ??Y^obzEE{ zHWP6g3e{dyFwYK=YYcJV;pOO00y#f(?H35#6;4`U0Dw$PLV|)s>ZhAVcUl{<8s)Pg zWF!5gVC%+wyX?G*7HowEsC*()?RI?J?qvbSrK-_*M^7ec1g@;HFjT=C4Q>}>RXS*T ziv{-9J%N;0xz1L*tw#v$QSY<5fVy5)M92^R7Ui~9(NgUFv?uYALa!VE#qxNhOqN{> zh1_%5M3#?m@$lV(Q0cFMCF_~v=C})=oqx`e|5vQSD8!i&C!!jqw3IkZa<+ZH;B*`S z&Fd!VWwp{i6OxO{38Llg!$8EiIqM%Yq@q|kkrccl30h8rccoKJjBHD_!nk<+DM^vv zHw7R%(zy~VvLsYqOQ)FHO|8|W!7N+@AMA3VLPX_Y88VKKuV!e)R$@`J$ct^@>vpbOh1To|VJfeaJQKY>2!OP?hk%vIFZdtE? zN2gUym^%oQ=d{#z(T=I+=jr&(azS*vL2shKRG%2ldw<^DtdAS~DAwXS^SkCo{N+4_%4u;Vhczk@sUP6CCbv93+-^2k%3%vkp# zi*|hX~lww62%R1A@Rwv}suL6gyiDn=6P-qo&`d zkZFyN{tG;jcG}oUXzxL4Ss7Pf6YnXD7l6cF)%aQ2*HGi6tH#6uOSE%R5OW)RIJ5K} zel!3^Wy6d1 z3wky+v;S3A&2+Yum}fTYr+ikeW8;{#VJdfB%GQ@j){l;w4o7Z|RHS~&JGHQR#D-j; zlYCz;xV)y`w(!Wc{>|YfkWKw_bkg*1^zOd{kAM5`0S(EO_v`L*QM#@1WoP)m+jDt{ zFd)V1u#(WL7Z>`s>EBkl;roDT$imf8ojO9FD!EEEB&hY`*M7fWb?2W?y49zfIn2DB zWR+w*zrYPyE=|c#eMk)5YSWj%&1br` z^{gQLvOzjXMYdidR&1ElC`a?c_X&UwX}Gm@cJ`N@w$!si!{m!1i(^IgJo46F-AS=} z-&A%^XGe!Z*Ysw}Us-T-N2k@Q`}p1Q;VBq^K_$z@<`fu|um5nXh!D{zNtHMu`;W59 zF{y+dLn*`l?gRjLqp;uPyDDK9Z)<>ZGki6SXgO!swu*IduBzlAXI{$zFZvr=6@IT3 zB@i~lItA|ujG%lwJ=PlYALrxy7&uDP!nojHd~LIUqV~EA{L?MxGU-SUJ z)AJUKI~lsx*;@z;3P)8TDaPx>Umqm?Hl7Y|YGWCOytqWw1ezAx#C2WIFYd|uROz~% z|Ad_3eETWB*D{+}&dJ<7z*P|O)s&%cD_f~3AU=Vy6e(x;HQ$|ewG%xW87gr*L_}e+ z5p~+G0;T)}?t}Gk63qLjuo_tC@ibSrUC{uJRY()0QxO^n)4f`0bS!13z|FI}kQv^! z++EH^2M-*}$?Wr1+4CucZv|rAGC2ePMv!l7rIu(g98h!_FSn<{Ezo*N6&bPiQeDfE zM-n_dpkRBPfj`VzXInxzOiEUke}n7O)G}i|zYMBx)W{}BrkI`9M*b#sNwjqBx~N03 z1g*-YfZeY@&1H^*PA8B}_Y)knVA%MofRdCj1@zCOzI)e(55xr}D8QE@sC88aiilz3 z4hTNm@kCI{Xu`P9diIZfT4V_k4%;*puf|biN-4`tj||x9q)BN1nI}_zx9=*@iNSt;ok=R@hG0M#0$)u}6(46)xSvP4t+Q-{KaD9#{9% z(tak$s#HHM#nXcP;f~7e`F)WO7wy?*g)2F!@u2>2RCI(Y{z15}MVuxF|Fx{q@#R=%vC@Zsc6`161F2S5 zvnf>T%kA>tklkcpE;R5@?54_1G|NfeqDLBb>by7t(koCwxs2<3CckE_8^?pVV@GtEbRz*__gC}?)I+yQ9UX6}(q2*F< ziBd;s^&6Jem&)})(AKfIt*fEp^3=4}mf0<=J)xb#k;yBBi}WwXTBfEcD9KTMyPC@c zj<>yAJ)%nT=_8Zg5F4Ctm8Ofj#)enpn0NFJB+?o#^q7117CO$K(Q5I=S~0Jvh3NsY zN{g>#ksORM-~d2{W)#?8oVjV{N^qTP)A;^|S9qK??Ac;ovn?jkni4@^ka*K`JUzXT zj@}LbrTW(?{2%ZpIK2Gb)Ooq@0X&P2x7LAy@^&}gvF+{Cmd7mzi|*@gKiw({Jm#Y9 zj=OMHmzxC_=Ya3FwzG3SGyB+JZ>KGZ*I#+e(CLm_sQle=rvTn{a2-a_^RZ*&Ee|Ud z@>)RWc~$2h#;b808krxE5m(#@rCFc(O|ft_ZV|L-PO^E;dsM1)jW}<6{Ts*?zW~?a z?-U7d%4SKePv8|BY$WJuM+VvmW^QhGtbDddj>VN9(`!S%`KrV%E@s>%uA4G^IX(H^ z#_N7qFq!v>(e6GHi>`dtolxun(!%yUT}yll+Um$3ms<~A`w^=6cXMZR=g3=L`IQ~0 zXTWnRSMhN1H0+RgJFTcKv;{Cp%{iD}of<3m7^^-HghzQXf#nX!fY3IF5&2N}=YvN_ z3tRW}x$z4W8vm_nkJR?6{Fs%-Q~ydKMUQ_cQ1A4+|Jie?k;=J|;>qFR_VBqc=9u;I z+TLlU-3)wud|cP{z}>|zq4W-S+g~JQfMVX=10b|8nI!E|0xihue119A|NG@(M-}18 z`$M~@)h1YXB5+X}HylUgbgUlYo~FlYTt}eR++EFiXFOBUzPzNBm>7DqY^_O^sTupL z>Vz3u$eVBO9BlPAm6`K8q7_Lk;ic9@%Vlq}Y7SO5A0{e-k0hlE7r*6soL_gYR; zUo$s}BRCX@!6_*#Q}ooX#wVNpl`Bc}=paw;oho1Z1-uV0_M29hSKe2y`zr9m=);4^ zBUnQt^1s*!jorF;ujRPi-`W_AkmZt7lV5luzT31Wxh=x2E&oHX@XT{JzH_ZeYXNN) z4|z1XR{L?q;eJiQB~dvuGs@`>oB(Pov_gg}Qylbuu1VsrvvT!br3(PD20ISDjh(tB z_jbq27xm0dIWNB?UEHwvWlK`n3>uh@uUlaRrDM1wVsMKchk73y*>Rj_pO2G%h&O3P z7l~J0#SZ*8%qgoBJjW!85Oq|yw<%MJ#Z8QKHa%Zn39;lt7>Rxwv0;LLc0MZ=t<^Rx z&Vp{9uxD<4LtLIc6Hk*=FF@2!a+m##3oKJt5{jfw8qJGm+5gKQETefP^9pE-J>5uA zIrSiBPxK%mi~R*FMb20DuAi>VITNc)&adJLiosJ>Yt|~a!AeX0z3&HrJvLf*c?yRz zBLhlDt56zzrAI*z1}5}fd*P*h%#z;2`>6A58&$t=>2@~Xcyp5CwM>li#T`)21({W9 zVI^3VS1p8yCVRg~bmn@h;ZQnd zovKq~`4Mv6yzxqzpS8mn?(E`(9L=16AY8JdXrHWa z3JeZEGL3EZ+yWu_P-~C-Mip`h&uK06(&9M9`?@EK`|!V{w1wM*=-ss%e^AzwjzVDb5&mu0yIl~wndAXI1R(N; zGU1Qb)_o9Scm4P%491|`q7w0;yx2@B1f!$lNE++_UKP%W`ToyF}ok22M7_R#P%;Vpqc*ZO^yD6Of$r%QF^BpkJe;i zg_OETJFPVb%amr0_6)dQ(LvDT>|MD;%q?1QN;LFS(`qP(dPaEd#g5g*4&XL!7g&Y= z10bcVhXozaIpf+pmw}0K2e<9I*~X}ER$-)9ap8YYHroz6NW4JcDN!PStbE&VJ_%?Z zg&+U!CG9&>uU`DJ1T!hWz=5es^%7YS6J%K2dNAqe*brzj*wBg3O$WEG9z=jSdIZfa zvj%6f9%8>Jb<}C&P`Q&$p(nHHXqO09uZrCEYNWKWt}}f4VNIyk;Jk>7a|AB8HU!pV zXy`=nYIkeevov)P>uJa}tZpRjb1!)i@2YmTOBI2|GKT#-R~kdz2#b$j&h!kQfBG_Y zhX!ZNa;sM&_?DGf^K99Ck8_wxL2Ck&z2~V~U0(Va$)QOXF{x>p4GtOLSl`*4 zA3Y=UK+vvuHPO(+J!fZ!ptg>xMAh@(n(*CrpM~6|gCw_vST&F zVZLekW7DyB$H$Ky;VNhA{oBk19TyjC>x}di!BEHUFyCV@%wxQF=PHpbg1$3u7-ULZ z=df!ibz8J7;Uc!NRS+=${qj71c-X%9VR|#S`6tIN6pnU%8ArGa{~H<}6kMi+gLL=m z>E3o3 zYz@w?U*hI`sYRVx)kb40HloLtykJ*j(JT<-S_c1lU>Ua`;x;;h!W`Ot9Z;Dt4*NP0 zA(vRQ+xNE8F5zb_+|Jg^&nV+D6{ssKrrz&5633^AhL=Hq7Yb8T-xSi;>Dq{dMh0b* zA!sA6{=B_)F!)``;qV-kb8MWCLteDTXXgcqU@;J6O_@ze6EScp=8#y(OUnlfolCZ{!;- zttnV@{ptu-`gDpT8T)5~g87OeWsH>?@rqWQas*rJwC- zm6&3E6a>L6W5gjf9*Ptph7v&=Bl!spreqjlakcydxze z#b{D)JUQa1VwoQMV=o1HZ;qx4GZpZhEAeh$ zIhNyok;-(I?P&2!>5k7QHMrD&CS_`=2JR39t0m+CxQBDpc(KTR_t6WbyaJc1;a1?- z4MIPRQ^5@Jf8keC8W;LCxKEji#>&f&Zj9*>a3de1R9~p@^|-pNE1%pSUtg)xXQuJl&z=SjT0V#nO*FBhh9K{gU); zXst}#J7tHe2~z4`0jr0uG9y=#@**tu!+of7V< zl6_ZK{_U#%8D1oj!k(3;O892#HU=>6O<-QO?R*drqlhSlc;B0@Fxwr%zTB$){v_iQ zuiB%McM!bF1F`tG8kUunX>|z_IL`z;%X^GDw(8~sEEP($ddo7Jjq+=gVq8+~tB!UH zgM;KxkEd25`PZunFrxGkCvxE!sttQOOLV%ry z;I@wNi(g`ruO$BO1eF>yUmUbCuRbRMYSig*`M7AGfP4OJl&c0o^J`sxjPqgKE(oTE zNlybWU+k0J7L$h0MbRaL6O*&2ZAq)-b=6Ct$oBXA{l7u0&h~bm{cTY)=E`odkYxnB}MSTB^(|qq{6Mcz*pr+6z;R3i&2YD8>V>8j0+(A{CNBr8Z8+V~>W;D)s{6o(jLl-c8e#fkS5Oar$^L8-V@Cgmc2Ww<(q#m87GLdym$;UuL0N6&WD@O1A` zE|$fXM@QaBStVv5VG?_lFK+QqrAU~mJukK;TQWW|5(`h+riJG0^443_i``D43yiqn zaQE5O4Mj1Seye&a5QSS#)tY*N>=QUE5wIKq``+@A?|ZS)z_Uj5M!q~y zx!DJbbFJr98Vu3#akn1*l@`*paikEC6Ju0jDGWfV{r3_##N<<$HR{qL97O9rGE5(l z9L3Md>ocaU$|m$TetuU20_7_$F$B}sJ9G*2vqvD+++(Q|GQWi$a&y6p(o+MephAq$ z*W;8;gTp00Lh3JlWRgH8cG;>r-rcW&8@yasd6?w#4D?|B;PXNHjs=b_%Zq_^zThQE zT@G*cxi|NF5OaBjYN;ryLRTI6P}h_~U!SA58;T-6&X;R&MlQ zmiE;51e}0IpQWxgoRLymA5j*KKQ3sMSNT{km69T&|0n{9I zZ*tRATPsSZNfa>-Ugi6k4&(+24l@CWrQTB83c+w3^F2S7n!Tg_yHEaxf$FmI#)r#S z!yO$7riOdol~5_p(P!eiA5=r-=unm}bvaRQ)S*!il@t&Xn{#m_^1-axk0am9BS6sZ zB=^QKk1IW(PAyH`=NY`3UA`zl1#!$az2_lrTtTYkuWykkQfJ~WmHsZy2)M>Gn9RPV zUH}^*P&D)NYe<=tLn@#hfJn7LK=1XDF?#^7Ca-$wK(K;FCQlpV(Oe9GJzb};>nv{j#Lc2I`0j;bBOjR|@ zREHa=HsAcvQs;h2)869djyXMer7S7{++!22T&ki1-zC9yr0}9-08eLeGi0?3NA0kFKt)d zA^uOs-xqY^w9Iz*GVWgWR4IVa&_@I=-eVjtXX2~ExW#;UfUPL{&~Ps}e54PqM*g8C z(nF7ss@)L*AWMnlz^7lAmT&8-US*$@FxIRn_v{C;?+`=HZhZIV>R%ylmv5I6o+m0z zD0>HRbK@nij*sRSSpDDevRm!{K_&ho6$9u!5YU>QTY|(u=&Wc_Bx`h5)q}DQ`c$T=oS&}o4Q0+tY7w|J3RHuFS(n_6Y>yOUZ#&p6^zAC)AiD==-BtaWx` zx@FDD-r_zQ1d7VM*fYQzV7@Pfei7a89P|*cpC75@RF98$j1^J|tKxcCUWS+)Jc%=~ zvq~hHNnSag*8kN1-#>MgPzYIKxBs12j+L<{v97Tt(_9zH z5DEUtV;#mMH1#7sBx**Q^#&BdhcU2U-3s1$-6n(*5jn`u%`qQpqJ;MGCnLC|$sN*& z9FSaVI(&^?-ArK3#OGgYk3o`>QDC#s*i~sK9j5vv=A+{w!l4(FgHzk0xUIc_BLg^r zQ#$M9G5T4Lt$XPQLAz@b!NS4PYZ|E-AoaZFK* zuSOu&=E(#Ee1F6xR(k&ZjO~2%tR6bS>qa!iR*iNlg?@bV|wyPOCsk*K2oom zVZqo&o)x+sNuzBgH2u1#Z=>r1fA+^j0fCC(usQeZ;y1jvLuqJ*0kL}FFS3uuc*_oM z)xBN#O2ja{rjD(;(G!eeO@S7c$`C~~v_$SksE0K3-vTjY2vB6s&rp6$Wq<_zz?xSG z9>y#g!@Ky@;yzF(SA3#2(9##XNtY##L@7-yC#Mvb|Ez!_&U44~Y>{Y_DCCgiYuMWQ z*WPMil-s>bJ)^YZmPBh@iw@N0j_6{TdLQ~WzRmT}+|8TIa#Y!Zq4wL($Eg0({otS{ zRKKyNIutKSjX9jKFcLW+;y0jCc`bc$vrON}i1lZD4Hs6RX*C|(5$kE5l%stk`KiNl zd=CLOjx!hj<;4WY%pISGg`fZGA;g+DYP=g>h?CG&^>>VCYJLvU%~HAg7XFD}-;NDh z+F;)>l2`g}81Ndde@Mcrh!D7d%M`LcGLv$G{r ztt>x~$2bnVz|5@b@)(>;6%V<0STo5JsOavWpI7jb{X5}mc=pghR8t}832K50UgP0G z>bT!^Jj@7r+H~xo(X+RoZ8zq1()f;7C2Xl_zj66VzN*FQO57DSi`my%9*dS6^S!Gp zE5wdSDwO@g)cJEq{3`ikQJf#8HZ!r#u?5V{m{NNHEwVhb%yWer_wd+0 zj(>z|o=u4oq;r5H98?dqOi`xIN$Wq%5&Oo}SuRyuNL1+%X>rdZ{5Xd<{K7$TklfL7 zE^$^~&dAu=-jP4cTJMPu}#_}P7JGLy#U)(h@o3_953mS z?%-GUo3xO_8 z;JO-bz|)hDq5!CelcY9(fv8o1k@OdY`0?-ODu2CHNF6B~dU_)Vj=Qe~aBNp&=)@n5 zc{&{cwy>Fs#s`oRYuTC>+$o zDj6;Kg+0rqln-D&G47-qow%0Z3>#||5OB=LS=xFf-&(?XrfWVd2|2n+n3af)2_Geo zWa2+O{12r!aV1i+P`oE=LUf{i=6ekgxhFUpnWUKs{mWkvR<~2jOhHRXLA84*pvzaN z@DqFxv{%F>Bw*v7An?Nq+hP%_1wUCq5B=F7GzKkk4)fxuh|z-?oLMAOdeH_rbH6B; z=8chARHn-EQW;LFz*8rU<@u#f2&^~`+T+Puk-6}1=M@&IH-mO0dIZ;u1>nNgc@F0-9cA$vz z<`FKRth0?!^*_zDGLM}dwOD?Seww$*2s&qw>t~cI$=Z#Mn3v%N>1AGP za93$tT9#@s&WwjT&S@E}8GXuM?P!A|)lSQ1T?UCaK`9H8zHqE3=(dUEzi!@T)0$Y7 zFLsEcoC5Y>P_sNNP~h`nL?owv`i)v`qp>~DJ8JmgiK0r>`A@-Vuz;h%-<#cE9w2D& zQ{za{Ai?^FpJxP6_~R^9T-to3Z8Ro zQT2y;Yc|F@ux*26A7d_J#nKvmVDBU+MQ(Dfl}P_QUI09}dM6o6FA5za??j z<9zrSp%=+x)2eRmX6(|oe29K3pSKk7YOIG{lQ$Y?RPj?K)A}tBF%g8Lz#gas3k%sG zP{BVdqm!XZK68-u9FNZP(f8r|2R4z|tp{2}QnlD^^u~3H2p6GJWZ;WS0M?}|wibE( zWNgcOsnOEqx5NnG5gdjXHvUI~3H3j_d$fJz{lt(559+Qx+`T2B;j+coSZeb?8J8fD z=+@cMPPnyt`8`J?o2#9--&5A-enL%wN~rTS)%LI{a+ukFC{x4XAmz~WlZrw3?bE%` z&K1A6;>yb5U(edVlvy^px$2U()@6yVAtB+ap929_gn83J^}FrU&EU?{QEsI-ab`9) zJqwj>+MDS4hNN0XbIZpO@r!xsFTu>qk!mJM>KUgSv>u0toUb320tJl0(K{JVOo1Vx zuYO@}NuIVB3;bu$-$8%ent#-G6R0Aj^Y3~;q0x1#&PyjY$;z0gAMY(*7f=8uKk9i% z`wjU^4e5_vOm2$62&g~P$3B70!3LPV%oAe574SqMiENPjC=jw$nuc**B{jO?x->YF ztFUw~T9%pej1(`7(-%k-U{T2jt7nSP@ISx2-ahd-TysAtp9XAr=cnNzq1*~O6@6cc z9W{GX>BjfD`p5LH=2PQ#VP(@hBLeDad9%|lTNYqY(n>*o!O|LeAIJ{x0+0IFyaoAp ztyID(nr3fERPB(1!-K=_^iZ6!(D1t&xbMJ%W%|Rr0K#_~QM(#|@6iCg;nl3FZm3&s ze3yg;+YAOa0eM}be)GG%>=%!Tpb6kazwfA^@Puf7n3v^ZzQnXd4Au3MfU`)hJ>!C> zgOvVWrEvF3P^O$cW#jQU^nl1+4?+5j&R^x2h>V9?Tc)R4)r(ho_zWqm}K@A1- z^~+sua<_pCb~no-)t`?t#k`LaG`RmrfQyO3CNgs8K^V%~X^sM+SN&)G^i!{8=m}mT z9+Wx508f+vBYGrkRA4#xVHGXN+)t*?ySXMqgGwa>-yfytu2Yn~k@k}W{gCf)kad-$ z;*f3<(*kS&n!Y}qN(TRsCmEC}`C(vFto_Y~gjkN&ACm|P;#kQC9_x!fL1EApFe;5T zXvjLncn>N;-x{{(1-29VQgJOHfPJi+pJr!-dY*OGharb!&r&FKFyj+FVmB(fteTtd4 zy%*3o$6=mXlxKV>m?zj{$Faq*2Tj$Mi+71rt-Ou|N_6128% zKtySh%OquBN%1EzwZ;^|`x0^TiQ*9=@E{hiYeHlHl2J?5vnhB_$F-KntXUzCwKisR zZv8u{-uSAVlt1Ij*J6(U4k@%QtB(<~h_b-w6K;bbDX>R5`E=Nz zRk)4PEUVN@#Aa@sG1SHu6paf@7}4$GbN~rRH%QS#9}JP581j<}la;r%&i|nLCg~i@ z3CdtjN1H*Nss4ETRC;z~9B=vbf=j#Gf3o}$r_ZCnIFX+I+A7SQdm3_}FBd?1=v)xMoSzaJq%>-8Dhvir!jZM7|#|10Z7I8a4So4 zHamj4*&{A!16+HkNGUpucG1qnQvUs+`tQW3`t2 zpMYG#D#ys1!KfhD)`tI9QC&sy@@3(##y_&cOWG`c~%o6STv2d~E zM=wATIaaHy&^H8^Ej0nS!p!pcwh_W1q2n;*1=VQ#%nOj#^Pi;F8j8T5{T;mQ3mDR` z-|?BTiFT_L-`4v*T$H&NzgW1s^|&p%_YI)nYI-gVC@|nvXOy2?LwPIxj-9PcA>zRh&CT%<=YW-#UPcOo)d zip*O-+^T=p59k7F_2eJz1RAQtQ08BtAE6s^T7CCYBNpqZu}P40%n*mAUvmfo-V^%P zv7=Q{Qm}~b2by&e7f(MHgpAiIyDIrQ%ZwMICHjy-vZ4~F_g0P)&cpNRncxdHLM-X0 z|BC^h=+qsjH}+9aps%@NLAF;J83o})uq<+<{5B(<($9n+kgTimEzbn5X4zZ1+tynO zZ%JfsJ$o+l8j5gw7zd&&bk^0$zk<&<}Zf2W^@r;!<=9 z&GI&IHq}q(=e}xoFI#1B>D95v)%`WY-dVqNN+SWV`RU^Y$G;Ob|a2cltwQ# z#uCTMAT9O$>k#>o#M*%pRkqS0?<`vy2EOp!@o zXo*dV5o9E2bBxvK?hXB`kuoNTxCUDqrJCS9g5)YwArnTdLT~n^&|5H9a8fw_xO-K` z(I$)2)Hw{2qIY?}#JBl@yQS2dSjpc0DWueTd|5P~wfB<>ANn}=A<^k9X$oIj&`8$= z(oC)elvwX3)4(w1tW|d3 zQLe_e1^!Nz=cf5+M8R#Yp$tjB`M<&;ODFNKYEdQwzuD|WjVXM6-h<0M91+`GpwC)fP zGW;x6C65>P_L)`xT^t))C5Cq9J~0^H3~uwU?>H=lz|%0jmYTv$UoHVH2}t)>sQ$s>+yVn5lu|-_ZIXb4ImR=txChwk&DohROa|Cne=ij~SRg&* zC8Rap3H3eweev5tUX0Y}u^862Jhv(B??!mU=j`ANREBS=8#(4_s1X-m;aqP!`nRpP z+(s_|64l&oXgpX7+BJRrdVvE-dAiBxS-gsu?EAEwXm{HLbn(0+Q@-yl0h^Q6xPbfi zgTz4vG3DSixn{Od2aC8t`{%Tg4=Ls#22_!4%ZQ5vVsYc<`FUw$>*3MzHgOZ;9@>vyDFPrbZ=?>qe}dMmoIT;fraTH7F4Q*aLu%{rlsKPnM_JRi@I^Nx8Bo~ zv^~femIr<2P}igu^~0lyZc_sB2S%OoG)a-Xt9e&nczoeMQE;h%706yDgPG;ZV{os`(lVg+#_I!+BH&eIHKDXzK9| zN>@11JlvpG)nGvVl#iiO{%mZ>!a*UX>xlPFKoE8E!-cdScqhoe;b-Y;SQIol8W=_O zRlL#Rbu)cxs?!_~5q3WQ5O0vekKW2VD)a?7XVxr#-rh*!y^)plox)oC8*=NNcsJFD z@$U>yczI*H%MiUHD!zWDu^q2_;>c!mC`F$p%XJmKLh0e6sTf=Qzo0u>^pjh>LhNcI zqEb|hU|Iem-#G~*Il=VgB^^Cy6}Z~aaM{Ewc_niUA0(5WA}&!UH6vt&9)uSc)BE>% z#(N_(5#jJa=ap}3T$bP`maY4uc=ezfebF76M6N-}b$UVA2pdbG?8}NGA#f_!557$= z@%I>%NlX`5(9x$!!f~KNp8A70Oqf?w$|&4D4#*x+@p`o&o{tweOjjAqb8d<6-J|?i z6&u20hF>%fJkx6_3%so_;xokz(&gse*mMxh)wU6}RXJD59Td(-pH5!GFT<(60pGi^ z@)DnU3w*pTD5z?_grDE&vs#=(P58vmL#^o+Rml%ax!}xrhzx1B%vfZJTlCi>Dq%Gw zL_0WY<5m1?i4u#D6l9MlO3(E6;SlG&?5DKUEN@t~wqfg@3@gG;v#81Z4*h=@oZA#F{fRt7*gGAL;ZXNK!ou1Ov!ro^a zG|=go%|X>xGbjae+NO*kgFiYu^Y2`BMF8hk8`$)W2mGdrT>ZoLq@{2AY4rLbHWQPUbj@;d^+`2-OpeSg zuY;I~FmMzSw8zgZLVfZ9qhsqXhfjK*&OwiD6Rm2A~=I+jsu2Evl4KhqdM=rwJ!vFOW=hyNm92WY|y&r&W zf0C1Lxu2apmoK6qJ$Y48ZBHPSSLCO)H-fLQbZfPZG3}`_26_y zp$+Q8CjbR~Ts^3+sTc+T{&C{m{k6;P!>xC8Zz@QZb5Qy+1t?nGDHUxZ$(Zl=_Qujc zb#AFQr|#BNP|BFFNSaVDG>%F`-lS6Eh>K#A{b5W}*YJ;T>H=Wd6ZJTaD=fY;$h!kG zw`$+MnkiTzhE$6kM~<-2@DkVqb`%oVpAu?+&7l+xn)~!J;SX_-cHH%3kK++;(@KYz`DGiQ?W9PF14|v-Dg11Axbp8G z`O-SKP1Ez%rKaSa<^%yo%D=_JL0X|o-UjxLcn}xQ!GnQKCT5i<`CGrWw%`L-!lN!HH2Xjs z@v&@`+@aViaqlpgYGrrs7>sG{%F!F4GM;;y@<|KQYmf&!JBQp9JjLzrsGzVZ$s0Qv zFzJAShIcp)f0V4$5EKZ(Jf*I4JXgqlf!&gS#I<=JyR8NTEvfyvFQgaJfjXd`I{C(* zeO4~~Z{ru@`$%i7kcnK-}a2hooV_XC{mf7Y40Wm<_WBNTiajT^}I$(Gt z%X96r_|XV2Qe4Hogxc{wD53%mr;zyVY23{B24d$f0g9l38DER%5fh+=y@nN%CcG15 zqoh;KDNwmt^%`du#PyJu0>+ZfQ8gtCqrMlfKxMSdKPXhnr4SZycw0Ia*$PDrO>@wpARR7{%i zLNeK|oQ0&Jh31<=#7x@DMI{R8q zb`(_SRWjVyWBS<^gkq~G9jf4ljIWEEXjc{Ca$cv3hqo$?n&|ocm%~7ug{$F$DoGvH zdln2Cw-y_njk=mG9)k`v_sWc_OH*?f45pbTf<2CvC}`2oNVCAW%l^*!=Fa(x0l#d$ z{nP!^!|;D3L!b-0vu&jWINZT|PXBhc3!uNr`)N3z(&uLXx!ov3xH@i)7pTUpH?9KQ zj(`6LOmAmgXHhsie%9unx0RK}34C(h2S~D>o^r#sf@;v^&O`n9inRE}c~l85FwhYL zMIW$iInZFidzPaK@iiH@;RbqJ!CU}oqI6)>yIsk0^h7S{eP=jOFQ0MFcD)Cw49*S7 zL%k3;s`kr%=}!*)hx4q6L6udsVyb~z=jyDuj?PZWqkn%o&v{D>-miA-H<1heoqOPB zfU#-Hfm&{(yln*lLmrX2eeP|^q6E0N#BiaZm!6*$8tdyHqE2SBKEGi|9+kGDr?h`& z(5lGRpr;WcWn!DVU3$8eq#uZ_Sda!(!n2zFXN1RyrLo5^P}oXIU1(Il8W(KKL9HiL z(byd0URDuHMa_mULF87`4@=+0`f>q`6I#f)p<%?*_)7ggo$2s3r4wTsbvO&!1oxo^ zB!3`=*wNMqY-Kp6=2Me$UFglr#r;}eub#O9>Y+EEZooS1LpikB?0XWbKfQUl*+W+1 zi~{N8#o9pfyfVEh!Y-~meyC|-ZnX@gtqJ)Y0aw{&r9&|b3z&?40}})L*e(Y6hh9T@ zqQX&dqkvx$Y+OT(AS}mP2fldc(XUOSWYG&s`Dwm>cFNDZhU~(hz;_Mkdx{_T@tMm? zLB|pW`m^sH8T2lN%u(*m46dqchM*kQ=uU%-6p1hV$kWsR45Lct3KH^sPeaIY* zx^v^R3w->M?*Aw{^LQrwKaOwCiLxQ795c6&J2dy)XDGMaa^J_?G)xJTYhqH4rd$c3 zxxXPp<;qR2++~>CZ@<6ycx?OY^ZC4A@7L@3YCMLLqaHaoGw2;t0}uE;;8S>>|FpgI z00A8@90L*nPqFq_>AZf9NbW3(Wn*sjPXiH##}U^=_&^MZ&oWHs>hbbMnkAH^AP z2a`U-;b+22S2}uUH6_;|3sPM;X9T(xEC004>#yt3Y9v3S5D9a+z2m8;6)(FZQ_&Os ziS>x^Q6PL~I^gR+SSr68a{V4yFWUci zzK^LJx6_1P3Nao33L*t%$e15Yq)FaMvS_Vc4(sO$yD|d!Yx51nY5=@W6V##xyZ0)C zXqnO%3(;{|y0Gep0$Og$@S%ikS!-*TjlTr}`Q2PY%S`7uz~oMOdxbLo^=-c!*dK1Qu?#HyRQE*_X2#48p>d5_ZcTdO!kZ`4 z!?v@%RhCVB_435r&;&(|t$F#DF5p>^y0b^=@5<4(?CWyyFpM1elO z>D1RD&y2CszQzP?s_*IXmXl~DBnW$_WuxFc*+dxsSWM3v=6l*!Gw|O?bvL@xy?i{V zZ8UQDD>sAN*5p6$7fr{4p>mLT~o~E(b3;{m_XUx5!v2&8a8J-M3z8%_IN?& zr*dxl;YK7_UEB&?4%@u@*sHVV@#QZvnb4pC7xK^gt-wsUM-zR1QKgTqz%r11FZW0{ zd~Zp8)H*y5Nb685Emk>SPI%lNN7)k#iNfT%4#$b=ls2zW`{L>evjHS00?!L*;0BT` zl7L<5!Qu6rD`xK{DaieaftKj=!<@`qw<|r{l)e`?+}Z};Nw4CSvANTg76*wZE+qqnzr1r;yqmA3qEiJD^s6 zWyD+pD1ROD18!6;2-}9I1{4T6yZ&~w--H+SUi`cGN({BZ{2J{hitp?;k+&(T+tPaE z`PngPg!Fv$KGEYh(K2m!Vi%1F#_kk>Or*&h5Z zVY9b4s>;nlFkDmXp1;SdJVk?~FwSwYJpZLpY6^7=m*u^-NU|GTh8I}hz)*=NN<1Z-7|nTz}A(Cs)jGm>d@M^Pt!MdExQ={^7PUa=T*5>7XYx)oJV!LDtppg@t{%bo0V4lOZlGuJ5P|PPK z=2La{aTO(*o)Xz)%*!Qr6ek4p;85}e9GCf!lsD$=!<_lM(<<%>Drg2b7x+vEYRSZ) z$?}apUYM?odc=z(!{k0=$p@d{M@Ip&3J|@{v!Fq?vxK&^o|bE#=3aHML=G7AKP?ER zP}+o}oLYIALGd9oJ$aG8|H8xZyH37mdSOgHC!GyfuRq=mIbt*XLMJkjS6WYL{+>#J zVIMQz*w5Ri$DD>rxAB3eX9{mEoHRc7mpFo7Fv z$6Npwvqy2=tZyaozXpOOlD%0We4J`QkkuX8NL?x#Ckzbev;jLajaMvz0+se-gCc+O zsLqVA=;!m<7$dn}7`C3}=+sL&Q&9sY={8^XQ7`=)lMX)IpVHZHiqdw4ynGkN4(vgs zxxK=Tmv5kn|()_fkcKRYa{M9aQgk-)M}F z?#O-3 z?G49bLgc*oZi*MpD!Tj)NRbQxwGzn3K~)WYr^wd0Y^55Cg@`v1HrA~p%bi|M6^ByM zfU~dQ4C7!dive4>lRhT-HCcGt!Hv~}jzvfIj0(GIMzDGOkU+;^QRa|KjaK}x(M0Ie zv(Zm%QgR`$_d$H0t-Zw`OJcIkbkS(Ts|;&y&34t5E&e*;*7_T~P zL?nAn*sN9;!JcK$MWNW)x0Q@WnpE2;26+>vpSC9SNcj$;p{`0X|%8`nTIx zZ&GNk6sRl3=?Ok2<=q{ynGOVN;(xnW?l?^cx_yqfFFtV@t=R3yY}0xES#UetVrKX}3#r$4$&Xi&=tBF1A}GA7Uj;yw#$=%$Iu> ztR9>;O-wXdVmI2)1};rexuGRy7yrnYZ4!CKvB&GNl#aM|SQqKwSix9waI^Nk^oZ=( zoS~7jbAg~!zd}#O^zC{JTsn!3;y11bApBQj(`M+E(a+dxYb@6cmmo{&%I zC+FD(=r!Ra;A=L$k$@t0SK^_+VLDoJE5)69ZTj%BwFGYX~+~&OU1{ z;=vdFrI6gN?Is7@Ai<%5> z3m;~wv1RCS(z2ZQ!Hp>_(!?cdp7&Xp^=xWu?pvb6v<4HnmeqKuU;t;U^yLr4Yi4_+ zbbj7}|IP>xf~-)Tnco!A zS*R;1rJ_su>Sy2(GgiOtPCRi+^I6(&{$ToGl(5)pZjR|KlUZuX9|!n>o71u+KyZBE zjg{g4T?UBT>vV4WyB>4a^67gna@u^T>wFc9Anfq1zugA7=b{KA2cxNsAWMHh9WSyA zg>ft7m5O(#$#0e6qXKGxHJ7t#86FIxCh4!z1=nlxqbE6N0IV8~tV_x)wErfRSd|SU z**I?E88r&knHAxyGuet)+>JFN-K2}-e@-yJn{wILngr5GGXJmx(6C9k+ju9pr#ah} zuJTARk~$zx=syW+pm)l}sq1$A4ep26mncEp4squlRR^mO2jc&z zYr6eGWR=_jsCV#b4%!*hK`40-madBgI%6nfoxvOUu&SfADV85HlAoKc$* z{i%7~!dvA4?)nza{5qaCYIWB@m09F> za@^Sk#iugxQ4J+&5=#5VcOL94nu8kJS^LrD;vfi-eOkRHYt-8&P-`XbGCJI zWxe0RJ11E6&W+7m?(r67%hwBEP?{3!v8zGN`af_ZA4a)H;m?g4jOAv6m5Z(;EveN+ zQ0T=4CzMUus+bN6<4*sp@hXNQz3fGpKLj!X+;{BP%07Z%0Ii?ntb@4d>&#%xxG~;M zl$I*e|5{ixobP>O^>##yoh!a_YIwV1Se5IvH}LYw^xO&IigBpm-vLt2QqUH;Y2qI2 z^6-?0WY>Kjgrul2L+>R!uS;x(7EqSp{<=s1{^{!uL?829j*$G@&;RXGGHQI)6AhmE zH&G1TEfOSILfn_uJA7kiQm>6aj^0|G4!e=&hEt7+6iFhUw9V%tahps#y0Q<)wL@FR z%?_7l+I<<0DfltKp|2*)H}VrJ1EqMZ@f;rZtd>k>MjfeM@LZU*$5EV?+v#AtoaC46|torIW%;$03 zIo|;J;DjHxJtMQ!Kl^<2wAWYln$wUNWg3%eT6#y~EU#!Bw`u9*F}q51O7sjTkH2c= zT1(Z{b@j84QUm1;M0!@Wx3^0%(6wv?s+M~$W?9#>_=)wHf%WobI8T}$#|f=gtZh=3 zAdL(%3L&rODZdEC;ff_Fh>iObZceL|+b+s~ZGXL@jqHZAxj*=am`%N9^nz`?#SrT%Y}s7|gGPeLdb~m%fyyYw;ZH;$7q##7v5{t^qChts%2v za3k2y)?6DKe*$@VlOA1%#j9jnwXdX0^5E4 zYigQX+CNMz!Lfn%H~1$T&tvC_XF&vXfU2$K{q$~Pxn~$No|bx~lvfMT_kB2Q6{Rr< zP;b@8fOvypSvIS9Iux7KFzil&yfZsyyp^Wn{GOr!LYJS-rOkZ<_&}6ZnonsjkaIRh zYa>LrXaoMVR_Tc)%S8#Xzk>V+7Bss3^kZ?i3-^jWz%f_WIy-#q!3`Lo{1(;Me)f$0 z@*n-*Ghk8QQzV0A3oi(qZE(Zx?yehegZn1-T<*Pe_@zELl#|#Qp(mx3oy{sV{JoyF zZ}z!c6-z>Pj#L*k0Kf_~d-U|?vmHN0x6_Q5&T)Szp(XR5obZ{x+o;pqCX2f3Y-@wP zC+(6q_&!x)Z&o4|FDVkZH~j`U_H641P|AK5=}GswIoIZAyLY*WQ!kq(|63VW-h9ZU zwg-OGTy~D)ZfE!`#OJ?olno*uXm*y5sn6@zJEak^C$D{2c z$|R45!7tEI7J#W7Ipj?QBEP{$H6Wxgb%=8=82x3z>~%MD9|Y!X8TDro?yuOdS3w=W z5;?%5x7sXU_qR^j@o>Lkx_gM~t>u_smm*s#ERiX*>pguFw@y{s&AXWeg=~xvQ;7qM z`&1DMPU;BfzJ5d3Y8?)go3RZ7@g!bAQz@Yl;^)HYLP_`k5SOx+_awy#MM{Kc= z$Qoc`3%B8{Rjm%VxbxomGQ;BbXuor&tIUS^HyIfpsC}yMtev5e;ropAD4EOZSWsa1 zwnBK}K?)`#Z9Hp@s<@Q$ok`v`NzGOp%N|Dwy*uU;05FCGrw^mnL;RHcVSBgivj{}9 z{Cm_?6>K2U%)4}{uQoffuyyoJrJBNBR)(0iEzPf* zU^4QBxPD9*{Nd(<(N^xMD8>x@Bbd@Oou9_*MgN-lRU3)CP+T?T1SNgB+m*DTG=iLI z$_RIgj5>d|6O0*wW)slU1?FV(om6J^DRcQ4P1?Y0j#d)qI_ zmlSTn`s0fc)%Nyzl{vp8VCLsOid~e8LPcosE;b;1c+QrH)^p9Jkeho+q!ZHG4Z+=Q z$>1ye+Wb1Dn*$H8yg&4;ZpaPeENQDY*r~JeZ=C)pM)3Atvlwrh*d2j0lI(;KAI#79 zkA>2X?p_{Tk`JcIk1zHng8g}Tb_qOyzfEx$l+!8Zl&II8v|hmb_vaY1^(UYWQYX z0n6cEQO0TL3w9=szy*5jTVVydio?04e5*lQtzMt0ZivCExJX|B<*dY6S?gTF^pC^M zP_2%p-FXgI2Z>#Oik+V4{~js+2ClX9B^4u*Y>pYFwoFryMP-Okw6VR7`nE4Ho?krHBd(P0lN5pA5iVeN3axt zd-^7TYx;}WDh=4P3NB=ES)n?7XOAS`A!@wNjbU zC}|h1mha5Hby0m41+9I~Dn+-vs=}2DQp?|H&}H3KnEk3GYx2fb4Bi);e|a1%@8yn9 z*E4frhaYzV-IZ>otMhY;q4Zf=pZiX%{g4o{L zphk^W;xnXOx7EM&5JM7qUD9><@++J#%B1#hX_^*|=h^vmJ>jIQW&kD`NIZYSmapj~ z*CVAFl=GkCi6mdU<8x47uHa)Prf-Z_eKl=hnJ-^Z<0|Ss)U~9ZdG3QIB*x{AR|>M^ zLLcsqr_?(C>7vKxfMm3U=kN1kZ?h)pI6B&R^y;L6T(SCT`P5&;h@D)iQm;YG)U~%+ z8CZ0OwnoZ^?4-pRxd+%#0BR^=`$azFYhIJcwl9@VL(qbjsdn7FMpeZNfedD3Ztm2J^iR7jLkc=mD)H<<}N(tqul1 zPN+V7avm(1i4n+cR0PnMTG&Qf`H4e#sPqvs_d+!UmjQz}UYe);`<3#i(otmJ<0d%( zn&oL3O5^IPafLbDs91YPai)-iLCTX?Z&cs6ToqxCGj*jR@xLsS1;bLS|#RWA1bUiSC%mos$xqf<*%*yh*J$M2{#mOM@L5b zP0hyO#s{<>x5q4**sT=+8kQ8Stx6+M7J6%3!CV^p2ocU(x{?#ff;F0u{W+UW)PkR3 z{Gv(T`-bzjS%=4$`FG+j_M5-$olL8K@QqJjr1*JYHY4gwPh(I@^S-o*1}}F}Qy3*KX62!uwIBK^`6+>^H+AslmjAsj6Ke zDeUHXY;nc<`a0G?@5Mpbbd*2wWBV=%UNhjeLuiP$8OKYE*ctG`n2#>b??YOYOq}N$ znv`;?ry}`KX0(~+)|GRq9kWf_#zfM=$-yQM@!xRVsV%mAJglM3fn8+GE+Zz-2C*F5 z{_4==ZgJi~S&=dxR+0t{bzSWnc8S@sybl=}nQI!#%|fgn(UWA4{zI?RFtST% zAZBdHlCHnMVOM005&?GnGr$K?^EKs$yPU?S2a2b8{YJZgGYDWP7 z!-LUc8ah-h4T?{p2Z^&E`J6l*TO2KwvMT@BQiSz+$TmOAEruOpZ)PhaH6S0)1mpZ% zA$^74JkImAzjjVWJoBo1`omz20;+*%VwWae_HAnIzA0!*z6i}Nx-PL7K@TLs%a})X z@L)bY`uM3FtPeU2aI6ynNnUg0h85DA|HzbDqD3=274HUTN!`tQF(?UJ%m3Le;+PLl zabJ}RN#S6vFQL*B1%J00E((rx;@P~GxX5x1P|N!xPWY5{@Fs3oSSZW-b$aIvp8xd85GvZ@%PJr>Rvkq1m(74)!=W3K{*Nu< zQa7~kF}bcI0ia&OgZ!D%92@)a?d8qjHQZwf$Rwy83gP`@W$hE z=SuD?Lx6NO1(vj4O|F!C6CY*dgHyoYJg)XQt|vx8Ul>!i=O#?~Xi==% zDhBHq09S6Y%TU~3ofkuhK7<7p1gElVZPu(ZqrpDU{tc}vrc)COl@OLy>X!ASPa7ao z0}Sxx%G{$#u&f)e*y<#=zoDeF)8xNO}3Q3q8c&l9k;!s#i7Sne=QYDS#IW-QT!!}c1Z&*Z2bH% z<=ZT7X9)%=DyQoV4)ezp5G zWNBTtSI_YGl)dF^XLmDt`&WY}X78B9s1bd7L2w45 z=B5sgJ#pnE1#f)P(mFn$u+@7FlZt|mwfLU?7`CDGBd=YCoPOq7>olbF-wqeYO=Y}5 z54mWTC>fQFjt|cW?2vEs9k9<|9&x2Q;2u-`-rhEv%@4Gnz#BIh_SSjtE^>nh;1#>* z%x@OuHp*X$d@IK+sFi2`R=0=^tOTjQ3BygtcoRINM^|7J2h)Zu@~C;2;EDAzvQ{Ns zTwqk*rG0lUNepVAtu`@PL;9SyRS!g3gYiXG2XueRKtP|St5g(qXP3vn{Cs8I956FC zKiHx}b21s;RO?6lHGS;zEx0wtZhUicDv-Nj+`=c!7xsx5ta>_4k=-~4K7Pn6BU8gx zl~{jRY-}%u;XLp;jOy`PeWq>A*;Ih0X?CY#74ZgRqbQZ{w%!m4Ey{Vu<7Y&*_zgCA zVnNf#SJ986`Ev`+p9O-9ELq6VKyP`yL<{F&j}$Tr-QI0}BsQ@9RY-sPZFFdLj#`2C zT^T$3#-PpptVKG<_?c5Zw4Ie{q&WG zMOm6VNd-|{OVX0;8}?<{VYuU4L1|N;G6dQcV3+`0Eb$XsmNC3(4*T4*Jto?)zAA6v zDKQx}sp5*y42^;vM5D)2?uiq8t|@#zVE9N}1@a`ap1rKT09WXga>&`!H#;q)DD)Du zlGD%&ijfyDC0R55277SVsto+Jf0i8{wP)fi!#zsO6j;8c{>e`0eJeRg&dPb$%4_O3 zQNP89s5o0(UXbh{QR^v|7fLDY#V;&o_&g#pB^WV`fW)~J=S9*uSblRrEV&bOxfd6c z42C39=>7jl(}^o5T`%vpA-8|rMn02<-3Go+bGdCmF7eZgiTio)D^%qb;fH~>+0(4| zFVkrOJ!zYZoC6mJIsk4KePb6 zuiT_wpB+W!uVYfK#JhyQEN66ghxyLh)nDv-tnssF{FixkF#m<81k8peVhSo=)p6WE z0VBM}C#Qt{{X2J`1+fbweeb^e9!I+5x&L;nwqB<5 zM;+p)Do`EKQW<722l=h*r29B-!}fQb$L&X>`vGo2(#l^)SNk2l&y|fD+FFd*h{{ko zezwg0kTxr}!bSsJEYF?LZ}i>SD0tYwsJ9UwWIh^|@`DEX^?=via$_+8wUB?|^U&Z_ z48Y6hc*|`s7h1nyr!Np9);c>%nH2Q?c;OFUc7edvtK3(Xtd|y?040O^;>Sd@tCm76 z;W`|v@t}a)t>v7tlGpMyz7a^Bh(EgcCs$UXrE|6_B|+D7huiAjm8dbL{r&QU#3=cT z{M2!59Om2{+mgKo4Ge*rnqy#Pt0fHU-=UG<&R?_n94?vddo?4~Vm)vruiR)^A!r*A zs{S=fMChkP=9Lzt)A*cp^Uzq=3$2gL%Vq%KR|#e`VB7oox?bCavg<(|YJC4dcBLWP zgd&8(FeB>yVZe0}!g??A)#~wjf^ejnNtMy|M6H1FGY-4U5y* zCU9ppuFY2&wQC?yfbh>9QaU!@1#+r&00^ zO;Q?mwZE%jUDk-H&~J|hXk=pIEh!U-s>`6I2M%ULN5G zB)IC%uJqW3fW-HL)tD4Do~G^X?W;4X>n#!!&BhPA&SNhxj!$H7;MU%p6W_1+CBQpzlG*yZ)(ev2k$Cv_9<*X5Lq^`vyoCY@-(A*gypFC;c>VbjDK$(Szh%jX_3EWw38_MI&4G>j{K* z?Y~^Tj6OoHh!3KDn7G@lxT1#@~~7kW1oLVa`woM$;kNT4xDJ4Q47N6k1Tfu{F6N7^nwQH?}p}esJXl6{UE1>F}S_ zaIiw9XOUpI%bKWGe#YB}A`NUA0i+OzXJ8{ae2C)c`v$w}yM%jJWsKqxk`=bkoYktO z>gOQ5UFZLXFfR+JwtXN&u%}E;PIF~xy6Wue3wSnHARAznGR4YJeZF46$pN#7!prq8 zt|s^R$?vBDGAETY6`5$c1)LDoUpr^ap5PknbCq`-5L8RNUM6Nn8pgc#-l_p%E<1Gb zsvnv%m=b9IqfxUo8UU{!0Du619zxwB!h(*KS=wOv&dqKGOFj&*yZaS%cE8(GjJ!d( z&`{ACcv3YM44`;F+{qfc`98>Kg}{V(3M(+QpT12 z!3nzy;5Nx!Rt9iK>kkHb>#4zj6E}5v)@&CD^z=cBdYD)LZgTICYluy%i}T0c>P{WK zs~LsGoc+|x{u#|}%(V1~YwuGh_gK~B<)CMEg3r($0mH{33$;1vzg#%9UmX3@5;_C} zP_&ScBg+)kQ*~1=48Ff3dY}LGBK4w5rb^~-7cPTFHbbV(L_qv{=CFpN|J4-b8Ld7U zq=#vpIjG08$>bG*1bro%_jpL7U5lTK#fz^>=5(eUuhRSaKO=gW83v64C{ zq6qD6XX#t+BvtZ#S$RL*WqEl7x;aJ$VG;a0P3*tXGN=>^dyD3s_Z4<=vI@iyy*Mro zgU%Sb$R_5#SEQi;mG^6T>u(R`1Gk>|Vcb*1R?$ zj_E+7U>e*z?lkMI#)(GXT1}%4U-R9kQhr;R%9uhAZC)!%ScGsc0297GUb%gm?ugs(cpl;JBz`*eWqKqyxQ_MKO#BdG~$+DLsRnL6e zP0X>+YE5^?5A9v2$fx*c`x2oJHN$rLpS^c?91OIwX7))ogDW{4Ddb6%wPwUj57sen^Fg2J)BEl;%E?~ zQ-GGdVp(NO{VFeedTq2(z`n#t)&n@f#*KO+^;i4eC>Q%QGKqe;01>v^!?TV zYWfV!t64u-k3aXpMDH-eeC)NpA-T=I$qudseww4G^{u}6M)-8U*-yLJW{06Ucp--R z4V$NZs%d3BU;R6;35U&6b|sD)_}IG7Hhef{daaqtvD_{1;CCc=abRGenM6To&NXh@ zNtnkt#07cxl=@OQWV7URigw=oVSr+zgvTj4Kc4uOHXXxwZ~J6mxcy6ejOrmPfcSsa zx>&`U>`#%oIN~n$1}J^VmtpVt77tornFVoQ=G$XVyNTZ769>-j{{A&Ic}w3#;n%gF zz4f1Oj}r?Pw_8OzOfWK9jjRVfXo%%ja~QCw&__3sU$u^T`h|Uo`_eQw;Z{_RMC zK-NfH>@X(V`_qr z&(=wBlhZ){s>^|uU~Q`6Qzy?4Ou}W2Z+X^K?#iIz&M!`tYS<`rx`(#CH-RaYyyv3W z_L8Zj;}%LUXN=15DmPElgo|n}vd!)hyJC9Gd=u}<}-Rgux3kU zm6BIpw$OczIHj9OfIXd?DAIaZXV7;d@93ED_4$D41cnB> zp2={rcXkJy(jaH{SoaUJI8q}*dK&oirh&^N&{G9jj<=Oc7P~H!L(M-s)D6Gg424^q zc>Ze0jp;~{4RCu)V1Rdv$Gt|5K=9iixNpfFV(^Nnu^@&l3?X0eKPzhDQ&8t zJyu5c^Z?mUer7$hB&V!ektom?`ddPbVeyi~i?@C+l!aYg`~C8z=zSUHtvY?ZslWFAOfkkt| z$1qNEqjHzKm#3GCT?SPm5#Oi^u&wJ)Vx;XAuL3TvM^^PcV!#hmJ$G71DFAG2{uE0BS$wgtTht-N%-C#$detf0=~at@(BIe)K^sH zKfoXFdvtwD`1yBGlD}T~KN;#(O~L`^{Cg#6Y38+51I#Cp+JFfD#zgeqR{!Cp)3-SO z4rTcm<|^=~aS$XS_E#NWA|~^7liv=kP=iPe;JQIY1EE&{*41KHjjne?4K#;YsW*?U zG*|Qk`8%*!G@>IAr`PaY_wrA`{z=5=(&*>ODSS6}%F;G7z6weR@gUm;{L)fkiaRcv zy*t$VtSV+)GfYsd;xWH8%$4)-pP2z21z4G4G!nwCqjF_Trj~C{-Bh2e%2!F#Owvo~ ztI&~E1}{uniWa~pW$;;w(baQHomlNYmHJw;qP|uiPhv9Ew)&=YH_x+;E}qCpk{$Po z=T@OJ==PDsHPK}6-Ys5WR}LRM`jzlomv;ENTiY3?;2Naw`?Ml=`SCyPNTJe(NBmmS znE1rACQQfRr+dMyP9QJ_G%>DUg%~YpY&0=T75>gru>_uu(LuQpB!fjL$en2Qhl2Gx zPA*a`C6!4AOnaY-*0`Br0TO1wjt|AX3E2Lzegc;M(1mie27?fKzmjq2q}UWz`L_+|W=t_qtsQEUyFYom|% z=kZ4u4r=1dhRk;l`J$Xuhi2%OLsym{*xR=qbN%dFjYi-f)_NcpAu$^S@*E$PGS#g8 zgy%slRlcW->BsaR!`nMy)dR9p4mFmRjO5*#IJJ(E$n%pEIjrTxz_Z1!BuvJV!n8F4 zS95o_>6HiC>3bVj&F(Hm=|BGtuza2|eHhqk6q>v+mzJdb#ajrVZR3GOb(=7qt_<85 z5#%(lN@CEqh}jPtwV7~J9%cpDs$QHlweOG5tIo}A1tDXjF4Jj>AGiG~4tlg(HDTsm zG`a;pJpWZ;R^djcVa`V<@=^!z{uKm?B*1tQ@m7nH*%*})`Tc+ zBqP%v^W{3`L$1C^d8PXr(ynF#X&rZdz_SUzJgA5}X^Oj~d^P@I)NW%+Vx!FQ(S=b}4aQnyoxIyLis4e^gOA+P7^(`j(Yeer zE+N(ln@{0@9fFBLAanhbs@CH#A4sxe&7UZnl*f0yQQ;#o)7BAK4`+sWqCZTKo+jR{ z`JE`#OO>HE+;`jRZl36WGe4njUJP{Bxz-Ib!T#q8LpI)nZK> zL$!6IwokJI8R8xpixhF2YN|RI5r<|z|E191-wDb(q$ZHx7FE(+Bf;}*hL-qQB%1#B7fW(pQWKg_c3AWT))IdN& zccy@rAxD%XW?r;<6OGp`a#!15G%!p!tetN5ko+r2Gg3R<^)8>!yN7;SAQ3D5HvXe_ zn(oj2x13DzeM{4!j<&?mN>&&?EZ8MCQ1*;t=0um~Hs6#^q>|cl)bJiPy|i$Bn)d#? zu$aE5lCUQn;$kuZEZ3i+E_A!M2_6t>pA}}2Ybw{8OGHySUg|{r^61r-qJ`i5gL22c z?QmUJ6Mc*RDWi<~oER#}$eR>O5$estH8|PPAaY-o`P4Kp zA-)Siei&eqGArSlZDMkzl>j>b;QJaT8MJ#`_kAm0<;#p$aS=L+-j|bpc8= zOASzt8(p-i!Fe|l{FW%>fv^|xcw3aj`(2+@r zOh~fz2i8hZtacqvR<|e}6zrU1kwxE-@-kHokugC?{0nfINj`vy}Uk3&Jc6|K*maf>x z#kuQy!>5g#lfeChgVVrh&DZ!`8^M&Li;MF%ISA$atzyHE1T}I&#|O$D9e>{SNUA%-e}SpMk9!K1hsn!aO&Lv)dDCoSE=Tsn5x0Ek zA-vKu43-eR#oiyx4BEbBeQx$fG1HWhW`Ez+i~Fm_4@!<&BviB~nlsA3h4?%ym>FWM zyC1j}TJS7;NdX4c#HX-PFwv_>(XQTD$&yUR*H~Y&GMq9gob0cgr?1RAwDaxN6+>*w zMdH$<0Ji`uJ~=4xkI~k~+60-JkYj#mk>v1w}bqc(hy z5bf>plnw$|o~2III{Cq}r5PWJqcqE7^HO!wW8OE^>U!&JCZoE1+y`u|^}Vk`ROeIy z0oJ*A;~l5UUS!zAMd1dGjzDfNc{KvaYjww7j?)SRK+w<<2@NtrcRcRTK1~%)nc3%1 zPr16&92Ink7O9ZY_>by~H%%=HRC(vE)&0@{{;8yo3YVB%H?cHyLME3mt0Lk}e&nALo01 zbxBf9q=aSCwp|k07lF)SPi~p$Tz%%allh$vz|r~j>FXj79oOLBPJgT$PjpAGMDv@h zSlxtiS5rr1{|C-wDJcA{sO(_-d%HLKRy^Rh`fR6DR2q-CKFiPgj$krdc4Zhk?Nk7E zZMeXN<2F^0&x?WrDQ2feNIDAetKo(S5UbIFqk?5{G{csJWB<&4wHUH6z^{%E<1oIe zbRRItfG*v9Dlj`tW30|;;L+Wc-*K^bIZ#>GZz$y7W>AmUl+0Qq8|x-;19OvNN?T~O zr9;vu-2NoYpuDxI_%OQti>KcOmes=El?NrBxTI#Q2a0Sx7&cK2hfbES%5VW^qJ1Rb zoNw3+1AHt6)TsXK=w|L$rBvPTDiSs~)~P+WEv@u3|#*u&ZR|tsPHuN~N^k6`IaVAD+F`mv<6*yzjfG>zXpJ)CwhV zAfqVV!#}Qf@aq1d7kuu_VIAcAWGYDn#(_>foh(NtUaE=q-N2&DKCxvSU*UKO&=?NB z#*hm0>;Yw{wOAPb%lOybt*`3fv|H%~>oHqJQg(cUgIp!!N)r!f$s~#L)Ql=pNK0tF zXQjfE2geUsT|@d@79fjY6)``lTdz39`E~Ul2H(k>X5r`mJu0C7x|P>V@Xtz&I?XH_ zFUxU;7~&Jp{rEf^*oR@Q=Jc1_PX4Bn9-&!IsI;(Nceimr6+vQd? zaeBecofc9Ug?)yP+B_ZsOPTx1N`Ie%f6!mwF09bgzq$K7Ghg}l`MHZVJ7A&9PZ+=j zP6!$sgl2xG)23!Ns_y#r!u>S^1WTt9WR^SdpR#_@Nn^LJd_U1A6J{h{mZm1c^Kl?j z50k26NHR==K>3DISt*5Agk@^XLo=!gqf;jgG^8)MUYw%0c~zG=P4vv%D)M6GZhQ1O zr4vBnS{}0NZ(9?oQIR?!+1skPG)xsQjo3Qktmn2Qu54x{5j1CV!-XS+p_Mfrm z7oqpBl^$JeM{TZ!HeWHge|lH-^3Bxc(Rpho=UnXh$*CIw2DO6QOWf!B^TcnpZuXws zowT^){of@{k3=<5QOv`_+8*oyd>Zv%dnzlvNN_?s!Q)zfi}>b8i5vI^@21+Ac>@vl zM-}~p{<}Oi^X;v@BL~N8#c_v{BM!~FC7}d)U&1R3#o?izyo1QC)9u)Hu{;}5NKDMR z%bys`-HGb}@DV(to{(%U{vONK3!&_eXPVBJmd^8v<3zQFmLy+<&8Wm&u(4ANlj}#K zrsJUyjG32rMN)8(D8}dYZ>cP|_DQmN6~#5AsvPGGW0RGWks~QlZZ8B16h(%0 zrQuEU%vNpU`QV4!P&NdpiwUynrkB$f*3O5iq@efjg~}b+48SzHRc(NMM5~?;kE`$GRaQ>dt*}>T zn^Ag%(Vw?d(Hfr!?#f+F`yuN|zICpWh~BFPiYyI1!#4aBea zkN%#wJw%bApnBF&JRU7m+p>Kh#;y^bPAT6igMBPWqLy~OJ2l4r-iE{DGcDwbkfqLG zkyHv!c7qJs$3z@(pltQI!2p(A-=~pVehqB8Z9FpHif6c)GipB>ue`I*=OK-7v@P`nxP?;pa2si00G@(|j#E|x*vRM-1 zJyW?-O)``t5fMxy^|tz=eJPSaF7fF`_$?~fza@Y!)5604C_3+Gs{cQZUzh6I_aaf| zC8dmO?|to+i^|HrzV_ZTk*j2nd(D*5Rmfh6kZW92R(8lr<~8CPzt8V~=en13?&tk} zy`ImmZKQTI7cP}s%L&`8y)qfoLI>hnppEiuKFwqEoLL89Jg!1x6 zaUrqF(n$&CEcD#7VY@n$443S|*RiP~hDcAEXzdKSo2EofMl^#oFHZhOKa(=i{FR_s z?**dFDFd9+dR+_2D(-+`2@WLLC!zf$j(J!)`fnNav8#b}dVW!KIcz6!6uMRH9n+O^ zILO0HKlLD4GE?Ju1-uZX3j(aG2W7gLk~e&$R8Pnpu@N zxRL{hCv?~@y7w7+o#bFuT$z)M4yZRVejuiF0lmJJlJe(tiDMr!)lV!m{Z=-LnTVH0 z7T2K6^x7g|dgVt^Z$tNL5K5h8IL#cZYarr%7Vw;PNi!P6b%@q^nyC;Mp}JdipKqYN z1kMA-U@CC8B$&W(tXohM;vqYtN@Nq?94w02ea_5O*4v* zO}_aU770!DB*hatm>Z4~9=zR!gvS)y%vENBq3sO^mFPf*=bJ0{;M)}2ASc$vhiiI0 zG!vD)X^8EnaAo-87x*Bhwa`^iPyJpq-)+Tfpm2pzv3v(Q{~WlBdu7Agnu`EkoDcsJ z{CfUlSwI5{PvkO+trWV+HFFrp z!uzd>KK2%?_nF<+tv91Olx&K;W`JRd;8u9_~2Z#{Ql_ zF?#0n$R-`bh-gt)mHz;ur5d*^yH@)Aw5BN(XaVKkF*VE$*_ksi zY&U8!if_R-I2VSGzAfSc_8lsb7xg<Me)L5m3 zohNXsC!nBu5Wpbc^b_3j(5)U!f`RvU%_O2fb%^Wl-MJW?MUX$OMyN%2-nq>%NEDS_!FkOneSOdF7f>i%Up;vHaNGJP z<>>_GQGD-Md6ntxuRsyqoLI7n0i{29uL=ZlV7~shaWCeLGf^xPRgJJEaQw~E=3pT zWeJ0po;kN;u(n>OC>_3&jp}DAt%lD)HfT<03O_AXb1eHYA+;@*kPDx=?ib8&XWvxp z;>@K)r}!e9@PN$gDqb#WP(FVZm8~%UIWLjlmUEUqhHq9$|0dBykv@yz;<3VO`!dO2 z&x#=kIGCwwAc>20u_unfO=(1?%S5{DA$!_$gYLQXRxa2gBWJg)9 zFLgUK!8|(u7@^Q4rzRc!fV)(`F4r#iq7xCU{fQfQw|a?wA^D^LNr@313{fE@AVKAc zENo>-${sT<#r_yYMS&4}?iS%>)&QuH6Cv8i{a#fQqZj#aM?oo|v;(G8p!%l6PXEwu zvQXmFytW7%e9RJKSHY}`zJ9GlJpD>K)hRdA4o}`4sf4=?B|ij=3+j#OTR-aTZ}9S% z$jR+C@$;9gWFmwu5Zk?lMrS_XzUb+Rm1Yx}Jq`KyFrcR>{vCR@SVT12`shf3{S`;` zoiac#w0{q_S_~HFu6hno?2_X?*NpVj1FJL)h%xKyV>9OfR&|ko`};#{;g&Z;g!Z-w zVK3{RsZw>$D}4X9Lb(z}Hw&MUJ5FoLqKx8z`|)+jXMOyVAyo$#x?OCCck5oft%JOB zQ7xk6uYcezO}ow0^%QHXSJA643|ifWa@kkuA?>}~%w@aQLM^iI>rpWI%TBYKtsbhQ zn#b_Ns4LX=eXk%mt_o=#`1^PzI{o6QqTg=P92D5Ee8*XDnFj;)U?vh69|`p7_khxO ziQN61=ldzkyi!su6oy)B_+sIQy7bpRCKeAQO{;tYFq}BpxstUc6>3IuoQaGOD%~D%VKbA^?R0%wXbLT) zB>}5dtmgJic){;53o1Tq(`)bS%g)1Eps3Aj)FXj zGfWw3H004eW}T>YTfnoJvT%9+**j}+ZKMe1st`oG&=_vY7gGcvFl(QTL=~>NmQnGb zCI&z4Y^~3jE7@BHvOkI3ez;>cQPtM&^T_v?E0ban41@r@*p z^ZYXW1im4vFdW~I8TL240F5-r!?oG-&*gMywB`I8^vhS^9k;XydBV!LaJ>987#SCX zD#-+=p_-DZkl*s~8@0o_gLY_%75J9y%S>mBMgUV3LO$3(Ss4YuL6t>7dLJn{3xFHkXW!`Y3Of?_{;FNg)am-D1_5CCD4sfo+9 zhao~z|AV#P@rvh*L4ww!I0(SBPLR|USg~nI`cBE?$hZqP_wq z=@m@yb8Ih|=WIjYXfcE;?iSNBq~oQ%5u1v1YY@7Ojym4{D&{Hg7qQZtmiIf=X3k9R zU$x&pO8B!b#>}B`?PlGu+U`SBHq?8afXFC!L1C`q9rj0bj86#|a>TczPCeeYEaAQ*@isC+meO|@c&=MIs zGo_@wFVxhR<(HF}^cPe;4i&+vT4h&3AO~;ZYE6#rN5(d?WkLkQRMJphp1{R|-=7Vx z)D{X0u<{eNCm+7RQ*YG}-1=wTgmEIU)m#(nZWXcCPA7`e`FY~FJWsMwti^2uy8uIsCdK0&E-~F;yTI!my(^QvILcW$EH*CA~_gHz`P5R~i)bqt? z#B0Te9&gRVB4l7(DQVKby#iR0l#zk1T8k=QWaCtXnDJcf4_`^{F!Pjn2r}P?0|>i8 zu}+EW@aj(lB|6Z*Wyd{k8y$*OlFBmzpdM)QKk9ODD-Fo*5^-<2t5Cf1hT-vW2>&A}! zuaZKx{KI{z{Pt%GaXBc=m6ov;c~N84uO^aj+9q8EOPzi`DQw;@q%Z^1fpjt{-0&h2 z#((akDk`zOUENT%$8fb_V&M$MBh{rn$uB;SHX9~pe`!h^rBLXGP1W6YUy~ajQ2fCg zHc3)+PgC^Oj)8QA7Gj!(?#ou-GQlx6wUrB78@$ z=jr-tB*k!|NdT6uX;EW@@d`Y>k2-*(8PfuCbZCM(~Ti6z0j5WEdv~=w9ZJQ zpG=Go=?lBE@B4(WH(;n!4W%BvloK>y&ci)Tct3H^yV~0zAUv9Hx8r=&_ReC;j_uBI z)9z=pDrA*mT-QjT#bh9FZ(WnT;ifadt@-Pv>KEP4xfThWx~|MpJ+(#^7>_YB$)M?HHQ%GA438xt;Urnfhr-9q$t z)h2YF|HQHb(ZJj_{Gpb5;HqoxAX3P=ZGrD&W-Rph9FpXWLkaMfG8G4TRvvm)WfS72 zLnFgrOb%xkr{_TnWudq=0FB^$kJ*lc5Vf!kMGR$Opgs#bqxIk`{<;rEc`;+IRWp&7 z7h8;=R%IpUkipMIi(;Z#F?z!Ah;8lRo23}(Ep$)FW%JOW_|SD|_~x^}X64JWiV=k_ zborC)(B#NqASu(=Jl9x{dm?flh6g^<>M9K2y3wNC5*T9a%3N`E;LUc4x6&GKLJI-6 zO3K>_fa4akcX1kIAqp6JTcRUUcY*p`u*Bu2%DL%g`+FMS%8VAA0QAp= ztwJ}?X<#A!KF5+m?1#bXRReo9gHjr130g7c(W#LL^%98MV{kF2q2c7_6^eSnGL1=j ziRwW(QD?TnDx_IONvUZ<4>ta#Q|b5_?fZ(hEK5RC5*zGgW?Xi2o9iQeC(IaXcCK3= zUqOQ`D}0bCZ-@ygY(FlW+rR20xV^Xt2lvhR?zrS|Xywn%X#%{Q7R@gHw-f3}gAZ=( zTXCRKNu2iN5u%Hp$|>1(E;hcGL*k9hl`a;+(yRr!;5y~>?sNKZFFn@fy%DT;a!@9!Bkd0>m7fH=Z+>?^ z4coKie|NLfxAxfDukxeCPMEp8)EI|+vJbgBM{Axbrf2zF{2JM;oXoles zhI%>E(p0nX3F^0g|DM_f-I8|(u^pNpdC8p_cZRV>L&U7-+A}0qA6J|ql=~zn?iOjTkifp-dR_rkh6TH|J zQ}o;SpxWS-a8aiOJE?0L-GGCkB|6U`r+je!WBZeC|BQ85YI1(x?W+u%+E?jYdD9cb z_@H?$E!41os@HxGh+g-$mzPZLSD`ePpk$W2iU3iWgzCWIU0 zeySJFQSd!yw=fQa`I1d(#N(s3bLR9J01dT_F_vT(805r|nE?4lY}oJ;Xs@ZOr` z^8hsKWODW8`l$F@9_M!+RJ?|Re)K6PU@$SgJX)irQOMC6x40#4P=RbmwM=obB&yMzj7QE;b zNBk?ttxL^zK^Rw@mBP^6bbsGsV?lW<6Z6aZ38-2{9Jz6U7n}yGF*^M04UJ(N{DeI@ zUw#hAC?E3Q34J%Mw+PNJPy`jOed&r38>yp%S|dzD-UPQCl4OHS+pH%*|7}T*uI1#E zZ%XOE-C6j2wG&{Sb>vra5=`6+$`VWSt@IJm&nLWRywF~)$~Lp-N4}etkzp5f80{H8 z;K3rGwp#CQWLg7&TI*OP{nOMAOy6*c2IUbwM6Gf{+vd`%zt2Yq#rdk8|v}(u$@(gIgm+3^Gs+V zg%1}1+8L}zywS*Ciz?g#%4nbfPd1)D^LyfOaZp8|%uf6JO~s-0&z#4hPkwyInj7gc z!8-wprwa@WF%{l`ck8tuPbl!#=-{Qe!??I8R(*j2Gz-4LV94^G46y)0jMW{BEG%I) zp~O+8ePjO5e)XEant`Ds>K7t-Gqkbns6gwJSH&bZyB-ot@a^F_DT$PZclW{JzQ3st zlYM&#@3_Hn8wmnI>kJ^ihzE>-Hv7R~Loxr7(ks8lV}TN$@fw4dtfwA|9Y z`xZ~Bj(=mAvG|1&M%lkHED)M`$R!3%R*S(n^|#;T;JsGDAFF7<9D`J06-Pu6vR6lH zOm=7Q?eg`7{yj;=XNtEj@wUc9*mCg-AzE*;l$qS+wY%3W^qW_zSX+)BmI~f9Gb^3r zzhZz-9@qp^)LT`RmLPaaOP7nUtNGu65qugvY*3$ITo5X@p5HLHRSFxjhE0*>4edpi z9)&YOghQ(@`5sYtmfA&`sh~FtdK6iVcw=z=HDH>mDiDTI67D7HCwaVGjbfbPr z{=R@Mg3^11bozAMs1kVXk;n^_06xo zsH-;LInGF=xZ64U>gF<>k&$+{CQnNh9K-MVY9gW;QY%K!@4$~l^V1OKS~~=zY<42zqkQ?MwQJ{oh}(n7#%x&}7in5@De* zm7&waT2?`!>&@Ag*xQKQAi7_7|MBFle3J$1;WZzdj|Vs%S2)<=$J;66aI2$&a$a(S zgS;E zEEf#6oUD=`zfDb7A#XTN{1D0$=Jf|x;`10p1;yr8WLb_l(n44#=CQX;3aVhS@%Ne9 z@Hp?|l+ePOvlBhipVBF4sZ>>d$f@_Ex*wN< zL|#0k!8Gv)H=6&`xB5p@UdMP=)~L@_ao?=?kfxU#q_uggooiH7Y@kma9k-L)Q$Ip^ z{CJSyobW$IknaIKO``QfM#7iv>9vOM2#tt+g(InT^u^5v0obzQDlKhSlB;+y;n zZ#ak_r!)mih?-Bh${hh))MxP|ru}ad0P}t$tYsV60?5cL9FJQ6KL`}J2f!~$9;1@B zIr^j05y~)BNdskKN@Q@na{)zcyomGRZu#08Qn&_l6vc6eX&g0Zh7DZ^(l!oRIgh#_ z0u9MmfWvO^|EXN7^T-uI&+p`oi5t>_JE>N;D_tio1MWVw+@BDg@uKs*~>rY zDh*5v^n^#JCna9!Y^)Ab<7ga^4%3O_P5?V77(?uU+mh#h|M=pW zf$2#tanu0mtGXE$8O0>HHg)g~{e~>Xe+9a6Y{tR{{0BeD#S|B)K+6A z!7~m#Qq255ticwQZW>7P!s#j*VigqWUdaUxN8CT=(fQPz@`3_9nwFUQCUa2(6tm0k z&9DLa)j7-fFpilv$>JilozWA(XbAYMYIo{09M)1(bn*u)AVvN;(hc&!@l$k3+g>R= zC$sG?DjEM3aM0#nJpRQjQJyDzxOtLvrHrM9k=pS0)n_~sR8b|rb7^u|l)v&F-M67J zV55ki@|(s+st-Hyc6l0PMEA+T`@<3I8llz(>DoIm6kQGk%Jj5uQmskDhZVO=m0)h+K znFYwLC#hW@s-oTZ78`|JW+1^TvKxzDa(W`A55A?&1-*g@#sVb2bCt_#4UvOyUZ6AT zIB`b7SO{7Kp3N3Y`nv=dPiDAQA+6CS&XpoZ_CGfJx?Jn=W?IKIydD#D`WX?c#6uhNBVNf{NDpSl~ zd2p}WU0b?3CsHnL_1_1Z;QRt$`IzY_E2|(My_L!#|mzeM!Zn zb0vG;q^w0G$Mp#&MAFJYmMNVi5hOtEhFX{HX z7BMD89+gGqk_S_&t#^b8#4=0LCZf(~NK3L5iM|IJ$(ak+Pz}EQhV-)>e>4&1jRpV#KV6xazHcp~%WOYK$c>O@ z83jwoSwqE!M2e^8;dn6X{CU$t zbo;{m``y+jmhL$%RU`wJO|#({n|DteJ{N>}0IIB_9YQGi>`YK`n_w6x=3}|KEE@|( zTx^J59srU2(8$Q}^V3RBa%jD6)Wv4g|8bJX3)Pb@TVZVrtFH52c`OvjChWj(u9&FC z`Q(hm?AA{mQK`RLhXFL&tmq~Yv)MY|bPRdCwjnPHFfdc@jhbZxih&#)Y;yDF?@{a0 zzDCbKW$ihoLZlzV^9@Q$?Q!9cG!I5bilqKVb?}*nSXTl6zJgck;#MDYdyY&mQ^b z_A=zZsE${UpFz^P)D7t3DsI98bL)B(XQnJ|CkKJbl)a6AeP$K6Oq+FeU@n*`mR-ys zF1sIa>0nHyuWqkSgxxYHQwC!jL+g_J*1m%kVOMl{T0Y!x`0SkrAOHm8*}B!-q#EWM z8c2$#D`A3x0}eTfuwwjSnNCR=ISRTjZtG1Z{70r7uWZVeqn=V&;T>xm9xbUF&EHw_yWROWowSwag6nQz1c#=MN~@pZF9xcrsRn!o2+WA&K|2t3)XdGSmYK%|(JamUX({A$ zfRtG273L_TbXJh$AS>p8iSMg(#L$iWgUDesIhh{|N?$o2M%) zlMTkDoAvW-jo1?qCwb;`_N>hgt_Hc`!e!b_`{0Fj=pFwtgL?wpxhJ+1luuDDl{r9d zQrJHet8wN8QhelVQeZ0bBpZa`$(=E|h%>Xgq6*UuB>pzPd)*qxN#$jm+Toz?x+?2< zp`>;mVV=WCMX7i_-6xBE&C$;wf#_k#)eW~sf(TmXq-AEu*3!J zs*_Q8yJMPz#R-Q>l!yl&q?tVP(Pc@m);-L$-4E!nR-6197Rh|$I_Oc;*Gbkqjaw%B zg~JAM?0tb2UOoe_K=8cjrOV5z4ET0hv8!!45qvw9Y^$~IH&0QH~RIMm0vz57Ha3>7SwiZ;l^~fm6OCp9n zu~UPvi$GEx<5W1)#?AZ!nbEQ_L+AyEZr&duoZ2d>I@p(eaav55SbO|Y;BAr+a749WbKkerUGK^XKu^p0`(j zpCKO$ueJHi^T}JM3!*uP9R(~s+lT)}C8d6j)Pw)7gpY0X6*LUp<`q{3}7CI_8 z9+0P>2W#KgDSJ*w^w4;^H)Gr}yR!*U;%$dXAxcI1Z3Q#Ff7gH)uxQQ_fD?+*tE{Mh zL2Z=spv$8f?_a9wffgT9e2>JEw@8PhWYM0~3o3ZM@1J$h?opKSx|DTCdj~H4{E%ZkdkuPSaocbhMMf{sb2FcUijSJGWBC(@&fPH^LU{4gZ`g9iA5LuA$ey3 z_MNCN+;sTF&78Z`OiH2A@oJ_RqBy9dLuHsm6|+Vnm3wzIZ|-zlKGADa@jCyz5Pi89 z(lL2&`=gL&{&IuTTt0;6(fHFOcdPw(v$mq@_bNp;QSRi6^AwEi(c8@Ztr=s2?>pBn ztsLF&`jwhAOo#s0ev@Qh^tD#OK+u`b5*a@1@=S)0Mx#&5ql5nf>Pf3Vgf#+zkboD~ z4_BP$sk@0ICcUhGcs!~90&AfXj?eix!E37UeCjiI)6BA)d_t+P=0MQjvc|^J<0^Ff zF~l2~yq5x2dyGf*WRS;FCO|=`~&N(V@MBH;6S9SjuE{pRyHGC`dg6mtAUFa%Oc86aD{Yo z_HWNd&$aS89)pmyQAfMMHT6P2DK)MskCi;2;`#Al%aOSI1_hm(%`jC*YSqU0tJGx{ zSk!hF9~V|t%tXesKUiFq4)gZh+w@5K!1Sm{;5MrWyC4^lO4V(^fQyZSzf@-sE=TK< zty+e99@bJBXfas(0AFgJ1~!0sT}|K8%{laE-J#+!adLmfZoNf8y$#5RC52@u7(KVNWKm_NPm~RP2)3~xO=`4=4>mB~}Uj>^x;;zB- zaeom-o*9(JOuv{o3@D2$p2V^@W5C2QV=bzZ{H5R1Zm5>jI%lJ9XtIu`o&$;5a;S+l zDm2H1-baALS>&EXsk-2m11LMgAoJb)`9Z;3&pMOz2=BSrMJ*WMtM6k4Q&%^O?)au{ zT3k%B-=v0a_i>;rvg6$sc{Qsdg%&JgV_I$+&Z&S|1gA3N>#XPzq}W=(UgaTqIZVY_ zcb%>7xlrX5ufqJR52QyUv*cdqxdfS}0pB1Sv^i(W;XA;Yp7AoWj2#5Jou^@n%qpbI z7wJ8*hnn=_A}Q(#Q~ruq;^=mD{~H?en_q#aMUASqKKLDx{hK8LLm{o{5tlQVQ9TY7 zO{-sPK3(x2`FAgPuzc?9=>}{0Ea%fioLru-#4Mlo%&eT@tJQVIwUp2_8%e#Wmzjd{ z6!?*6)^GXzJasZ}&q_Ee0`eVJ341I337~RmWEj1v1zfheHk?zB-g|@^mE8HgGQ%I3_k9t zD~gQzib0&J5+^B1`t;2&SP<9!Ty_>sto$LtqM9HGEJFyaD)UmVs`R%gJSzypJjMvY z9zClsCOfb9OQ~Z2n@)R$SGCp?E(3wC!3K8UA!t*?lJj3hog5ayVCWn`cw8 zl3MNGCFezm{_W&837A+b&PR=^fw$Do0H-`Oq4d51?&OPfa#B-8i~67?%1?*QNa~AS zQ#82gL;z2&ibhk3poYc+FlG&M=gdsh1==IR@<7?>nQAr;?O5eEzSJ|mA9VCKWmre2 zuZJCZ&3Yu3(C7*2BSM?E6jr$OWF^mAxIh-6**7CeF)^rtqv_NZVg1Jk#mIFEbmIqi zF8?m}I(O6`Mb^|Ke{JOA{mJ*NRC;t$%sHTSMdP0OFa4;TI_@|-o9+M$OO@fZqqF7e z%f^o%^v_Sq3AWMXoY-dQ#p$Tb1I>evUkq3+A}x_ee9m?Xm1}$NDWgNRt`AW zJNtKb_U~W0%H^osFaQKoK7HrR`=QZ9F&O{IOeA~ z&NSYLZF)$JV<$}Ip#0OS4WafH*~_v`o>rC;m^Biisp#|hsLCbtjSUx-zyn9}%9@Js z;EzMu5!2?vwTlo%y)5pc>X8xd&DyHX3a_%8tSRI?9E_Ri?~HCw-+$dtswcNA_{2;@sh;rp*fdSr0ZFKmzaT8pHPU8seq{cT zMtu+NfF^&Q{IS21g_=QOe+m^=uN_Te_O`Y>-({n^ZHBbC`8gOo+nsrs)F%GryJbI| zCUHO?yXit2CZr8`YZIA{mm%>Gh2g4hr0lr11gpTCMs$U1U>`4$m&1WhDMf-vfuU`a z%TdROdDdlyUKcV@#XA~Lf1Fv5L|jL9p@@LUVXP?n{5sYItB=>$M@TCAJ;~h=lzpiu zVf8a`VwI;%=mrOzVos3i_0)N~M$iQ3Z<@3aA8Qr%<;{g$$v&a^CbLz0zGMM#2ETbRt6VMx;CkNPF@(Aj+-&3w8kaDK{NlUAg-_ z7ylS-F*hJ&Aki-w91Z6mzS^Xd1cknU#En>}j9!I*4P}$nE#95VrGse-OeH3DT~D=Y zYjDn*76P}tQvl>m$N)@rgjqf(gMYcED$;Zi{OeQ7UB^hsiFigczOC)1SR$r{hT3kv zIQgl?tQA1yN%6N}_5TKR_hH$AlKa z5H;`J6l7&Pa)&oVM`gs>0zwTFG)5+t#ogvU;sy9w>vD3Oa|* zmOz-e$#T!1Gn$dl_9$apc0>QZJr?_0ba`g1aZXwWPNmEsF7F-8-rl)mKxFh;M6x3` z_mxi|ebRpoul#?8Rupd5HlMUsl}(TM<6l8-XGYJgvzcVyM{gg`7^|G`M|_SV&vYow zbGut5`XpV9igvvCc6lOtnV>N1<*o(eZ(FyolvY1IoK#8N1tvicOO^u2yztM;b^V82 z>ocF%c%@HIfYkW4e|~MS?`$@DHrb6kGb!9E^YinzBQ85ONUmd~Ej>zP>^(nhUGQ!q(^C-S0+L7^4b)eUz8cdo4i_IYsBDxED7kC(Sg(Lx76SQo+?0^#MfR$wY*eJ_ zWt*vKneuEG(^VNEpPx6hKMt|TVbOUBc?o0)r}gpE<_WxNmDX-sE+g87;X3wUpyO}1 z_~{w^{Vg2SHF8$!RaLjwak$`$>@drtgF5bW1Zk$V-cnDP+_;uz)CEUbQjcK%<5``} z2ob|G7 zTT*2O?+}GP9}I`q?tu!iW7mm0ro@c<15U%Pf51F#zsCdK2;gV-5|1fWV`KhhiI!*) zF?IK)75CS%Du`f8Qs!63hl>JQ96oY1yDw*V4~!{^M2*+q6<+=ao@d6?Bd_9I(k#j@ z7q9$xn&l8pZ_>hI7&yuj$`on%GsEh$I5l6mN4Nia%PI6nm^S`Lzllg&Q&;WLrW?~P z?gNegJATlkf-bK7GV0i~GuQ=1e*w7@d5RU+q%3F8MZq#0%Mn9kkFifk`t{(L^|DZ< zx^KjgW9lblAwdZNw7?nnWx}Ph6ce$BT}>n3A)rnOy)xXywUXkXNgY zjir~Y+l4Oj$5MPe9?lF)8MN~Ogade|;$T4uH9x%)QiBG=UIwrQf#h)xeWTO)dSv&DI( zKqDz8ZL88iGTt_tQ8~}ZtB2~fT=A&BN6M;z0cq6u4+ZZX37KpB=9(oIY6-ZY{Jq_a z;3@W@oH+k-_0h}_`_)OC?|N+hJK|`ureZI2|E0M9!<#gZdg5xz14L~;vfoIhJa-me zZ9eU++p>Q?M#;ZNOW_y*^3J01g$a})78*HI|M=xtS`36m(;}rll!k$Bh!x=;vD@T; zv_wEt(|v-uzQ#?uH{DWow2iS(1tyCndF2a#kC(OgS#SZyx_Ci#ze|a z_WnrLxD|NLaeP9ag?$=oh$RMYt%EFKR3clvfG$nd{mbZ`a$U8c05#+OV2@+pV?x%W z{+`RmIonJq8`Y^{7Qbt|tdh65V5_GQpU?CZKF{sg*LYUXY%yRQlV-xW+G3(UeoYSEwm9~G(kXz~$!! z`nhi__5WNQlDvHlr57|^&U8No4CiW}w8T9&v@IGrY#c)ow-kdOv^AWboKTd}?UBf2 z0$~9w)_bxQ6drYH1pWm$R$*Xh{lmiQksp7ix2u<>91wac$iEYa1Wvy)In3T!#!sRVMmoVKrHhuAL+56A1K}u7TM^LR;>bo)V;m=#Z zyA>@x`*%TZIxd>^XrCJ)Sx;zTE{}4e={qB5_DjOT6+cIk`@6|g^c<9mV?^rE16bO6 zZoxS;yUBa!oTI0H=a%XxmHAS@*di46JP9}B3EYP^<%jE-VX~Q}VUJZ5m|uRxU97=1?ey>RM551`r~RRzsoh@5?6b! z`c$;ctp6#r$)}p471kVT>-Aww4{Gxl++6-rD%6xm`LP5y#N${Cqm*dks(UPWcrWI4 zMOXUc$I4XY1`%#*&oDgS49b5CREbnV-~lqRbaV!B!%DSI1EW* zi?CGs0p{rg5FL-ekhl~aE*U7$d*4G+d>qjLom9UGPX*ci7zQ9^5?YuIB!h!NMD3#|Cw@AL7a0q(hO~Q&1PJF?j zow&nBjm0It@EQ2Q(>co%OS|UbnW4ulc>7M)U?Md04Ifocf8%f7uR{Gc2DAkuS46%Z zKmuILh0w1ga@nm%6^AzkCZ8I3<_cNQzdtb&PQvlp88NX}%0Bf!n_jGH_g}piLRF{w z&rAmWk7X@deCXk)d~)PP1j$2WB%)X--{<-v19l6lYcCZ17 z0iq5%Q8&!%6oI_ije|fqJ2}%K5FHzOmlh8PzqrmvHw^Y|b?pFduc>m}l~jS**wB|6`UPW`ZuS{8#0+-ikzZ!2-4&I z5B9w*LK_cYODx|{G*gy7(2wZGZon7BEx*iGoz@*h8P?G>(^Jx^>-Dq2bg!egUAUL6 zr#*irepwtY#nWRDhto6^bV_T~0E~+v<2EwJY_EmEd@C|w_rFS+Pp;SyAG3XOcj(i4DVzie#YA;f1h$i^vCf5}$e`}bVt_9hl z+xcE=s*rh%Ki}^f8XIeU{Q$Wz!jcznuhv@4&sSv&YM9HaS0QCc|N}KJ4BU_F0rr$=A9O=-m?vYU*4b)7hN$DxTJb;{8I;Vj4G^NVKQ zQQw1kf~+y1>2fk@4ad5voRZ1mP2JDOQAoDby6zgtVcnguK(1%2?%y026dhXT-0;@2 z_?>fE#5?JyoyY+sDiz-0~phMM)ynvTs%Slb;=&&8?B zQQ#U?cChVM!(y(W?Om$I6IXqFNFpWPFhUws3KQAIVK{eL@yaWSi8vbS@+!x7$;zll zQzK$pYbcKpP}zZ)DL*zug@LnswhI`AAwll0u*MxFVO_yMor8kA^v^$_;Y7_@diYC0 zQ$t$JJ3h=^4ES}Ck}0bxOBY-yi5SC#?1TqiVXZ2ydYPHK=pJI2?fR;+K>(>kbbM!> zok~@)T=B(z;NvAWNyb50T+y|+{^{^XeNZ2?kdFHBgo?o4UZlfm9iZ4_*82-tKLvb~ z7nU0QO_yD}`$?z^v3Op6wBSREND6hWj2(C7?{MAbIX@~T%rA5XzQ!~7x~oAEBnieoMU-- z#<#8XmjvfOhi2_>La%aN6gW#Y!NHi(t2l$3M$9VKUyp1l-InUm*<)&MD1rXe@YnZ; z1%By+d#3vG@;HktocB|3-%Gdpin`Q8MQ>L`9*Lb~Nm|9^pnBy58ik$E(vs%fSR-u= z#IAV4&Dv?I#4f_mNUS;wYM<{T%21~Z0}<=Rkq!YQwN znkB}Y_iK(k$#Fa%p69-Q_j#RHuZ|s~;a|CiOR)z1J&Q?0eqol)<AOc!gDVL7cTCCo7{ zai2?x?d!^WH@jGv?r0JKMYv>E?cL@bGy=f}OHNbV4T5Yv$ab=p8zV6 zlqQdu62hMM;q$}7?3Xr&I_mkourFw;PNYV6cgp{N8WMW>FYG1T>_9{H z&?p^}wB$Hs)B3H;_@-sRNptRslD%RfxeGS=UgF+RDv{yEyB@ERj3 zMn8YhqHwX1tS|8fTKxf;;@VF9IeY6YKl988GmChDvg`;v{uRdY7)E*4i{!yI33nzp zi#y2??Y;Drlrv+&oP=h^_id!Xg2M8D!R<9?j^@S^Jrj**BhYH`29~n4KRf3^P~<{iq@lPG!=SB`V4zK^*!TqtGFwIoXSdFAm$^3@FljHSN_?Cx84r<(L&Kv!P9T6J@Ga`mWWV?2lw*hJO{`2w_-&8Y!(2^!&nuX`kO_ zzTkFim`^CBiLG@cXF%(=Jz6z=o)?tB*qub5Dm0;KTJqHI1zdBj`zq^Z*8}CPL4|&w zP}`;u5BP>Idcp>WLN;V_eO1^tC?loAvmJ9wtDJM)zoz#c=jf+<=P3&@ELStzY8^#b zt+|?yriF_D6SM@ToT4`)|P)>gc zXPQ8yj^R#nO4=IbmO2ATaVa#qex8LyEQ`-@xonDc-Sry}Jd~|#PEm&h{y?FIlg*s! zU6kC@ZE@@+O=U&SB#MEMOV}B=@sve*E1wi+lGLm{zzEDC#o{4j%qau9VGk?vHC?Ql z6)lidqH3HV>2ZP#pv#q{j9h?}xuKG|SHSHqJ z=-!WcryKepL`u?_D!jiq7N5kOR|b3zI>dck{4~xw*asCVbCy_R9f|TVi9^a>)@Wt0 zl>`ex#Di<+?-bg5S3)wRP!0k) zXd_5Wx!N22T@P)aa3p8wYl=?K5;p}aB+cWpN*I<;gDD56C3?1Jc9iTH9?gJ)4A?4%5>D530S^1N>MhxKV z^~^=vyoVI#aWsDK{PJ&`$Xb_(Th%&yZ{P9N)!(BQ>y)K|fq=sM)#cMi=Oms9zs{-~ z#YRG{_H}O=jT>&4WiHb3OcPl}=j=+xgQ zbL^i?VE2?#k~*7f+y%~NNn=Nd+XP1k-StozfHVX`nH#$KV|L}S>$G)8g^0H5>Xlq< zK{fS=_}8%Kgy8N8l(qm+Qco4aewU@vNhLa{a~`|2IS`cRvT8M%87c)me5nBZ*u?h) zr>lN*MO>HLxDOGvX3^20P=)+~uut9L9rII-JiLX-emow(*37r+#Wo-p_)tw;!nz>m?N+>)=!`&k~u>S+#Ac>azO zh!bsUyfZl|m&P@|8Ylk*4+d7wsMj`uAG_T88Tl;%LA(npl+#8H6(1+naHuL`|5vzz z)E^rG;OJQrsJ~GppvM!`VC$M$Z`~lSPiqXSD#<3)id4j+KFp1rc}#2H0!D^q^BeX?VDjWi4gZjCt!cwl_x)h; zzD~6!v=uIry3w(1l+MylWLVH_iH&C9H7EZFwNT#QfI!WRBX-{6XK?J{f%l#6_Sa%u zY6JICq;jkg|7LW&MZ_p{fd2uI(t#{aLx;HBJg2B9b%#naT=r|)+QU@8HG<7ZjF1-oidrw9pZFTgf;tLL& z2T$qPPynPFY!#=~duP-bl(Hf+wEVIcHghjL^_sz8RN#ds0M#IVyK^1mASE_rR3RR>OB~(Xm=}Jo7`>x|8!h1_N4=YMmo-~=$QDT;z;!Bgf)EEuT zwiJjKb)SM$k%Qy^>;`qL8elg)R?~W1SXpH~FS-PHSI*VIW!ILv^Y2v0y4X@W*txwf zLLau7F61+$daQh)eKW4=Xk+qystcOw&G*yAqQ@74K>7zmz{`$t{JqH523)W3E!&DG zl_d^|2(=(S+pM908gVa~+K1`K3FEgwAW>sx4wZS`xD3?u3n{Z&i^MjXCs!LXv&2eV zX$=G!dI=2991CSu%00GiZ+s|E-bYjV?nJZb?ISGSzxEu$)_}MRwjt-0fm3qZO;Seb z9}yZ^%O>yRL7*PicjQ8a>vzbD^3=^7zyV_pqHh#HI3u_9+@-0|ycCd6wp#*)Rp-dI z{c#ddRjZoKHaZ(*VQKWA=D5ynY&_#frquQtDKKRmS&5dwveyar+2J&XxQw49F;%iT zEpiObiW$zcL0(V=D3_N&^eC`NPqkG5e$<{CJei_i0N0RA(OjN{KZ0I3tfR zZ5M?7+zV5Ay?ArlqP*7BndIXp6y`5AWjN(`zE|7rP9Ue{uyG|cKXkvh-x6b{mRNWplX<3p-oQ#7WGRdzAFq!D^iS?DpC0Ke0wX&@er*iE zd)f_b53?_`dvoRgH&qS}f0$h!(V`V%&@vUh!Y zy4jh$kbIP45=;dB64zyxbZ)VJWjP=QOmD6S>T8&K`WF?$cL`mSs#@7;UwRXa(5ZuS z11y+-!C^V)#A>_X4)vwem)Cz=7PC%H13UPyt}f?NRQ%>%BokI9)fJOECtkA@3Bnjh zz1aLJW|}5*N*Y=Q5@U@?MO|H(D{HDv$kKXUCizxyKR+3f6!Jl&rA?3`}Q zgh`g8Qh&szJ%$NbVxCv_u|Y7ujN#uj^kNIU&;F$Xbrsf==&C3{>`q^OBWl^U#;Wr?NFz#+xm~w z2PRAt`< zP_Bl&Ea@(e5#^eY$>UnBjIiF*pL(6n30ZnmNd{MCD!V%;zeIH>=jKi;Qa54OqyOcX zSSOm6{x_r?4ZY`atbX}mrV2x89*1|vy_$zlhO{TzK%c)nzS58Av(Y3d>pZtvF4E=DDlR<7(FA4W@IF!3^pPb%h{XR13A zqI!7wKq9fuCGm@RwDUTZE|&YhJ0zHf@JMs#&`1Y10|qnt)cdy(#h`}+F5uVLu4ru1 z-%kQgM=F6BCJjp(!!a>Y_P-XEN&p0NMN?C=5ZcjvpWMS1z|-4bSZWAj$;(gB7%U&= z(NCKhN`Jgw`5{(?=kcb8s+Pj*zRsqccmH)JXPiKwHfYvZ`ci#rF6fKI6}h?Uj9#4; zHY9p03a=8*@W^F`3n85hZyU}iUDGnxlEJUY=@YB!z__Hr;)D>zhJOv?Bw+Mb`-tI$ z&TUUgpn8uNTf|2p>cGY||2%@xIHWzqW!NZH+V;d^HYj({(K0Yt#v8GiEU&*;^~Nwe zXjTev>>1BecH%SM3|Wt+mZbr>TD`8QY-%o!vN6x}Io7d{3sNp{bLmj~ghIVlVTRLz|osII;n zWxmigZzg~H-83=qp*V#PIj1FD4M?Ls!T2b?PFhg6bge!iPVRN9#~GG57ZvxDLj-q@ zPRv~&CE4e4Kk`-m*StV=M`Uiho;`o49^`ZI4x3_2!(McActmt0cAf5>EneI7&t8@8 z4>Y&5=MLm?&eV2a|K7(>rN>%URSUBeIJx>iCm>5x=Uyr{TL^-=mEE)v#S0Iq11Qan zWBk{lILx@Z;^|eEb$E3(Iz(~6Z4r9u6|QZ@zx8*tLp7l$A5UjYoYjYWRI{&$H&{?Z zL@BxU2l#NMU(rp?VW+U*iiui{IL|Zl--Z}U*jG^>_olXNjp`>ZPi^6dat{oBQd4HeCpGDG2bkWv8#mA^*7N6X-Dek4gLnX+7H0!3v3JBT#9j95 zhkg=Brl+%jx}xZj@?5>C&vAhVlvnj(3xO1L@#CtA^fwV9GMe>%KG57b$4=5g8xc6S zcxoSmfu5ZIC8~}y(>f1g9Rj7tGRnfeme7p{gcUCeh#^rmxle^b@p6p`?vW1nG|V$> z5w&A@BJsxe06_vB%dGz}w6~uiq^dPIEel{kkwu_}qCXXPpa5J-_*x;E9?d{KgUg&y z+utpJN|yBROWrFw^in~V4z4nNBB41n!b2#EjZmuxRB$}c8M5}_OF3Q`rL^8g0iI(q z`+b(}lC+iC-!;qOI*Id1qj(;g9Q$_svA>v4VO6LEd*4=L?q8miX2j97rmU@yexk`b zkdIi8k@W6-3rDk$4cakX9Hj}$Zj<$#mIm_!y-O}|5k~D6PvDn#OXr}`sHt)gJ%|1A zYQ}mzuH#KM{aC72zYCq{&x#?9-9xb?F)!Np*b;Hm{Sk>eobPDF`^!nlh6yfSRo&k` za)hm$iO0^aWM8B=qJdOggq;!^fq!q=dR;7tsF`|wv)6rD#v|rS1I=6gmq45F=~)~T zolWWUnS1udBm}t-BvWc$R0y9a$?A`#Wl3@XD7`X-g^`O95a&sr>g4HMHT)S+c*Y2I ztr7q-*g6%iR?1~(V0u{X{5rK$slfI$`nOoPNaC2a)pJeI*XJ9m_N}9`!u` zj)(vl{e13yUOz|*Vc7VUQx#ed$KFa$=#?{?*DY=}Z(ZZCa~1fa`Ipt%Bvj;gg!QP3 zre2VAZzHQHUhevnahk7o`j4r@2T&zX-9P7wDKS>13E;;OjPF4WDcO8MT$jA^x#7Le zIRoQO4z?tzl|;|(NkK%pt5c-L76`!r&`-5%CgJY_0FX)=k{QkkX+NLX6qe|GVJA`T zmlGLxD5Wb=c%zPd0OHI&Oj;t%bG4_kTk_2)jHW-irg?>=ss&54o?bUmarE|`wV={2O;>rpk z0cRG>O>KSpD?mk3)q0j)~`n zP3?1c?OJcy(v$Pra~}sQOqE0?HpQLwUHpHx(sIO_0!jz=da*5a$>&2g*ZRhoCxCZn zH=G4`>N+R=yv~T3q1x(+=iU-hKH>7#C7F{Cs0~HQHb3s2cy-jW6wI`2xK-bt+d=u! zL!j&ddImxZU&fx|*G!=l73K zPyLU5I)!B~kB+7@d57QmwaF_gf;BPC8_jc7A;tKUUxdFz!~5SBP`T<%{kS7otCo2; zl&Is4)qF9VghqetZU%Utm53y_SdOq|^Ipj+#AjGkW~YgYv03sL6W=}3$uK=K+I+W< zr%E{DODe$U<|NBnTX>+Y<4e1U`EqEUdgj3-YQJT`|I_#px7m{I!#n*07>NK&(d62B za6Qj0URv<7z3-nuIgjO~N{!3QK$74JiG$B3RiZ@GrnRfREyYjUb&DN_tT*3uhd(rD zHh?LUVZEeWu5PNXHk&Njzczz*S8kQG(M_~I&`uS-6H&lT;`TLE7JCmM21c|FJFnHf zh<+O+c{{{?LjG_xJ;D7#-JA+AhO@_Rfxcn-J)Z7pE4(rO3 z(L7VQXnQN8Rw_bD&m=$}cshbE`7TPQLHP)>w0Uvz-IVW0UA zcE(9x;j|9eFQAG*qf(;XN@l*-Yq>uWtkV#7IUwz?r5bvPt^dOkMXz1Kk=G{=%MLo+ z)@i8Xk9{S`&G~zqQty!;@&|HivrTfz zjW(Bb$o$NZuG6}pEtz7$=c9A?v-s_d+{PRz22cNZuBne@} zmrp^s%(>OQGLV1egE=`?{~YXY)`<+Sm0>Mb#YpY!_sNaQYKGO1H*OhS(bHSLi5HA3 z9c&;*W3&b?PIb;uoJo%QU5(`Ls$UNjfuDNcGZg0=NZ7?iL*?vA!H{shz;c%w1Xxc< z6SCU2%p8%bhCwm_VRDLslr7`bR?qdrre%iOFb=z{urmd$KwZM0`%Qq}O{?BeZ0R(V zGiEu7=T59WCPUDP%vr0b11L-4)Tlvpjc_@pZuy8yDWi{}J;&L_@1Vpne<-ow*y<6O z-uAR-&6=OYr_AU8vbLI-PW!g48IwUyt*re1gJ>y)&gS)>KZ9@wYt^S$ZG1T)^q)U4 zGzf4)kUsdqkao^tT)kqv55M+lB5#WnMilA$(bHfD{JG!IDvE{P9P~SzklkigD*hJI z!@bj#f+^F#wiD)qXYot$Oq5{K)mqKo%xS^?UOT*?7oB~%p{1>nqVzA-gn{hr{6(3I zls$%x1Sj5I4M5$~z;JBU(1{m&wj?a8^NjP9_RoKg0*U28j(B5JG>+NMc^MufC$XXo zmBCax>^&$o)#v*%{pIGtvWJ3wFCPIpn1P#E4uS9OjPK^2a^~bFR8_fp*Sm7pxHUiGAVNVZHp;IXawR0yYf zO^$|0wal?5g4IM<+GKKLxp2H)G5+&_ES!_~jz_X|K$i{q^2L=xkWMB<}_sZDsOtvhL> z0r0Y;`{tzfMp7YZ>U3AVw&d6EgnwD#1Zol(4EXR3<*1NWfpyrqiz01I1WU=-(F$cm zlV(I!q6vDzW7B4v(3b5AW>A9q#?9K-)zrElsKnprs&d)0|4EgZOP`HrZkf zt^IpP7owPO;Cg1JeRW=`C!DbNqRHgZBiOu$AVb$vBAHnn=r?;hDnZ1r>xJm+h*h3V zMy=Iukc5Jbpz_-SM1M*(@p}IFl=mE&n=CdM4a*!$Vu%t9J|pN-(cXeyybQC0kg&~$ zJ(us%7U*?zX8@>Bz^5j~K53(40l;WJ33yYg&O;sBCjy> z^X?A7%pwZ2B2BDQIq_Mvv;;({KF7AbK@H=EXXC-+!9_#If9yk6;D4J@b8?!2^XECpujj^A;U&n8=}N>c?dx#qG};~akTfOm{TDah&V{64l_DSs>w=`aDP#p ze!9Po^<&L`uWHz(ni20ImIf&)Pg=cD=gg*jEA}xr#0Eu?1hDn6B{mVQH_I^oFp9~@ z(fhrip0~RUPlZIO_X-oV=EYSwvMgd-cOXz24S|LZqRO<0I(@vbvY#y?!3>+aB;DSz zuJsz7pU;WLK_h-xaIX^6dSk%{8m%H{Fj>eOA{iH#kul@^)A~u!SJyt&OZ?AJKMr+) z-yZ#ioyi47f{b)~DZsnyPCtz0Uunv{&sRZ;&k6H1=+4FVkI$t!tYGK`m?c$7bX1x+ zJ2iAop%A?~Xb;OehihnQ9Qc5cnY$6h7JA%!ca+M_NA_Zrx#ru|97l^~YmyIz zF-?<#5uLyjji>lz$7*6B(Fu70KACg#vDSRv?9Mq2y8H6<;j5;A`KOi#?;`h;)hkG7 znUiRq6py#p?8(7}8<{evb|^W}pLI6;Tiwu27F>KB2|$9|i>A8wOip%t+curalR4@y zZ&nEHA_t2LF{c|lK^;VnyhY)wfnaoO$ywgY$x+k%fgnSs#l*3k9)rI+++F(-o|sp{ z<2OP(r)HW%@2L>#y1ToxP#nn=>bEJN|o5kReWL7gu5T zIya*FdPf?vdYeSdBbp8#QF6}K3Au5sZg`cRkLeto5!^dZ!tz4LO+b&;IYy7qFLj7g zE;ko|SBE0bI0pK;enDibA$NtPGk%r)UUf5ottbRpnnAKOW-;PUoY`AeOh_PI57Z?C zb61R-Ox$FLHR56_?!h1WZFS-~n0TaaAAK~~S*}^f{Ic+)^9yM4>*R&LU#I6%HEZSU zoXoKbfPQdAKHJY`^$eHmElMUTRcM8cA?sU4aiW9ujfNp)rLS07Q3hG@vY|nPP;h8v z;ymPST72X2uu7{&MalC4$`rAb)jpXa=gx&n@*AF8V-+d29>M! z$NT=V3?6kn6@nWYPL78TO50%)^eEy5%IWzxEN z{v-Mi_YWNz718X^_G%3}kbEF9UI^{$;#bD=$a)tE0t+ewhbIN7A_f=!-LfD=BlL&c z+hCeW9^nT+J68w6DeOp)^at)&f8KC>HY9Gv!v?KHhL4c^w!wG0m0OlkhWt>4C<&jX z@8*JjBC$L6$}`rFa3@th<->K@2$6op0$=S?!Kee&yRM zhQB1it1=@p0NkKwFrd^DlEplL0JTH_1b#P!XHAUbFoX5guLNwvluv2i;aNA$S#rZ`_@w&zAwa;C?X*L&zEZx=v~C-1qSb+_%bc zfD$5Dolo>2N0}Pwdvs|BhCNRj!|CMS3fbQGtsYt5;-n+vA2HSOAlFeA_Z79T#HQY_ z2VKl)0|CT}tPxAweUi2{y^0jLS8!^2F)5jy@9ltS^AEe#;ywN3s2X)pUU`_>Oq)H{ zYw&}w=TPwj4K1K2-=nn&Rt}f(z6#M~>{fGX-qMHTp{AHvV1|J&TRPnj+yz;Js3;lw z8?Ow~RY%y+dG(FSLm^UjfZf39p(?2h- zzD$DCH8d5k-~4!zVy>IjtW~jZyC3akW$F`SK809MmDNU$Xy;~+GnE!C`fRAGL3cN7 zz_iLbC9F_yo{rT5d$aXu@EeQYLm6q(XbNk;5u(AW*>{!~tyrxDzz*`GVv;Jr$_h!< zMz&5q=_n}OqhwfnQYvMc=C{%;d3WQ^vjS3}mY{K5(qrwn1v)q|EI7cFSP=tzy^R4p zn4A1!^m()=%YUeecpwC&kLHa4m}|vi7D8l;V-5Tw)YURej9Qp%<18%DaMNVbGn5;W zUhHU3+W$L=jUnh`!Cn`H$5f9P@egAtBp#7S$ChD)lpp*qsN}8&5o9u(Q&QSrkpN5t zmb}tXq- zOK+E31X?_xv)PtxwkHxEFLvA)lN&gj-gx;!KU6nbjC3i6Up_mr?F&&f*BH33O0u%D zLdhba?y9Z&el#?}%W{$k2P#q^=VtbP;e7~Jx{C|l!tUpRT}ch$3f4k6)pmaD2JB+YLh%IOeSI$XwEOz9Y=B@N;%>a7f1oIHAG|;NcWUu^Vwi}({8d!29yo1- zF;eDRK8(&L0(SE+=%AYenf94&6O(hhawG2Hi3+Y&PI)g?$W9ybuM#3q)Zp|Vr*n|P zN#?%r>+>nHhUU=4v#G1UY7s6`>Ajt2$Bk&!H!~LhE$;>))BvIlxDw?aghLwI%C!m% z`??tl;OdBF0;~#+-wRE6S}fuxQIFq9$ChZF(b5l%A>%%>6}=z`QYa{MRBFh5y^our zfPlO>H|jVs4H|MJ*w&h%lF>h)`@BsVE+L6i&FqBd-BcKpo#2k>5Pz8}LKkPmeO1>g zkT09MWmRQ#;)W#l3$#72Wp>*Z%HUDm3=<#6lq&NHecddz*>-nH_|z@Q+*P~d#fJG7 zZOLS8za08VY^+%0G9$qCosBSVJBaBM3;#GpS)l>W(@>YUd z_K4^njnXjTozKjh zrEEtNc&0do091=`GDUYD^TlT5C&sWezdhkHv@H>UFaxYJ&Uq;pSs&4_@5_R(F`i{c zCTpTe03dTxw%bOnBD)SWsY|8R?B$W@6${Q`pE`;Q9Vs@g>lV3}D%w-Nn?CyJLA-1^C zo`quXXymfKWRIY?<~mB>3%u|ewE2&Y#249$v_Ah5WR`Rsu~Z>?!+A4wBi~yL0w#!m z;ZB5!#iF!qo#F?{9iGQ)w9C!UYyKLC{TPSWSk80Nq%yei_h$I()Ke;nM7M53a<={r z&g00tKIMn5E0N$b%#GFibu2C!WVvkoyGB%fv`Ai+O39hl=mcnmAC3RwVt|i`N#w6{ zwTsjFJj`{Pcyjyb_3@lSG_N1pm5TK}Swn?X8#R(x2}HJf zReP};+>{9;%DaS>n4zpIQvh+739butlLPqBxiyum%tg6L#Ufi`6Zuhd3t%Bh@%_9_I&8jc4odBPMPLvXpM? zf`Z>J-`q;80X!cI5Hc;~DQN21clQpjghf6@X!WHE3ChXInbJYa3LzNH8EU_+N$^afL%hLyafUle2 zqs`*%AvCCmXIoMy$jM%Sf#hG;o+dB1ot4D}6WDt<38p8~#tjB+e?--XJqjde)D<*8 z;D&sGs1zfSZbK~gI%O$j|C(R0KA*#n^w`rJfynk|VlHUxUn7Wy79wZSLu}78DE4Q^ z^`h;~PT62}?Gq?^mjenet?JI?PpgxT(LL_7=1W})MAtdAa%^1qN^bvJ_UZD)bs!@j z#$c~Wa@{O5Zl5^Yxc9nykyG{*6xw)_ym*}mY?(SMQ2$S)7tNA684wb7$ugg0PdGij zUKvR4Ng%2rpKSN`&V~UZ-p)@xV#u{G-|-^m^AD=uT>a7#py^$*O=7Cuiv@`Fb^dtG zK}f=^u=TyJJZ+%WMw+{WyvQ#IynFKf~yi3Acx4wau zu!Nwn^Lmf7nA6lf@2t`}_QJ{UxQ!r=ja1Bh`&{bMS@)G!_q_T6j;ARJmRa)O1J}-G zzFzS4OOJt|RtvYiuntvZ#TedJ*21mtxT%wv?WeEHC}g#JY^wZ?C3j(BJmTYI9CG?h zlgJej>cU@MwpIu5UH|*{xR^-<_)HS((m6k_6SF@MbR0!LTR&;>jC@j+r4c2E^X>l= zx;Wz1Y~kkKZ{(H^%~J=~WfOx&Yt0OH2iqQKxvxR;&mf?YqbFGxN z$2K4W*4Uh;Z^ZT3n3+rYo>~^DP&hFHXurwQY5gB($m%Z zwLZy6%%&WLSO#kNoi?L@p`uBAWsGW`H_%Lrw{$^bFKGR#G@URlX2WeK)FCYDZT`mA zep3c2F30VFgBj4&{G80EVcg5{)lt72c37OAA^AQfghV5iOt6;ETgr!d^M3(Xbfb+AmA~IHGxiE*_vt); z(2CAznjm06HE75+1!H3{l$lHl!voEXX(MkFD2_biZpMCYRw{>C*1(qo=K; zs`opi8-_ZL1~fDYyp-h9VeXaN0Rdn%7?@bu=ArEdHZ_nRu38%Xd;IeEiOfd7+(b20 z(#kpeb9uy)(>a$zFFpamt2O-}+$ZOGIxqr2T&Ay=lG9@<%z$sXMCbv?LdnB-W2*mf zlz($vv<0Mv4>B1olInjFFb<|6hQZP4h;MJ%iOq8t`7ue2v)dnW^25KvT5N2;c?e+^ z9yvMxNLUzHpjhWfEbkkJ=I^YC-J6&Z>m(~9Wx(lDb(EO)XB*~{wh62X=Pb6B3aviK z=e9G}*5+l*&Sqk@$8Ayh#Pt8k$OVA0Z86RUh`X?^C#UByE4vCFSuWddx2D8TcL9g- zxmD%aC4N|Q>{j6c0&SSRvV24TI1cHMGaGFv$z8ejEE&V2lOXG4x~~(fHBV!hGwjmr z!ySJd<{g(Ia$@-#XMW`Wg%^mJF@}+TDOH)1U;_Mzf)m8Kn@6- z#&D-up36=rV$g$6Hcb1{*7G#Qp8mI#K8;3@!5K?GrE~oprte}tmb*jw6PGg|Cws&Q zaIL_MhfttVywtk2m^_@7%rDJ%Tg=HRb4zD{1!6i%}>0 zH`^k`v$-({CY}X$30Es{9E2`f*E-YO{2d6$OO#`#w@+|1w-6}Nl1{RnbxQ^Wuy;;P zTIj_TB)7!mG&eDb!GL2|VGpZYqwMMHUgvGJHLIE>9c_jt6lhmzcZ9xtd9#&eO?><+ z*%vEjAq zGccaw>9W)8-ZVM(y^9kpQqB8yk`Ux}RwFOyveTXb8(I0JxDWlJbGCvg^s}PeI$BXz zB+RZTA?-K!x)41?!N?g=W%C$KJdh=QJbW=t2%4Pn*r}fICZ;1+evWg0%1ArsZ`dsI zwqVKftE+MO>ed8&&&2mWbTQLWwG@SJrC~4alecgKCIOM-x^C4qTDVN(2j}J~KDzVd>-+8{K^-6(C_bsfzq+K(@O9QNxQoaL6Bb z`}l2TZ=M^svP<#WVNw!Y2LdF*UR#)9w+yOKSQdH`uB-SBuhY)>kCo1T>vfzJZPqW` zD*TSY%Q7~@>1?UlSfWKS!ew8%=UA{bQ2w21wQ;h+5%R+5c4~x%>yCXnX)2(u(MhSK zx?af7yn>mDPvCs_KLJciYY5K7K%Y#M+e1{MBzAoy=UYOJ4^$5)V*9gV7yItXAoGn? z776U0lV{o~CW4VA|A-b~mo^LkdHH=*pK;H3F2|YPM&WaVCKc=vtQ4`NMf*j2G?8&G zw>;eJu*XL$XE{bDTz^YJ@8>Dfs5av8m`V>gD5Ojdf4BB5OJluC5F&G%=6EYCX(;$@ zY_@|_syWd}k{4lv+oCS}Px=eQG!h}kpB?yC(V;7_xSy%d%DI?IS408wnY#NyBD`j$ zN+uu&=7DmDY>K_nC=jw^<#d+ z6Dy^c#0q0?h`_Wp4kR$EToMtko7H7RPlnRt$q;f9%v-MXAdsO)4S3nM$%Kr`Qz9w? z6&Gw_1&+k^>HJh{6LOQ?PoG8^|8#jhET`q97Z6R*NvJ1-@cX4N1}5pG;CBJ~qM0o2 zEZ}+`PrWT1)<2~1_z|PMR?+&1ylMIIf4HR1xn=MD;nAO0mGy;lOU@%kwDP_(7ET=Q zVLpZxw<>eA{D01|t)HX@fr9892z=CBx|Hv#tY-Zk))`>VMl#Bf&;6v=1*gf`C=?x5 zM|rXdH-ATDpi!fJWKj64G;p2AOnb29gY2C6->?=PwO7+5PMKY=W-OirDFxA4@0xAD z9qS@p&*3Z_8M@;W_05KBoqcqD5rDk1BW zU>IDgZ@%8WE2I8JM>f}w`bL^*+>7+sm^=?XoH5VPovZDr{`d_jlumcg;BeeN!G2M- zxG9<%erEzFh&aQYuV3CF<+7&&7fnj4(XEQo!BdBhGhh1!97z+*&CFVp^G=`Hc}w6d zC3?AnIbFsd;S22PM^9%|9e%z1#Ws+A&%#t9-hxLzYuA1Fy)aZ^M5Pr_m;*y?N2^Ui z9Tn`yc5gkMbuFbI=`V4pb?&Z}D$tP@MObDzhR6f$k9{gmh|q{-D|3?auef9PpQ9Cs ziYM65@2Fqk`D4N_uL)QFK1ZR8gfL0-Oh2IVa_}c&cHXsbJfa}~YA)FQWVeX9qPo#B z$bEKi&*XuzK;pj_;a6_%CHWgW=g4H}t2lu4vFM3~c5sRo*mwf=B+BfVE&1%Mw|>u3 zM`$D_uf(M1Sqn4mSfuXyQYFkRs_IX0 z$D?0$`A7o|t!m&`=+(GT!tB|~c`$5rtnySzNb{;HDX3gpgON`Gf8^f{H{_I`d$Gd~ z=(w%SLoy-SaMtpIs2fC@xrNHNAP{8iE9aMNu98H_c_?9PwqB^{%)(8wkAncDf8vnK zZ4blyz~g7`GIY35r4AWKO<2sHTDh8MJu)~x%0T`9sm(EH^7*BfBUF^@_Ybc;t74w% z-rj7kOsnRd`pe^=c-gN<=i@Nt+d$;%E6vn_qyEDNDxIn%}o^s?q8 zw1JO;J7O^%C$Q)D5dhnRQ1^J^xaoxoVVY1c#Mt3ya1Z+FfWSg@A51f5D-|k?{2`_h z4vBM*IuSwX;yBXCnX&rYt_cOP{-n1GsXD;}h50Jd0eI}V?p9_!xrMWPh1{RLc5P~Ic47*2@?a{ED`jlgvFZ6+eB7OD?i#u%Nt8|P!6 zJ)zQ}N(OB}QDwj@MzNP8s@n98V-e`IyIibjiDoNUEQmvqVO>}*Db$!V#zys_iC)1R`^ z;@U=FQLHxS{E;EFaQgF0%9+~y*JOWHhx+@$SFT><$QGR6Wyo}u`g+UyiP~=&MR3P8 z`^7K?DO~LoT%w<9F7a;Ybzo!?^2i(aaQbl*BO|$$f}&UeR&wtTPC?zqr4L6^DcX}O zvGF@K(m?a>6G;oGKc~=mOwlbg{0<~u;Fr>$wno-}ch+EV*?IR$Exv=Olt_~;jFg>L zXnc0xo814vw6veiJN8PCXtl;AP4Qb_vI|qSt&y?us~3+R3aE)D$3s?(I#hIwLlKZ1 z!FZeZCmvMLTd}BCeV_9rq5<~(ldP+%D!TbG$n858DFz~etyNsFY5m}tYQazmxAraO9Q3h^r^2THy&$)NlV9sS}FGF@B~Aj7W??OFITpungUQQrw-t9{4~qL)d%F) zM<4Gg!Bb#?t$G_XlRybY@(B3EYl*)KY8S^CM94qqkgrIf;pZ{r&3)|QQ2~S-rt`!D zP_=1hG5`$$r=rZey)}}&uQBJNK(e6^e+;DT^vbz3LhvW#2;A zy2<9ME7!T~vx`cA^&5Ua#iR zcKQTAVwe4y>L~kgBaVS7WOL^0#roQdw2hQo!qUW5U*CsAK3K3CR)av-t81#~$u>)x}%+}tq!EPun+$lcv3cv-^8=qTrA zb927zy03T5Kn`}&kNR2EV@og_H-KeqiPHRmnc0ldjg}7ffq7=*hDgg1Z)-I&8tJ~Y zx>J|(&}?e|o)1@ihn_nK)S=9<_PmWyrv>9GyLEZC1^k{NpyZL&SqINStjy`^V+PK1s@JymY z@wtuZvU{#0x9B^EWX>O#APTVIQD6WDq2dZP7-8Nqr|A2m1Cn(r%?uW+x!02ZfM%x$ z9s;fj>!caWjy=h1`J=Zzn+}NW;FW^AB?$AD8OF)JQlp1SnC3G$t9#@|GYcyMw6X>- zqi(%!iV^`|p6^Hh&iB6MOTSmxVk{j$INcbW;%0Kgl}dA`S zNV+{1SPBg_ZBlo(O1|IBRXPa}k)G8&Gz!V*@ay}H#;osHZ74H3Hs5v<{nc5QngQ!s zejMdA#K&7E0hiYdO}%OPo(SH^&Sa(m)Fu5=a#RQ+&ZZrekH7pzs;)ES`S&IYcU$yy z5G~K#;%0vLM^Z9wh*OLagwGUsXrK!5u>3N#1mx*XcXiFY$L8YFX_K!XrHZ?X6Z)6a zBI?Yc5O-T#UuaE`G{1svXY@TEpccC5Jl6f+j$Ov44XOWGdI30%CMh_ho7xonwwdyM$RARcReTNiTeC3YgN zIfE3%^wvehmTR`21o?ORSQewTqi!M7H)dRCC?c1vuq$;Vt774Kk1{;?pa*}wEKW$b zFJM|5$C&4p4}eZLJ^sWu%6ivc)pHkEhNCa=krZ*$4uAPq-#j#37uL?3cf3aYXUk!% zkvOARmWn8avRi!`+a((Q3(jnyJrOyt4xSG>;1t&Gm%+PzLEn9?6%wx3wuQO0*83#JEop+C|Tps>iN z5=Cq*K&S!CPzaGHLZ5rfi?=+2U0$@=v;ys4bWQRxIC~t1#wrO(jQB$WHRU39ix&TA zvC@-*@CO_Kp9iDl2XSSBR(+GTxZlLqQT4q*Kn2s)lDc6Vb@9g@)z*=`&Ck7$v$g}1 z4BE%70FMc*BmOt=$N?D|H3`0({}5}BHXk`psE4u9-V(YO@&*e-SLz8XEKtrpMS8z~ zqH7{$q4~V>Ts%`1ZLrJc=cgNAdp668~3g*1DfA@|Y@HT045s~3vPL`t2#-c-8 zR_QeMW}?Wu4!fy``hX}+OtZ3Z;c#%$aX;ko^sDM zsGw`zeg7Ax6Mx+7gPok?0hVT}>AG_MByIXH=Ykb?{t zho|DlV5Pp4Pc`d;*$#JAUnXnW5M+EF{A}~fgm+7U(zxf>Ri7-Koxh}C4ZEPCanH85 zsdaVgnDtX;%H~X+QoqemO6E~B031X4m4i1;Q#kgyy}xc-dVV@IfX^P=|5xA8qT31Q zr@1l&7YAoFQf*0W3@$b!>w6F}=w#d|-02!UgBp-Z|(Wvc}b?8 zba$vg_bhj%X0_8AeC6}BcNJ9mnmX1P8@_3F|0Fb3o_5!Iz`e%IJX<1k_Dy1=Z%sc*VHdQMER%@K-DoH{x~`IeunxTwDn`S(M9;W}FB zky2!RZpCZmH^=WylfY$$vACD4mW7PJY^pi%lkG~JJ=Qj~;IyL&eWUkW@lTVWC57^8 z_|w~nK5mkEdBhbOg>V_`C?y}HzgD{>c{{dnXE-qWQd@aJhu2M}@^6ZsXCOYhER%qz z3XVP?%kuKhv=$0?dS-XRj3aoe8Ivn&1H=9wX%Rg@igwV+yB=Na3Mj^aI( zwW7!y0I(aV9OQcilD_k^;zl9XYv-SX`%|1tD#$HqgJv+HxZ~f{=L4m3lN>1wXK3K% zMFO^NTeURvjVMT(MSoIy(rgxEq5&~y3`p`t;5ot14%*G~F+h+s`v&*Jk4-JzpdU{@ zNJCzg{(xvye>qOlFDlTv`TLTGKS!r|ePC`|#t+-0FIc5rEySTT76>y22PgK_cqE6Q zAA3c|KhYM}yHn@;BD8V`Y2V*{WH8}*li{__)BLxrs2h-6=tnBv;)LbxLQ)CsmeX^c z>t+?{QEWZ)+3>Q@jA+T3IAUhI5~Lq251IQdAnYlh_t$nOcbKJoBEg~hwT$vLuP%#d zy^v1ZA1pt-#1Wv}23}ZwZs9EMWu}FP+E7x3WcS{U8)2qhpuuQQXubK(@4w3d+O=px&(Yk0%Mn5jRrh?p z9>`zTgT29Nr5Ta`^W8w)43i^bRgZ;BrGm6?cDgNM;W_wBCLPjulA>Al=){RDw|OVY zSJaOcO!FF^?PDMo{K~|{z*iTnBaJ{*BORy<#>4wncl%M<^7J&qqxe@^*wA;ho=0y- z?X0~W9G6SN?U7Hq+>=U&NkP@g6dm-JBo1bOh9MEs#;+4bAU@O6A#gMBT9pbSkmxEU z{e#iQ0iau1504F~cm=3GsOxn;KdzxYJl{DvIz63zMgrnPsUZTOtcTr^(no%luH z05^{M5-N|{gkGSHF};lk!Pn?w(v}*(o=Ff2N`xNC;U6EiwR&?HrP@s*pW1I86z3Fc zYZupyO`n_(S^515xSt(RMDf|A0AG>Atk+vp&Vm5&%|aYVot-MG|N3=iuwTpY>y)F< zh<*Uff!e?y@RzHi)uDNIMMzc{tkB;k-Fx<|PRkM{?Btu+(%iAL*G$zvzrtW|ptC37 zQW<q(HCXvMa5QnA>rWyXH=bqjV+sB)d;-9m3%-xv$G$rmqB_V zleUi|WPNj4&dslTgVYcA3WePJ5Ox8vW3)iHAX>#U958-q0$?z0!N- zDtPi(rQhNA4XWoX^7Huh^NSO0T<8pyMkaDCrCT}Q-B*Q#v^3BKDvve#`L!NI6#?wM zMaq<0mkLncSrZQ*()`h2_1g~!XFJGQUHvt1@b%o*-bsd7GnUxoY3GZ6aY8vMsdqzA znE(Nk2BtUG$p?*9R4Tl{LH9y!<0#gD*VP(ttP90Ul&{X6Q9}2p8A{hJG&NR6OShIZ zv$|Kxyy2>irC<@b>1-4${aR$Ely>xao;9*E)I6V%Nt6MZLGP1kPZz>$m4a^sU3DL} z8(!N#5ngB!@$4x10u6+eQeK=9b%NFHuTk@{4Xi-|q8y7ys3+`LX6DAwu! zLinl1KnC^Y=U}rQpkYoR$jFl-$idgbH2MLL@md=xQ3a?aUj51>6wn=3KT02O0Q@!# z3)$5=Hg9Dl2qx%qSq;QSk&Y{q7v^Pt(G9fwGTGLf~hErT2Op!9V5vj<^zSK z^1bWmxf~%_dR{qXBYo$;py=B9j*fO zZ5o~M%B`JX(v^<9=jE`6>0I$ptLGG=$d9Pi8zSj`M|%uY*Ss8rbH^uhdZ{U7npw4q zi09vbkfiJ0slcZ!80%o|?t*+4AL8cN59k^L2&_|A!*xj`Z zcI#h+=};jGv9}glvKRiQf2b)KTw87L+q&3^KAX9WsnBhzTrPk>@TKI@;Ri^|AOL&6 zpL!RyIs4OX>195@h+H`^z_P>v6E7g(6>$92JG;uApH}f_Xu(l(o&(hBzNa2kyd@Se zU~-e4sdrno_R*_O!2#E5LbjtBtTZF?n_X;TW~O=5W%i~MUL~Q}$rqrs?&m+_3_A5r zD8EvLmN83|L4O%HEc+A}n5c*8TuvVNfS_+L7uLG2EwdpQV|5fQWw=M^a%;7f za+x7b^GFx4HP@U83$VG^cTRNZV38H5%HkTdm4u>hNd}@svMUu!}=5C z`>rqB{uOU{fme0h5ci>VgXJ{9)J3r!`z;4yC!I)=m&m+`3`|Gg+*@_#@&syye9`-M(W5#+sxvf`J>!QAjjKE%WL(|gfTi`S^Ve(3E4lr zQl>s6s}Y2F8lSJ%$-mzcds}hE##bC ze*1dcypZxs?qt!5bY0l-kT-HhE|hU`{$=*2cDWZLmJ{ppcSnONJw70Xb8@S^!otm5 zc1w8Pv?2+aKqXtk-2V3J*}+%m(zb^OLKnve-2vf|Q+yz>qw8R3cr+DFMJ@M7O-)ci z=e>Vo&=mK}m(6H8cLcs(9D*0457*fQeP>$_a>jDzLYC^OlSv!c(?%^zutv&Ut!@3` z-khY!n>m3OAfQ1mkx24w99UibE}DGO(MKNj&N!jW?JaPD(r?m%;mv^}hO%$H-Jfz( z2eMH7KLPK>$laWr9QexmIitC;%a9oo@)qICgu!!Kw>TY4hr>>1PyF1IsNdsk=WJ); zAJzR9jJll?SJ9^>o0B-KXj{&Dk37Mh7L+N3hysVI_K(J-3&Uu|U!06QMn64$Zt zPZ6P=+}*nnn+W&bV!w;r3lQAig&_=K(27?9V#BUZUi&zY%3ns75tt(lt6<*9C*ki2 zS&n7iLf5YIi4Z%B>(G%%7BSlP1xY)79pjCLZ`-QYzl#3jYScmR zEDe?A+s=xm(7?#|2V(-O7{FzA`4O>Fv@kfmNP8dFYbIESdb!s`pM^%_M}IwQ&eI)2?^B|Acmst79AM#gOUy*@-Uy7f1^*$n^&z{gS; znm**Rxg9j=M;;x_TJ$yL<}Kj8FFIQ6hqhqH|Axj6=O1ZEj;GCJU_$$ww}L{OKm0gD zwI|&ezPZg*n`CK8 zVt}M!u@iQg1;0x)I=Qhb(zO;JOx1fUA1S2%4ezse>#Q)#Ev;0`D?l+-c@(K-;Q)_y zgA^sWvW1V9pBrD@g@70Q*1cK3buwP`B#ZS;T0!>)nJc8UIS4MuyMWp82QdDX&JNJ^ zTgFGvG(@$)AZd5L)FOu%=M-+xC;ZYxQtpl%91bV*axhRi@nl_u zL^!%9Qpoe=J&itOap--T4WC#%U?_xjPT>`qo|iQZ+)yFtpMuS-_`tiqR;B?FNHd-F zgxgmt4p`E~f*2v`FEi!dO^|{U16#h_)P7&m$J(i^h`aqjx8gc=jwoMPueDSU@7Nww4B1Y5z3aLTS4;J<1c)$<*?HB-XV05HfQK-Jzasm<~ws{AxYx>xC}=bsbRM_N{|V83TaGh+`mMWXqI2lgU(r*$pmg z$N1JiqF+q9VNCySE=2E|3yc zv&Lr79HL5hCV`hz+0J;4s@ zxdMpmZKETqN$CC1qh8$K7#>YkK(3;m5$9jfz}8N_cO0jQ2zHTDJ*q-8;q2MpVL?_3 z12zPBV&#YwO5J7HaaE!@CSsD+HEeQ&F#1lMwzBl(aeuX3&6ww#m#TtBufv~z9|8Oz zHe)5R)>)wc0#@=o%Ab=-wvXOlg>gc4L06lu-fNDG;D%Ol(F1o(F9kBPzOICL?z=jy zl$C1b)aUsMnOpeDyM3l-9N0<1BFPH=$vE#0EEbGlWo;k#3y(+FcW_NMD8A%@aq^c{ zsj%gr(VMa8_r<4H=%(8b{>;8{M=4_BLEUxbMw_iX1BgBCH`3%Fn#Xz1u|FEMlBPdD zayPe%kDp2SP}{FZm}rBZ4@pa{Gd<@QDSx7*iJAXY9hmmSK(SNGj8%ya1DDeHzPg&; zy};8$6IQw#9i$jTBwq9hU&d5Y3`9&!1G7T&8FiK4n@-c zbkG4P37j05s<;#fT$ zVI5%!>%(U$XxxVs2xW*=sRxCeIv?a%!RPC$SD+!)|1viRh^v|BM9 zUX1d11fkA^!u#X-?0OUs;Vd*25BfW;>HWfZ5Rf=}ldTp#=s5L^&SwFFH$kFb zBfKQf0+&SHpehUi7?9WdB|wjzub*MIQtexKvVcqFPg8;W=|Agxhm+qdOU<$X z*q~ZQ5=^YQYtKLXZ)N4EV=qcGULP$V1d_;((!=P>+7GdPJK|p$l(041Sam2sv0HPQ zZ2uK_fE7KFxJSYwxY5b!n+oDhOeW!`iqz$24;6XK^gR68)%aGHM zN#w-zzIl>_9zu$U^tow{89CJuyiaa2MyHf)%H>R_WEMHkJRIH)&^E0g(wdhHj7rhU z2a+2A#fuvf25`a1(B|j2>6eDdXLAc-t`#XK4^pW%>$^!NQ0|eYZt1;41Cs9?9U!(gdmMa-> zQ)F2(EV7@oodZQ5#)~ph;!1lrQ9uW>(A=zmPb^J|9FH^pC~K@*+EO^7+CRAV6aw4w zLlW7yVy&4WUoXM@H{^`y<3hV_iKJBLr5U zA&rjK1VqPZ(&4z$#*tI2lKz)ftl)_+gtUBH+Dht!ie2SX?p*B)@{s9OD-)=6fW{xw z*;sRRcX(rsX>xpDU-8}5riO#9f|hW>I%lbCI_ANS**6vqVSGBwfXZx~XGQ&wmNm1e zo#6q=4u&0ueqTr;-iCs3H_061tTsDkbRqfaH4Trq{e+ zu%Bl+`TpC9M2_V)kBxf!P;_oJ8-RIW#OT9%Kp7tfS2hU*uX(wIRKBPkv9I|(AvW|j zEaY$~;1i4n&AX4eRuyT`_3fK7GG9w5CDtzn4LoJLn2N@IuCKYszT}Ug>rnD$qP-&%CUmq( z3QWG601B{YiqK_B%*30y?;E5uLL~m$Y(9qQ^(ES%NB*tU*LW=2|EhCAAa4*^K(Piv z2_N(0jnSf>&-n8~b+fsi7NCxIxA63ru!kC9Fp>g|3pqIWR#2zTPbNXI@ZBL)KWBD# zSTPV9#CpDa<%Q=BHqh*it{d>vDqw1g<*|d25aWUP+X%Pe!RWq9{*5GoKUVi-by6!| zd9|kOXeepi;J9H1rOzFE=EWGQ3`yoXXno!h_)%~JKvQ%q-iNl4up{iOnJoV->Lg)5 za>mPjDXYm;h5Wtb4|BELv9LS9eSFz2$eLxv?0;Cy{>p zbgBefN>R~wt<`Mz1e(lbDrsJUU~_xQlDzvpi)*f=m0xB2A!>>wr~v8z)ULys8W#Zu z{3bM|aPJ15Wqo6d%I2!NtAi&koykcoTPzPO1;KA{o*V&VrPUU>-k5K6B;HuceIM1oGBNQk1WnrGOTv!<#tf^lE7Fo7XkGk)gT$ztXnW zBvk=TSb6LDA`g`Wvmp0s1AY;@G|qngbyZ7PCBgFA#^^+KW%IUaf&Bc-TX2s}Qex4v z@1iNOnkfd+K(L_jMh;i8fB$900?O8CPVMO%QI4k_CkasQ-j8qJiK^!Nv7@6;{1yJU ze#!}1+rz7b>JH?X7xTaWqA=usJ5s(SPi&T~oH03_1<$*ai5mVKh3_iVdq zjR&SPKR-44lhc1v`#~S_^Q)h5F~HoHulmJv=S?%`vhxD=KvA2MC4Fsetp~`j53wlO zSnIuoy&$p_pz!Qh3vP9l?Dth`dTNcl+89Y5I8U|nZ6u%649v8(hAvSCtn?%~UB7JP zK=$@mDb5$=(YrhE@W7pg!1&2Vv~%@s*yLski{8q?!~2(j)VjjcKaRr^ifxh6UgQQO zkMNy*UW{1f5W+VOX&h8v*=O{0!w z%fnPrF&h6kt4{i&Vz1Al!<|n>ag8E`xoV1>RDo_>&zTO4V$e8@ZTicpyU`VzP@`d`;Tsg zZFj08Xgi{y%7!i9nj;FG+4GoRZIsLKFC0I-NdHKv%9l72sJEEy9++7gOy|KX^LBW! z`ze7cP--#sk|EOmCz|ns0U@K*x_yTOYjJq+lHi!?2E0|d$w>i~$+x-r*~#h>GE%3kg9zC0173-$m*JL>`L z=BwPL;`Bd5-y3!E*zP!N;WIgT{tmb-TS24~`-krz4>JM{n83@*I$~X=bCjeAMuo>e z-U6((Qlk^K32R4JzYV?4iaoDfvo_DqTra=-8|88S;f<@;6G))Q7rSk+bI6cN(S41# za&agCMAc&`J?NB}to_~<@=$3-Ucy0YeXE}T__^pWQ{jFQf7rCH6+e?SA6%MdoxSj- z+#Xj|>`B6|DG^~BmVKq8_J!k_4s1i`@3M!zt5(y#@F7cN@2X1vO3#)l5b$4xRg36{ zc5(X+)sS;}E8+}%h5H9@VBeLp&jv8pfRwe#f06?Y(sQ&s*)|8B>B20|P5sivlZ8M{ zVp;WTUAB5JNP&sT#vN~igm#x|Xx^osB?wEm*JWMQl4p&*^=t{)#nSLzDc!qgAr?+e>wJQLF{Fyf+Nn3;4k#S?L%PJCkvKjlzmv!UNc# zSl*{r^Ldc8Ti$O)J$jSMcz)qb$Flwp(jp_->>rDcI3;1ZJZa|ljfW!JGLu&2F7;?$ zp8wN5@@FRR9wZQ&b-O~N9P}c-+$(A=$dTyDV2DxrRC>tin*5N7Co`f=-U&#f0RoEN zu6~*gu*WDWfMC2ZK~c*~;5Wca098F5nZ1usQMB6Yl-{#1xl#kWbn*#XTvFyVkG*@a z{l2c;KtY|YQVwDEI|v3^;g6XS^r9*o*0^TuysWq!oAZlkPfNxJhDf8z^$|`0L_7d& zj3cmM=qqBFYn-~)#%nm-KWp_*lOIE5ARX{ufRzS0hrIjk9_-u_Zz$gq1Q)rhvNb*e z8RaJnJg=N`U%IYg@Luio=1_rw0f0R!hMA-i*^UKl=)9}!Y<~KxhED9##WFTJgADm%tnyvr@zLTIa{`gG;zqUsn0^5TJj>0Dd zti1znjV1MU!F$KqAne?ZNhO|MT?QIk5F*Sv3zyL$19^QTMV^S9sE?-?!|BP8*0=%h zi*HikF#^70=3lFfa;x`ydj)Igu{}{sW`q@(--}prOzjznPS|eYuOF zua5P} zN5}uvjlRzKz6sbzMNP||EbQ&!pY*9jovdFq9Ckbjpfajb9B&DYI#zLwOpa_chI?HC zQ`R17r*ywa-9IypksHf_C(3_~Ze!@$TTt&ayP+e3Z)nJI+&qXIer{al3#dBSSvWr4 z-d;T@)~2fT9@bPFlBn-$X#p^~P1|=)`@H)CM$*eQGrK?3&HIZ^^Gt*)AA7Qr(p_S6 zzUq9z50P1a%B7Jfq**d3Nj5Idl;vVexo<$jzMb6AbpFMpFHZkcEFl`>@U-2b3RNxAR*Z zlAuxp^Ont%tiYQ7@MsFfkbF z9r?@5usD?3SmGGc^XOayKj((*g4L#7C*w@6LC?;R{sI76eg1In%denj(c-ucLx-!(}14i3`$Gis;4+L-cedF!OmOq z%^X&uM>G01b&V56+%^8QG>4sgJjT`Qx8%4cs5ydb|^`{r7r1V{wDlmHZE53yaR zbxGo^n|*6Ze4Zri)@J8ylp34(u|fg|vb%^;&w5-DZJ{za!tZ4b4DUaoxjPbK`|BgC z!o$~*_e*IPgNuh0bIqs8pJ_f(%YgRRV_J&hf9nkC^)1#bsA$TPN7;3J${@`{ zJyATiGI5E`WZOc%iu}7)i0UC2Q>plKlkzf-^31pD-Twu!V%zE8Dw%NIB6~w2fH&1g zm&JAJjNd4~j8jT%$tSzLU~i7i&tKtqg^{l4q%D;e1s7E(E3c9Ni6W*rFzfSX6K{PX}-H#aP;UaZ<>osUm-t$Dnof!kGKWSZ@k^WaUV;eM{4%$aiM zHhm-Vv}K9Gmf~+a^HaAbTR4+3!1}j;Q zR3R5RXik=Zxz<8ZPm%W`hMRoY46|GXnRaGo+Ahbne!Iq#0IL%dSds!l=%kGVDzii3eyOrV{r-6S@+)_cOpFRR4g6NtrDp{mi=k?wVO!PYF(SfR5s zDh94bOVE$pa1S?ke!jZtC+CNn?3p{%7!2woU{Bm)=6EbO*w)2b{X>@ZnoFI?#)3t9#Upz1-mQbZPa$~)IGZ$N{ zbCT*uHutoRicuf`Tc~Yqqsp$?s+}A|Bg6CcWp%|Mwo%Xq313Pc( zoU7%kMk`7hR^CLC&K!4MbCl(*`oNtBc7*=`648-gYTa{DW0R7A^W(ku zoa5Bvh@5-!s1%Q`q0Z7H$@HBNl9!xc4LV>_2)KQGb`{d6=Iw>&OnXkEG8Rh(yVJHm zVTM=E%}t&wP<2a4pH9UASqP-ymU~rDQX2Rl?8#z9JS}DoGbEW!-h{wl-E%R^>MwF5 zJzOo!^@tFB_Gf~MY)r0u-Z(FvOJtx$4%xyst$jF1in&kAEbH_X;d#f~U}cy##0X13NbrdQv-=0$ywkZM@=-g zgC>3u9jq-aB(5~j#Vu>tZ_RQfI&Fe%83uuoVoBFf~!+ zG+pXCZd$`f2EYyGq*@Nl}l*Y5Eh-ATxe91 zTme=Fe$Z3@Wp_b%;hFTm@r0iKA{=eBDboeuFdANB5IXf7^I4%GEDLwY_AKaI;k$omh z{a+RM>@8p%Y1!3KZx{=w2UyrvCMg(h;z!|PIlo}U#{dU95w{K06;b;ouS_OxCV#!3 zr-hyR@1W&>K!pyINsbn8ctci>g^32dZ}O;wk$&9W*Cdz#$@7&BV2*F@0aId6L8X4U zl{wR-wl<0LzwWc0j*-aI{eNfY+tv@9r}w{}&W2NEDyQdjV<$BbuSfT~;Fk_IPllrk zG{^n}K5dyv`GxZ`zglv!JP>_zMCZM2mx8cMbjxR>&4x{KK>PZB^T;!Sl&@3T-cad? zB1y%E&JTINP5DvNGepnm@dlq8dOF+QzKh&KLSLP-a)l`F1sOOWXv`agb@wx94=1R5 zu1i{d;Gnp~=VGY%{0TQyf0Gg-z`*rXX~gR?`Z@vfca@y~F_&NUozyuvA(OnWWNid` zZCsJFubMI+=4p(gE=$f@(+{0x$w38Fl6t`o`GATktKJk6I+!bpHAjPg3H*fRoarS| z?}-jgU^v>#U~*tHtV2WR*-ZOtg_CgUl~oM zDJE-Vn#wfMBGC?|A8`X-)ErNr&HWpk|2bZL6s@sAu(<^og9NrdyiYFgn^PUOu?jCZ z_>k>#iM32IH6fGIEzsdQb2CU~{lv4k!n;Bo`iaJ+vT6ii9G!>zBwq*nJ@;kI(Z{5` zB;};F!4&o-fLloHImX;(^`>o~ttq4MILwq9AdL`^gnemimAV@6%L|UiqIH1X1z6)Q zbGI~0qv%N?Yl-@Wo}P`s`Q=_%V>s-Bz5}7e&Xn%7mCN_$AXqFchRS+pRYX8v^RtSj#(#}LyDpm> za0uy_E1GdJV$<9$-a-nfqYj_d;*21jB>HAhSpo-`W1Hgo&DckP!|F{;ncV0W<7W|A zdb#e+KAf@6Cxi}GCpXTXn{Xsw&M+n&ugp83aR>7A0t|>)0MgHw2CRz&9@fG~(vi!o zi2fu*a*WA4n9czpNeuiwP^Gj(MO)LSz|`vKzgo4b)#<>S7t(*MXU(`}^~~SCK4P8U zWk?HorKl9Z#U3UICVk+x+7Jf0+vnkwY=^Oj@f_d9x3>&}zSZ+rw^!{6%4D-XZ*kQS zr_daATiX?wT?b0ivjtnXzg|E&{d!O>n5NW~I|SN^gBe4#g&)s@_!Q!bSb#+fcPF5L zoQKy$V)fG%nH=9ICGEzlBo3T=h!6g~*qS-#G3cSm;ZeGDMR$?=&$2>LN_*yk|HH0U zHpmCK&wIg#l^<>gTg<$0fZti#Ksk2i@r1d2Ku>egm|?cfci$13PL^K8+FFzkc)7C_ ziu!GjdjC-A$P22ubX`#t23=kv$aTWciz}2k#M5t?0?BPvWf^JNOE{xVwJZHY@&xOF zavGdTQA_gaB#~!;d60s}mR90244~ZuSAdX^5VZfsA-hl3s}Re(95?6%mo}`CNone@ zB7O8=&pR<-1E%?XR>i#A!_tb}SH|LGB*#g(q?hTvB?8f=Q3Ys|VTiEPkf5F#jhh?G zeAa7LL9LO7Uc`orYo&olOghLslJE(O@uC&&H24r+hLvk!1FR&hh9zXlgG7{qL4k&5 z%kO(1AYo#2GgG7_-i$ZQfcR1v(cc|Xw`(JuOB0J@G(lP<7(=1&f?y1PK2w2LS96{_ zDIQ2Zu+F`lj>XzUe8%eJr4vboX%OkYU4n{v`iq4>;u#ZqumEm0 z4w<^n$~`i$8#UIEj(>~_W|yUQ*VE?TOb;|5&6|JExAUTi34k3fb&fgy8xedY0EnW> z7jH0ZLm>$fPZuIXzGo}s4NP|g&B!ilQRsmL5_bj?cpWPPg{PK}j~`)szKxk=HgCj5}PT zob9mWVtH99>)hO~KVc_rEl1t!4@}iI*zSe2K4g63dllgSX80ZTfVWN*@Sy(Nts43sZ$giKnv=CUAvYN4+iXl=eyuu){lk%)( zY6_zNI{D-dHC&l^JL#9`hiqB^^%cKgb?J9+BTul;{SmFW3=fapZCN{8GGOBQG9#2S_YLk{ zCC$QGY3D7D=&TnrR4q8WyLuUbvSfLD--lqC-$-u7=7wvBO>me&)k;iSoqTNjRM>Z+ zoi+*eqoA{>@TQd`)#m@m{R-u;R`o63@)0;52shuOa?+v3 zOWXt%w}M5yEg~L$dNsjDY;ld7TFS$7!tz!YwZfvC+rluFnj68gv5csdWme2u8O`s3 z^X;Q=BrXU&#oTbrCne$zwxAzetDj0!vg}L+8F{aQpP-Wh%B=q-x zYIX7|sA3u*d}V@4?%2K~?jeRS!PP6(RawYV5^&yEDldx#ksGeyY-JeckUk&frAy7? zQ{P5gNy?sH{FuK-TH4X*EPyP4)osE4M}+`TOzvPav$5KgQR(Doxl0$Q(R0tY79RA% z?<^&O8Lyg0yJ;$aL^Jr&@>}uk*B9@`G>GllH zuOG&}D#eIfcdT_nvc1eEzl)HL-*DHh> zjB_x0sk^XdXLfHO@PO zrJJ+Q*bClN)=VjM3TWm-pxt@`>`$Vn6Exg1o-g znJc1g{VKq|v(32ySq@J#XkfPf5yDR(Akjd#K;Xl#{feO+V2!l<%Mm}{Yc41FN|1=` zz~wu)?nxwzffvTF9NHjs=Tm+vF9HC;eRFZ=UK)<&`ht$BW&;4XG`r@-y#EG_&ZXP#UMr0r_rGyntV43hKM7B@&91C`J;SzXc z%BI=0Dc>@CaDYSD(Fc@@>I2#{&aANBG^`=$Vk)wv1hu+*d};^?Uwro5woa!VxtnrZ zX_H)ST~}f=q@mEwOCA-tV~bqjEg|qHbDKHQVBM4Lq}&o{@6n=udYH+qex&XnU}BSI zNcZF~^X6R7b35A;Gou870nKYdjZ|xK3ev}~#;=oerF$;mYwImnCN7c7p%;58)V=)q z_PA7zoly6B%Gb!2=(by4=1~`?P0?-4?9A)Pfxf+hXGPRr>+w#b-`Cjha{<(F-_}Pp zhB~MH)u8Az4K3`QCXM8{3eSWsfjWYsVfmjO4K1zcQAl{p{6r(S+JJk(HBxnsS+oZG zX}Yqn($s;b2GdxSz=kdfKHf2Pc5*N??I%Q|HoUv%n|Dr)8cxcMMeR?lC?Gal*TXA3 z`vryFlK`l_v79l-hPq<4m$@Z=RZ|1Q>!*sSBa{0LiO7EX5+lzga@k2$xe0Htuw9! z1PeU0Np9#sDz3;(^7yioOG=!cU7a1;Gw zGnKw5CmHSxl`~1*{VH<-ZNTqqUIRUzc5F8QzZMrQS3fvPlRcGT@hd$QQXfBIl}haG zXQrN!V$&uSIb}e$;ZZotE0s)+)~pUi8DcW>x$UA6KmneC_{G`G(Q#_yfXeJI_) z#L;&diUj3u-59SXi8P@gRG2K9Od!UX;nqhT3v_`ot?)q*a_ZW=5wnrMxM4Sk$(iH%)de9e4cj;Mo6H?Tkl+0o9g!y&RXQOL_Cgmc! zUU?!CZc4D`++1WOkbNAjVVY)$)Y1|lMXM>(<*q1e2`gAb2j^Jwt}B*~)LGq)Nqnrk z@s2tVcXdMHV6JL2wi=0|waFXYNGHWEPp@`;k{9zR)7w<~DsMk=LB;*WK1`GIs-Fih z=AUQbn@IrpSEWYwzG<*>-!@e@qKvds+H$dlCZ%PyEPbCwXc{wmv#hitx>wIqj5iM7 zO9}zl^#Az&E4BJoFD4ehvV_uk9ZCiMo)>;qU7m*h;Jx{tt*=BQgQUgpJOB7EHVx|X z+i|n}`Vm&uk;$S;<)g0=UIcZvLUAC7k^Aw8y@P%0056n3Gz6q*e5JicD7f40nU|kP zqUGx10;wFArm#v8npWb1U7^8@B`}AZfQMXt+w|~Xv@@eKC;md;TB?da%fAKYTCElu z0YxW8%jPZMc`qGBMtUF?(IsT^+d+u9ypl7|P0-|{3hU+SQ|G}Vdp|;%TBXKqD@J{a z&d^|aDyUXWoZQ3{E1>r)xX0bBM2%QnCz{BJsMWnvgp-B6L({Af*%*9Cu6!m;rQ9sf z=StT6AqxWmbz-MKj&dhqmg z^Ffp`=w+Uf0SOJatZZeplxUCs7jdZ}>ntrp_GE>Zz<{AF;-)kVNK7RLRHP>0-LE zX9sIM$otjVwt+HBcEP0T;HIb8QegZaQ~wW_e?K_Z)hfXle}jlxT!OFCW=CH2NZt5~ z&jZCzXJBu6rSTTynr&>q@jgT8=KbXd8Tp?PTGv%>docXh**neg=A_p@86>b|zQgD1 zOzBz`TxT1Q6IyO0A!UgIy{d>uZlr#nHzNfqBbz3*qZSUZF@G|;YSiB0bU(ZhgYkA` z(rIZm+kR6NStE7obwZ|;3A$cDSxRHFZLZ4;4)Yc^A>5?#$ExUR!qku|u^1uGIr4A? z5sK*Q?0lxmhM{DPe$YRV!~c%p)!*n7dWmj1TU4=3Z^Bv15sqI9oSP}hRSA7{qIVM; zlZ~Q$IvJCBlF>^e^WQj9tX1SN^|XB!E$kITf>2p7g{giPze`bNQUeFnw-*-Z_|T&S z8;6Zr#cn@w@_1fA+MaQ0PFE!apm2}SKPeM> z;aZ}?Het;M{5(*O>gwXVI$UOH$}lnhrv<~f!m_0D)ToHY*O<>v2+3O9>BmNRHC5fc zfQ;^CD>HHRdfL3hVBo#N1i*Jvx10dKO9o!Swp$)SpZ2YVCJlpB6BA_C7^tQEIi{AQ zIvnRlUp-<@HZ06FHKG;vp+zdhw|n*-R3iF753U>1^nUp1yk~y7m$R*CcT*BiRb61t zYLmWWP;jP#QSFcQA_T#tdLRjm-=BdcDq)@{le~rc)!8*)9Ic1snnGEEM*c+&k$jMA zv3d3b#H9s zS#+s)-Hd#Qt67P=hyCYbBUDqF0fY}_W!!&WQPdbDFi3eTa*@^%Mh*>yL+TXuSzSJ2 z;F6RKoCLJwTvFJeTF6cKasL`X8+o9P^$-)e>;0^+r!WKp#Nq(S-PFJvDVWcdD^ZW50gxmWVf zMGUe&j!qv%@UG34i?3}518JVz(WJ!bD0vkB?QOE3U-X>{9LtT3Hhf2hrnh%qFdrod zX+e3ZNvf_%mm9404^SfhB;+M#O?s5~Ob<0B%l=RedDHK0UIl%8>MLQcX2V1A1G7Hg^0h5P`+^%HUxZ~Hx)fMwUR~H zzXI-47=unb>Ix5Zk_oWk^F5!P`G*@=n-SH&T3lS(NYx{i43Kx7NpDYuz0sr$H~gFM zYqae%u5MK|!4x#d*VyxdPctYizJtSX>xkE#iMHFOW|CwkG?u||2P_KUjRj~`e-?aX z=30kUL79iaI5c;NqXE8W1iXJz;u%KP9)QvMy79tFy`kdbMZUUd{6dXJX^l;<^_ zSR`Vj0ERpNtv_y0JR>;4O#6U;*RFOee+gBj!8u6bLHR!@T%pyy7NPlZf_*=a+Ui5E zl7mJU;B?Pl_a*g~(G;3mCAfO}rvVcwcde<}P?DI7lxo@ zgL|~l~TuTolCOY<3 z=?8U@B)L(^>z{e6--6EI39Bv206>Tw>X3JX($~r~%6OZn!2GI#?-{6@(}y*c^#b*4 zs9M#_$R}pJs=;?-xy8iZM2XF{b4mI=TNFUsIui1!%gm{N_wEPz;1R7dQ6xM-c!*hE zN})?bYAjl^a;vapwT6Q#yOiF6_OoTiI}h1^ya&;u?6r4_*sgepG7hb?dMHgBz0`@Q z;PW(YFktgLqhg5t%E{FO^Lp$vMfFRfl5_u-U~l&_Q!k!9#)!#=nafc6+mH;*j!v}d zM~?Tk)YOhyZEMqY?Qd127Y{|KAMU@(Grg+l{+JzQWI{6jz^vLOs!Y5jU+%=0u!k%E zZ1!>mccYXz!B{S@Z}{7CogbW241k8vG*IfO zG%^$urmSX`%c8;$cm>#lBcNfbJ%WNd>sO(A?WK52&7 z<%v#M(a^z#-x!skspaQch_W<;TuNh_txMlhV4U*!u^`!}ykf%hj|bl!hPOP7}y>6|!1~6JcK~5jYD+p@3D;C(zfEf}-b2F_uGf5m-q~``9CNFM8(mB1nuJ@3K z@mcl8BfK`UaLgWlC5t9&^chLgv%M3-S^XSdZWS)ZmBl&IPIDXV)#hy5R9PWs!=3JT zHUED3X%2gNsCDJzynWtU8d{*94q@L_;9Eg>-_WgJMww=KtreO|Ho2=T7p{Z`Bl zZl;{^CJ@^G+Zu&m9@_p}GX*)zLu( zWltD4J$uJj%7OgpectiKfz!2ZnP@+5SIxo2gNcmY`njy0WcJ_6Lm!KGb(4QLl~qtm zr_B~d7kY?W0H*DXv%4VF+C`*6lMByues~;`2052;E(w8zq;sX zDeG$hrW?7{F(y2Fn~U5C&k6#5QANt6(z%X1L(6$LLW+}$5UUReza9(tXw&Qnvvf!3 zCt2XG8|6^AU5gQv+x>JefeQQk=7@X|5zk={E4(>=8~ws~xGQ^Lhx}7gt1>RhTg?=`3V@|0^jI()U8_%i|dpc2m^f$uLrz zqUF&2Dwb47PgoNeV785VwBsrv;+xI~fld=@%ftjg+{l)AO?3Xp4uX8xpidng&DRwp zPA$J^8$PH!QJ_5TSm9a4>!%{buZF35@Z28NEk>Gys#?7NC;Y_9!IlLrL_=$pX1>;+ zOZM-*Fvy1wjzhD7Whdo?R0DrS$*IoC0^**5XM#gaXM7b#wt1Y;@mw?A{ZTD6yYT4X zo&RmfI{`pd;ovJvYMn~?J(cK{4cY$50}vK|G%;?q9iiT%k<>?$SRwTopbZ2vQ2=rI zXDR~Bx)w$%DuD7~?OtGZETr4fk1QjR+^FQ?QEjR3Bh}4qOq?FwZ~H1KmRf+wvp}l| zpsVwqAfbuiF)2?*hLj-XA%|hBfmDOnx2UK91MKrG7Dkevri=`YzTTakH`lFOD#!-s zI2?&wK`&oAmD)GuRto^uMs+Dxng}^|h&`POQ1pp7Fa%Hg=YiToJO6<-`>uyv}?}ME8Ym(^qH}6W= zxM`m}c-Kn0o|!ddmtU!Md)=g1Pt%$b1(OH-Ym)0@n=3|hOUDSh-xT;_;JVQi^X-RJ z?KQ>+Rui{$O83Qjkzs#5kN8gwzv)TO36VZ0`%bk#MfdPt?p_YW`&=czNyQkU@C`NQ zWAHM0_dD%LyZKHi7;le>@z-!n4(~c&U*>OdRv06f;f-=kbhqVleG@D4=kXW#SHKI4 zl>$rCfz({biv|!R*M5IhRA8)nqt^gcCmz7=#kXqEAv|yClI!C;32fucl3dIVxASv# z>wdTpPhP@I_R%YVvZl31LjUtK<_N%Ygg^Zap#PnF;my6X0BhnyQ@za9d0)EqqLbs{ zvDMY(5Y(ip^-;BxyEP03yYVL9-Y?5OkpY|qFi@eDoeZc5W~WRjZ)6|kjXdey2@!o> z*`ylcr(L7`DU@!62X+T*u&DqCu=YPIz_2e;C#X4DkLYJ@AMNL=8+Ifg548X;H?rMD zirFnowQKOP&e6RE?8%AA*VIVOH*I z!EEZF>WPVF!uGJzE#^$j=V?E(#uF4OMSr~7rz5F18!9Pp9bokzv}E)#wBq*7jP-GXBWq=%9*W3g^4|aK z?Cds#U<3P-s@o%kS6C74rJ+<;@AKaf$fkAngJfyGA6E+n_RbzJs-w{i2ZiQe@wdxm z)rKUsOcp9+z1v1QcZrd*!BU=s>zRz$=>s-4T6oqK@H!SEE}qVG!o-{?@?GB2e2F$o z$Sq#I&?;PmkF{xC4YPDUAp;=WlVxgcw9#?JnqL<8v>)dCA5<%~4Scldd0`NBXP^>y zEJsvvap_`!T{GP!Mr@Js2BzR!A;tP-?gpGeI)aqeI}$muS%YKNaB*?wZ@$i0vKp+( z68i8}j6))56x_Y?{gsk8O1^(fqijZX^i$?<74taJx#S?WS^FA_aBbjMRO@K%p3vBw(!(?z;t zs63zT_bHFIO%1s3Y+DTe2*bh^9IsCB_p4owv9Da`#Recq_Qo`6h^mc-S$oQas9Ems z_iCIjO;*&dF1-Bvl*+j!Chvnd-p+SYW}>MK7b#FP7}@GvUmrL^q(BgOIxg4E<69l; z_|#TS7zQSX>=^~~ZB}^KJFdH%jK$8ODHT4%AV{K?&0RiUI^Q_kB7;)MlFtvb|8p)UZ+U%esL3)NYDLl5^O7qk0(h2a!H4aGHg4PG7Hu+gCE@$@UgL{?$g z*w{C}%~M++1=XpywO!8{wLmifg<{OcRIwarEx2o`TGL%B`aGzinUG0dS7A+i(e6YE zCTTE#(?N})r^1O(BmC`OoQ)w%@5&X7or|iQvkueWCcBF$6D_Zs z`0x$ZJLYH}cq~?3t;4t;v$AC3Qjp%jLyId?+J9h;+&!hYQ66I+OhJF9WC1$3TCh0x z{(91$tn-%w163`x@B_cRx1b*bSOa10|KuM2$Gp#zPp=H5B2n8A_BK>WlBU`H5D1mI zx`gX^AeZ4Vqb;im{Mis&7Li-|@mC-wpH+5(bKOoE2H5|td)=kcH`fAP@^W}}bz5Z! zPQzQda$VM(>?>t08I(3zDcUQb{FQ1c0p-C3EBl}Y_YnOaboe$n`KH)EiF+@2e+<0m z7llHfK$Pgb=AI?273g@R-O}ZOs9Jq`0XBMNWisB=tGGP|t-3N&f<^~h|3p{V*8qaD zvYDm5uF#9|E6dwSO=EZ%Y|8}-6LE6hNEqge6?4Y;t#@$gDPz(tN>a2_c;$iWvgjjFaEZ?2^A4-DK^E=?`;47$Hu7W~sIL*V~?0>)uaFK@$ z9~#F5X>Mizpp}>(U}6CI9N#yBRD>oJVL#ME;gA2FR!PoS1+YolxtpLY6~hCsR(krT z4`K!2He--d7fe#z>yVRJ87!{GfkT|H0+AjkS||33UO1+B)w7H34Q_pO>wb<|yTM~P zg2inCT5$!*aBHmkO-BBvaSJ1&RG+5rLz-H3Fl`h1o??lO?>A`cxSHN<^+#bZ=c>aW z);1eJV=7^)<<5py<2|8mLctqG=_(4ZOo}na&)(UIKYO(8=8C*$T#HB2`!mb= zRyUuW?}x1Ghwq;^&hZl$)&IE)_a%n?=?vUmm2vw^UJRT#pE8mVE-RxDZQ9!yDA2mZ zR(}VxO5d9JNAcI*Bt^AbK`1vg>OG#a3y!{{vA%K5O z6LvF{w8Mu5-4j^$3W5A;j^$rHjZoidCVO^8s30dgQVb67;rov){Ft>E8L3O;hP08- zud$1{RE$Nn&m70LJ4(aX=D9TW%0{5mk5a4?0n4SMN?x)y8>k*Qh!*iVk_48oBT+Wj2-dV+ag6|?*?jm)N z4NJ7oAq5{Dz%=u9y@^*n*fS>21im#Gl=(_JI_Sc~9yCF-v94xr7MVGaw-P;Tel{gf zvF~&0n5J~|6f95P3R|87Rl#380);$VI03?O&pY2s`%RYgc?T)eguv*9M8nxD%71w* z3c@8&0IeBtf+rq)+E%Xfv21||F#qEtsqAmoE1R{V8d#W~%Rsj$B84=ts;G2uHFOuq z>>edCl`cnI+ZdMb;Hg+xPZ)fnlDG5|BL00OyU+!l)x#B)C#?PFZQp^9oMMB*6A!7u z-s-&~Uv#AYM@w989-T=6y&#>NUOqDN7!bg>XYLnR$Fsr%@>{tadycsZpcC$wxO`=j zt(O-A4T)qy)?Au9wtE>!10UWje+~_nM58Z1#hfaQX{4g zyflj;pS?`f4M0wAW$d0ztTg0E_W1voy+ITAq=;{*#| zb?;06IjlcjL~D$MKP#KCn|Q0RscJ?Mcb0eEpBxfE`|HCm5A#&LCGYAMI23SJ`wCqb zEXgSV3o(yS$VS)QC4uwuCNkXGaWO)IrL7#0DFB}X0?WF1zcIpA<9;vjvOAWAy05v%on}#7j`vL{XtqL=dprcF>tODWyt&=S) zr^t-#f807pX)vl5VD8eusJjaeP9H~0g%u|ijoIvGIEcSUPG5ZJ<>e}>HCSb(MUOnZ zJUZZgvr9e>ix&XH%q?|_ad@t93H-6#&|-^oo%^7ww?8w~g6Cd&#D!Wfc%G;KA0-=e zC$que>yMD##klq{8$WUQ&kW1eppqVe-qqmE^TX=Z^Of1IuFmzf$oDO$yoh!MI5(GI zCne?mu!fv=y106$XEfB}dvkN(YNYxOtEfqcu38kilYv`{S83(@=24r=v7DE<+sX;? zImEB_gg7u$G+KCCanjz(kav<%HnEAdQXQJ*C)ya>6Vi#M@boixKPc&3R}_xJG>sLaWG%;niYP^NSyrLhmPtyv8A&Ueeapa znCL=KaR&dL_$~6qIhnlnCde>wF<2<1EPlaWWV_qmY+?a`NRxw3aXbv3M+@!8L?zqRaPh8n;{gx)5-)jKR(Tm zKQ||L@}DH>JJ!o^wdTK}Oxd~%o}#Uj2F0!H{a7~munOok1hj5+m{WXBb5&9jn`e)! zHZ2iv)ECwf7A+|>Myjq}xgKor%o^D|1zb=Wf^s6!?ATugs<;G{TLs{Ukx+$PJ5EG8 zcH_HG)6jTfyWcFu60aD_2nf_Qs`h)mq{0s^1yBJ1hHz~wXM5e(ga?pyZ%UYn8ifW6 zFl5M~K2LL-&ROATo(z7K1Scyqi?V1{(?Y1wi9J+kKR-Z3@yxVwwbpmS%!;t&F8{8l zA0Vh}ChqKlUhwbH#gAypm&-|i1#Xq6<3#S6Sq-ICeL~Fm#;M_FhVJcXl?$#0Lbxs> zc(Hw*6qqLT^Nir@F*Rb0It$>#R?%G=mDIK3y3XzBY{=@Uw`Vf0R0@kRa6#P!baX}x6bZDzSCIn2GmqVZdo4NbP&I`Py@cIf?EALv+wYJGevk9YFX060 zCAy!I0y>TVz4N$(EW%)depw1lDvB2mek;%|P!SL#4)$vKg=Uzme!upGUhQN2CB443*amk%mV>Sg~56}v@@G|bJ` z*mm++zgU73`830z5USW%`mB&rZ%J=j$ymrrLon7MW~Rva9b2d4Xs+#ntYwy4#n3K$ zjnlhC3D2+8f3Rty1!p31f8Go_pRmlHQK}?H= zjbNiOJzxy6w6@f9t8h%0GnzNL_$2neoWt<`ye|{FNeXl_*5uyMP>A!rQh~99uY8Yz zgq#|rxijeu9^W-x<;5G8x*!HEu2s0PWwjKmy63I%L2mVUu?s#LEdK}%-giy(OwX~? zyJJ+#_sY_*7IAEIqDs7zWDzOy)H6-XmElD5?{XXaen#1y3zVE&_5N%J}o$DsZ z+m^Pn4@rU~={N7AuY9vuAcN7Myl5-ebi18V<*>v95e4Lj1q2aTS9bCRAs-a@=>Ze*AHT%&qbk>-vin^Sj}(xJZA=_%*iUIDq6!XWJ@MX^ede`cdJF zU}iLN(adCG6Yo#%-1X{D86m3d&! z@E}4R>_fgLFvEWmm)7q&lo|M2Wstb$?zg=B_ty$t(B%g~9kLQ@V6@Qu!4?I?JW-N3 zMZj$G1bj-r==1l`2 zVS`F9PwZpHAh$tqhN}0>23xAWy*frdJgw?vlvkbsg4huj${N6jc*$5?@rzsoWYlh3 z{0Zh$4K8YOAsEw8Y$c%M2t$G{oi~eAMLD11)w{Js%wo8n#!ccuTd-qUuy_)bV*{rRO6CRb;k zgJtiTHr2&ub8_DoUKbS~cZs}5a*^ze*_XQTNgmvbw5S~EO{)HGaOv!YldnJj)b?5f zqhu)tJw?P6B`}Eh4`n9NJ~WZNU(U24&Sdu1yWC&7HkgACLwh0eHwhiV_4Tyr^8u&v zq`|jy7|YEETq{}nZaWTO2!rKF!`s;pz;+8HQv1)H3zv(Iwpt)#(BW4LUoymwnvbY0 zP*LG4Icm?`B&QJkr&UxlI=mSqfffDgTf+%0xR?K+UI1wieg4wmzICrbl_{4lDVI)2 z3)NlS)KKm$A~5`DVW%1{HrS*&HZQvhe@znTxAaM?w)FF}19*M~nM<--cum|NBv3s} z?-hy?|2#URncmc{>wjB7#7#u(pq6m7K&s^uf=e23!73D%s;`+R9iS`j&T)fPG9;@Ft2sMhTh8ICw;~ps_LX|H7>wnh`3-V5=0Txif28E~<)hJMQu1%- zWuc@1wo-F*RKkoUA21{ z`h=hjt8YHM2LG5oXd4(nVIZ&9ugJ;4;8#F`UzM%U)|KnsvUK4^AgZj7k|f>GsF`aM zroQbTVit#Ol{gm0Ge1-Nkwd8S3q`_*(AZqzkQC7--X!Gj_j{g!)LCw~2tI1wWcyMM z!_vOhsd1DLuwnd(miHY*e48k9E84g%!XvGum!cRxlZs>X+WoEb2| zkNW2Qd#!`Uj%p5$HVsr;&tzBv?TB-+;4TCw`|$bQfXdVGra zKs{O1S`QF#ej0ztqp6d<})V1yedpgPIz= z;-U89o;bU;ai#F)-&4MIp)gGoe|ZS|9G`ONTkM$eb}Y@yr`1!PyXXcNX<{s?j#8@1 z`n$O}15>+k?}~Ty*3M2`Wb6AWU=9y8`sWaWEaOn?P9iSe8=LYdT~*L6_a}q3tXD_J zQxY*z;`qE<*fh!jtf`m%X*CdD&6!+g*>>-!ni~8!7~PN?`pf>>W-?ELrsf@&y8N@n z`@W}T6d8SM?uP3dR=7$=zzV?ZCRF|Jy%GDGR?*8KvV$M_yahwg-s$KPnobNJR81Wn zy-7d6MI^XTYk42<=M?l57Z3FE(sPe0&W063-!%=5T!yp^&$ zGA3=WP>7VowevO?OlskV!Oy#gnj;~?*zz5;F1q|g&Xmdx|FU~M`Im*PjF>HZ&KAWf zprIyk=8UjbW)^YMTMMeUK6w)YjOL-mtpyHo2Qep>!|djfu`CnZUuEgH>|Rzr(r_a&B(wE? ze%Wu%B{lRjdqmcq233CpFV%KaFdIh&l*3@E>ERV8-(ir*^CfVF@zK(Tg?zJw_!%G7 zWM6q0ti8m$HL(CQH?$$r(a2T$`^1Vl=DaHFv?sH$=7qZP5UNa<)(J-+lX9lPc}4uV z|5zw-6Y#16U;yv0&C>QE*ht%L2nY%-UjB*yR}Y*K^c;UVGLvPz*KOfIgG#_v+wD>_ zIK`6eQX(P%F%Ue-3*!25ssO;#=cpt zCg@AHBw%Xnaon~%t_vHMMNURv-%3DKhw5;(cza{Q6%12D!*XwOHaaLncC1o_6Z}$38gpzX!{?}7&{}eyXJQ;61@3W_8@RK4jCF8ZRjxENzNUP~@k>yO`kPXZvd0o^?-^R8Gc7q46`=K%>=Yf`jibZ8mmhi}vGf6O`T zg%RJi`tl0_nEa3GRNv8{$hC;!YQ*S!J#Q3da1%m>%2bd*O41|l@UWaNgt7+BL%H&< zJ{LbToEZ%Xv=@M9<6EM$@-0PeW<98(m!lTOIkm5>7?;|p zLS~$+M*BA}Gpf7R--&!34|}!e>9vE<>-|QWI_oy4nW59sqGu2k28U|Bro99R3JVL< zFx&rjZ<$8m>xMCQztwM66kIZ@Kpfz`AdO%oWw;dCSsJ(-*fzMj>Oz`!U@sTsQ5%}zW>b&5 z*e1ZIE*^6~LKgsBGLn+;gy;U2;ZA>#AH$r=;?ET`+#j70$&^gK2>_izG`Eb`a2Z}+ zBnB>=p6zy(_)W><8Ir-SesQrEALe$ac?2^i{00vQGv#A^{9o2^kBhwvYwC1z1`Z+JIR;?6GXi!SmUa(|d1y^hY^R7k;WKOot?VgaG zt|@ahS-G$T$G1<=DGG(!`Na+Bs#nTP+R1u`%fQJ z?1fe%<5LPpt+TW750RDYk+T6Os|S&#^*@xIBmhzQ<5TBx@#@Z~a^J4UX8Ak1NBoz6 z-rQoazMW2e>&?^Ci^s~Au%RI6@$d6E8P>I*$HXChw{*>@xxeFIl;rnLqtrV~{c6!I zbF=b`dlAru^T+Nh{?|op%x+T23BwuKprkR0V+r--%tX-FF~F(1**a7*V9LJc>n+Y_ z{V%}BWSeD<`^*;^suSmNHMx4`lu_Cbr*r5!EU z%1|?kvD|;|1TiQ(cQPrWXr0^13g*+_94;S<%$>q*G`$O)_}#R$ zM#>ZzmR7qR{CqG0a!XoCmAz$ejqEr=x)ZOt8q9WZcGh0$6mi9?bC~Tz9(jT9THA^| zyC9HaH^mRqGIS_IHHY3fUO|&kUTS*v+3wfL>fW?dBt+aNtM0Ms4;eexe7~5O$gdW! zP6u3Xx+n66is~M+Y4p#(LMwIz{|pR#T4?4K!rl@WlRZv~t<{xGPHaRMF6cvzS7#F# z49fkd_3+FFk4hzbr;#aW{t4NO6cv=Q^8K$bJ~s?iO}{Xq#KdW=q-wnIoO^wKa8oO6 zkT}yCo8|X#al5H~`K|Fs>Clqj+4FHVR2`ye*xOYh*aChLi*v;!X{;Z{vFU;-PigBx z6sdkJ_(i)Nl_HY+=srr89nz8#-8=PbJRJb(--2{~*?w@-JGVv_))fo7^sf*38Z$D` zwTZ#Ks__hY%X)*YA&vgtAFk#Uzooxz*wh-s&68s~A(&7BR@3W4+Gr+*>VEoZzbo(8 zP+fkwuppEw=K7?u(2comcIfxdQ_05nd8x)Mot_Op$pNxO+a5i5AajvgU^QpdDr6LJ zaQS!cGDGNn;0<}$4|Vw;8Z(yVLKdQ2NzrDO2frI+Afy>|hPHRpvqJLct*)pHEviqyDK<0+B{c=# zEuY2Ce){lR<$Iw=`GZ}47C>$DE<6pKjTem_X?)}Zl`{nXqbvmXB7*&TwjXyL{g862 zwN{<)MI&jCU9;XDZIrAy-m5D?U%Srk5&;LI882Dm3=gxD3l+G6VPQ|1{^h+-k-da& z#02`YS=es)_ZO<8QJ#}k(;o=D(|@8)02szCu*p%MsC@nh-*xDc{T1&&^+)X8oHW{@ zpDWMliMS_qwxo`h5I?)PeiP#X3x}&T0-~({nS$$`TsnO6JIPIiW08y2hc^IW?eogn zT?(7Qw9fEtwVo2=;C}1ZW+_b8Paf?vcj%Dd*()HH(CD+DbDF^ay5dO<{#~#molvLo zF&g;WwWUVx{D1GR=^|#RRzY=t9m8cU~d%`X2Ks^;m1@6rb_MG%^dnVRv@!3f1KjwFuYT#_rJxkVb zR#Bgh?5BIhj1a=R)}&Khpjp_k;?S?%>CAhUPK@AVe*^+i)6j6BGCMmvUTBH`I!0Zu zcojoSVbb!y5!zcko&I7oZwA@vsP%DwPbyv&7Ni-+w*+(Upl(86_|nrqf(4w# zVhSC0KMmM#`X;K4+xw!TZ*YP_`NY_Cv;u{yVUFm`qKWI&RiXjyr&x2U}Em;|fd;r{uBE}2+NR+>H^ia$S9 zc9uVzU|*-5&jSsOu8iOqebf6+`SJjoy4dklbJ&oGfCsR-f9|W~g zFh;1!L{xR^im;O=zl+PAuARkW%GLR2k4!9vL3xmvf_7YC(G=7JCXzc;Z|*utxFNf0 z0iD*PSQ?JYE>0)nT^E|0o0)C~_t@tYad5po8Z?3W*I9#B_8GQLoId%VX3WI}LJ@9WOYm&WTseI)cNyj*_ztSKj$G)~cs zFsy~~nU-%9(Ac(_JfQLb%%|KSDV(&j0DXo5Y+%Z%NniXIrHP8%EJ_;tkci3i88h`A zZ;or9DQ6JCO+}hP-LiinTg2ozyY{aEgf>T&?CB22G4^VmgA)f4ZD;le=ZjAE^eBIF z!D;c|81<9bM;+Aj6N{E(JU*pO>I^#XN5KhMGl!HSJ4`04O*&Bm3-+U)a|mym*i`%i z=xYFH8Ef;s+jfYuMYXqT`_+E=mzh~0Qu$}lrVFR1H9tPX3mRZ%fV2|o^Do(u1v1%+ znRYxh#&@#;Krc4KT%~}%+FTusKyC8j@W=yZ`)gi^p){be)oL;goG0L_b57b@SE!p4 zekUtg@rSr~L?Q|Nt}x%diSXCcj|;+j5K4E94vh4cwfq|&T^G;Ly>IVA&YR?tz-);B8FeN3u%86@~R&`?9m z%|q}H_#KM%SYjGbzkiMU_i^y$&HN5Hn0 zt4p3=L|WAs6hVQ*z8m&L0ie@4Q+Y!!=JwujT5dMZN`%YC5A|vO-w21m9|P;)Fe zwG{z{8OLpFuW4H*lExjJ_bHW>w~Avc>O*a_p!)^#KnU_>!|SWb3|`KH6u$$Xx_q+m zn;ys?M=>FxELmDqyRjcFu3lIk5_~f38hB=gs_^wnn|S3Y$l@pd3x61)?&6Z+4a|!n z#5h5uP3tO;$*)@csnOs3XftV_$|#uYp@ZCXSzRNnvT7^vfb|ZqcXQ7&eorNIhxft$ z_2@&Lo*YT&|DEcf?7{PQg;EcJc7b0`j8Qv~9lKiDeCaOU?!gDMHU!Za4QUaTr$MYI zqD=7KHRt4vv;xTvzK=k`2Y92tSVy`5m;=s)pOUw9Ia%lwB9v}=<0xxo8=&;q@ZwWEeM;B|D)j~>W9~RnXweO;^HyYC|dP3 zmp2Gh8_e7YSE;v=_`{~7BX#v%Zagk=R<7pb9mrkJ0gpe0If?AURtIe5hE7Lwa{O<7 z0S~MqHkl!->P3fB3|HPIO;qYzw4+0tp2W$@vb~?3nf`>CQuW>iCi{uQp3jacQb}y| z&#Xk+begwh_}AZU^?3Ov+@RpV1nK_>^KJ!mHTHU@fbeQ0K^o^*iXbaRvHd$#|-gY%ryg!45V~b||s-v@Bk=^K;R8tlahek*n`Azd@fI zfvttQq7APE|0yIO$2O(>9Wl`4@jd^af((lcd(o}z0x4J^w>p5qFdzDn5J*&aQe2!; zIT?p;Io69hfyVdL`O8)}UsP*yYd>B7b0B0Z{kaAc^?tq{ z&*$r09gRkNsj1Nl`(`Db0$d(Uqx*9h zNQcnmH+2S^L5#MX{N{Q5@1$SS_~-zp00>LofM?<<>yaF?LIsp?_i_v9a)vm$zP=$n z;=Y9*P*)dAJ~W}UO|wq7DM=~`e1s-S0w(w-EvEclo0PAaPB5W^^ZP`{N?O>1 zkrkD?6GE=k?!#SV?dobB#bqlEn~7yG@wC0<^v~WSDVzxhN15DLJf5||6V=~_fy`Pt z9^H)V?v{VwUZA4=EIJSSP3Kx}j%hE!Vsq7Cs^+TQg_mHV=cbNz1o z?b+Eut*{3IwOq;*w9{y|Z)7N^w~}ZRfKzm}%atb|mYKACC$I^?E_5clfh}V7%{}&z zZI6`FqG?H{LwgQ|3!_OWs}}alw;##TV)Jbm>((&Zl3WY%6_qex2R(rfQEYYO0H!~z zXit1^%LcVglgNu8r8t&A@?K0HP8r4NAa^UPCZ2z^M%RSNYCp)-JxTKUVYWv3T zZdOSDki#I_KjOwMkMY3Ps7t|R$vJ`DJ*3|JT(>q{HO$-dv1uQBy4&zhdx{{(5y$To z0MJ%36wdCTlzGkD#(}L@WrQpbdIL4ka=Gy-gXkKSry_UgUELbP`R|+DyB{y7f(0eE zuC$ivnuTKb!CWYIP8Bp%UHrfDj}BMhUqC-#Pu(Pt*-m*6(wnF!D>WUGEhjHc3jH`{ z9&cUIbxnMg)&4OAd|Fp5C+N229Z3h!(2ttptjQId*Afh5LPvMo0~hJsQEgOit^dk^H9Xhx;}0!NCU4NN zH6#xay?Yp$8U9elN4}i+%G2;qUz@0$o9yCNn({+gpcCf{|4(;Zzdy`aO5+suF9A1$ zdYrzW%N9E}d6y#lGG9#XsmegJ;6!#(o^6HwC{eE6b0x8{ikqX9TiDz8`|l=Y#Ys=m z8l__W-nbkrl?`Ycu~BYcFqj7}e%CSy}i9;R8@} zI83q>3^hX;Dx_X4rM~@E=0A|TrH%e>>_Cr^O{%1iG{5JF>$sa>)#HIfWvf!k_?)5LIO(OY09_4=OQC%5UtfmxBuv?K4(5J_B zYtyK7|F~+e%>0!4WP6)~-`?Azm7+fy$2-jDmizy*_dyx_VILqC-y({_;JY|v+CSR% zYUIK8(!;BHlndv^rYPI7@rMU2aibW&MAEJr!$Afz>4(GmbS3|1hvqORQIL(va45*F51R zy%T~a#bXWW4GW&ESRm%J+C3W0%g_~3yT83;EI(40bdxibVc^7C$c@X%aetBhf=H14 zMr5H?IRmp62{8#^`^A-&FwDG^P&=y!j*3mkh!-V7ST%Bq; zeZW({qJr9F`7wNNASlu8)QZboJvK2xeT_M0rD!9WvF4i~kE)~zvX;F`YY~LtsbsxDEE(X{l`ru5BxRmy%wHX2D4itm1Sbu-{NUs z6o`heAX2hAj_SfgK0^wLvPAChM*c6>7t8li($YBAQ8c~Tfl&Bmko#ewMM@YvZjkv6 zPc@<#Ei5dUE6h@&M}1mIRnOAWXhwXJ8EG`P)$cROOb)2E?@HUv!~mk8vRuYgr9Ch- z;7ML@(_h2cKk*&c5xV@wl)sxlPp6KBUGD|}nD-rfGmsUkLK!$kQ-eRPJRt(s9Ncp% z1e4qgjQN>*z{yw0YyLeW0B0C`7y@L>R-LPS*Kab)JX`6#N~W;RxJ&EKVz0x~+SIf( zUU@Uf-3X?1j_3Uj9Ucd9kp3gY#Hp=0p@{=6a0F&^SOf9eZepyU$<+IirIlZLk`suo z`#^=qcEz#9qjNP)#m+!=y9+n@{Ami^=FYN9f<4^4$+NOu(NnJdeK4>`9{3V%oi5HZ zl<0&sZQrZYlaDt?$PGLkTxDQxM_8_&Ax<<3{<5~U;4ZB~hO5IP>Af}A%w#YDB#>SE zeaOYXm(~5kZI@;A-pof}g`8*o=6FL7=wPr4}kl!n<<_e9RL9LbeCC7&WqdkF~MQ@f^n=zw&3!~ zbWT%mQ05OMk?LWB4QwExke!Q1gY(`6`TRZd+D)-nBTe4#hUU)}Iog7}o2MpCgxG}@ z;c-Fqre`r+%@gNsb9RUsP8t@**V;ddmta6yO}3fz))FSy*P4%YR$5M1!-2hN0Jf{# zi}R5GWE~}RyDfVgwFN;|9HtRgi9vdIEKvKCHoc5HXNJ16Y>mDnQj>hw6dT8a`ez?r z=2N`!WO(u4xtDwHA@_W#>?u{x^IN@JW_j2^;iCi7NfBMWBi2muK6a>d>)d>xj0|$2 z4M_|lo?)7p?W8O=ugI8q5~`SrhfFqfzU{1dc>Vxo>kV?GV>- zQb_SG>NY>YokwomChp(*-hMB7UeY^54($~-5;hY%IwkCeosU8ET%^#blrciA)FyDx zuxN3cWo=ptIVhy4f)+NI^ZJ=v^~DJZAeJ1+#~{U+8WoKd(9=j|A$gU1Zuo5@?XZ7= z_AbAV;-3woom*B67Ir#T4Ksbn7OR~}(F^-TI-TY+=GBUfJ5zB)H`<949A2Uh6XajC zwpRFb3)o*fcRv6^1Wn*s#=;+ z`$rh?BdPVeK|h13ZiH^5pUNAJ7ARboq`onF9L>M3MutDa`aPqTSk)qzv zjyVt*?2isBqUfR2;g$=e!jzQ6D<<)=@o|;p7J6IwYWu=GAapcEnXr4b-9S?N?gA;K zTpY(Cbv@lFp;&AtV~OsYGk;llENo|9u89PM&saPI=q4u~w#YBqVK{FM&IZN6O-24o ztRms3cZ}smy1tb6E6T%~vT$EfVUCbBpS%LsGy2w!WpqyEJ32ru;HB6&V~U|odUjxt zex~yFeoK0EAwDF4Nt*c@G-Myrkg-HZo%6a0VF999Uy~*t=a2f-wh0Qcxdw%e{A-;H z470JnTr~F0GU)Rp__NAgh&W&;E)#9w1Su1F7hHXpXej-;?so0PHh4mpA^@C;gs=-b z)Sl%#Z_8yfs>`ElUA`5{nWX$8(VvY#CU=}4ntsHEF5#@41q|~oF2~mhB=G!fBTb=K z?)W9<^Vye8WqwlyZS7M)*|sOoebMbu<8WVBF?yNjuWofL@`@KdMBE!tT(xAeJZ#sY^<8np6$a*gPREIHjKTqy46 zn>(CN@p?`&&QecQb<*mBP7mKIbZWlZw-Oog6n*>E^HvLf2^UvBsPfLYGO$V)geTO< z_#7J_BbzJgYQ>uhUEl*T!Oqq~fW!rmw1>(QwcVIAlg1-8x^4A2V$0Z>cL(rdS&zSQ zXhi^*)i7-T*x;t%lr>jaI;%0?7>+-$F(a@apuFmtyw#y}M z{KaXi<%!>|6O3aQ0{N?c1Z2vt3|D7Blh;nx^m+CS^>OtS;IDSwB4GHZ2sJG$ZcfVA zl(fZeelRk|&^{p+1}Q${eJ!C0HV!DK%G@*^;@rmK+^R5CNK0^XD-X$czcNNe|LNr~?Wn4L1 zmv~yfl%Sjv>{Wx_HZxmnE~?i4TsPqEtkd=J8QLv_Z&y^Q-2j;Twdw0<-ybg#?o`{0 zD$ES1oJxrE-g_ZO-JX1B@sw#JfKWGx3`W2fO<-J06+#*NYs~bq1z|zK)<-3%y=&Q;fpZ%UQ9$S7bafSavoU zN&rUROTU=<$34k4MP~NwT8Kuna@4xW@Eb1Y#rv{q(rqMELPCOxXW~|T!ez>y0MF!B zMck>o-xcKKNIG7DU2kCS=E>z=**DAu;VkRUxD8Jzb;)Rqve$~lPE>HKxKgnT8?My6 zJxceHl$>B+MA4PwBd5p5Ea~6L9W~F886M@#qr8hOmtrfxFVPSIC3F@~P$JvQI-pjp zIcv_z62>zqi0)16-J_+G!Xva;6O&@kOc@!O2*soh4dh$7MG9pBPwO`|#5AACgU^() zq#b&FekKJB{(Oc_r{{CZ?Xk9X*0}d6D}gf<>?MtLHNze6zC-Ya09xWx^jE0#?Il)9 zgXvyo0&|JF9iCZl+GjGu>X<2>36&=xpr40_Hm$}c-GNvsUk!IMj%_8Ru-wYo!N$gQ z>4DzZUPw3HL{LkI5}npSmov?i7{iAxw2)91rYq}_)lPNXw;|!R$=pfKu;)vK<~o(P zJzpyUv_2@6BJvQM>1~y_Oi{+tSK^r=2{GekaZ-WtXHSB>y*K4&i}9))sLim|kh6q~ zE$T*W-5)+AvH1D+H+9XW+Xd|_vn_|YI`8PURkTZj^>h=;n)|@-^8TM6O$?P>H0T5pMi)iSo$w9&#EGh>f}ZnL#$56}tQ9^PRj4+HW-MS*bhv z5niC5G$Z`)gOQh&58o-Qh&%yL)r?=ZA`veOW*}Ydhs%cEf^W}cktWY(S*y!CHZ$%N zwbyTkTfJ90xBRoV)itjT_H-eLLDQXEzjO+C9m;o$Kr>jlGLNE$-3)t(oo6B!97kg3 zH-k1d;H;y*h;kju!~quz+L0w#+)8R9eoBlF=q-ybfW+f6=fA)r$SV=+nA;&Y zP8B)kHlYh91)LFmD&;h?r)wo~A%HqM>*S?M{TiYbuT?tmb)|Y&E zA?ObmTc@%PUGat(ZFuha)Z%HylZSLhZUH*0afm+Hel>e=Yz6fu_AIemNRw{ z-N1a2W;2LO%2YW)WUpWfd*+kchJ=@h{Q8EYbgtv!Wgd;{4fUlci#x#3h^vXo=|hA@ zrWnG*-$!{C{Z$QJDJa2WLG8gxrioMAFZ7I-_F}8n$gHYCmNFN02q@t2g_{)D9qh!_ zIet>LyZW0olD{-Mc{%1T%;JP_Aok7ZIDz_*YcN0Wi*4cDV`a9y7`K#n;ykNP5gUuF zfGl?##Fyuf6cMc?M@~Qrr}&S+>}ej?Tu%Nm?^A+)WkMFd{UHycQL=JPrD|$+dMBXD zVt=vx9L~Rn1Z9c-mw(RKT}McWd=A$Rl9zAFVNA7r`8*7_wR_onjQnqT(57s?6; z8Ex$g%K9Nub!RX5RLja^{_`&e;6>R~Kh$m_;s;&{okZ=z|Gy)#37*W&PuU5e~G zf2-;XMx5v1Ii~i)vSx-x-8Y=uMc0?BO&fE z4G|^q<*}FC%=@PGtnd(5xU7({qdb2F;r%>UiYeGATiKXGMb8i5gcls>Mn!ee zI^%eJCO2bnzT<=LeOlpIadFnsCQAj5I*RD@2x92JKVa?o+ovSZJkh~_)}1{m(YS=p z?u6OAjpBr({@#|FfF~wQF(ir1n z{I(%$kgz+ySYU3JR5;};_v)q z{WAnUC(yOE!gF17J0V5mBh34Ccvk4~91CVz--|t@*A-*QJE*sqFNa4aQ-#A>;yOo1 zdtsw(>oHM6kOvzNS(X1y{hRcL=>4gwu@`=$#V>6KNP-RYo4=!wI#d$cF16j|+q!jE zdPbki->Pq#9y#1XCI4Lv8EpF)B-K>m_a3^Lk0x6B$i}`r#kO)38(-OW(M#il8heAV zoOkyeDyUX+gX(7W;kubHB`~tK870hF@y_d|l{kDnD@!VxmG<*Hw^8gZ9KKHjn}g0`dbiq|LvCMnu22 z7@RI+@Zb9H#py|{na|J5qqzRQrit0vl>9P;a_7dsbp|vXWvG0R|70v7$KfyJF(i=* zeFz$W;bM6CJod@q9S2eU|D~wTRJ^GFN)vMOeVnV=ka`6Q-@7^28=N~J_Ky3nYvRrB zH6|ha7L=1flxK6p$wMG2LSMKf+XF_j0k&^b+O+w6J;+b_`yeNv{B`1Gsu?Y~oM2UT zq1fsOP0|Io_7tL|`EIh=W4p*#?0myJTgIZ#yn6&+K4Y;z+_#@{#JO@0oz8ZV%;uH{ z0yI-Bc-U$&R(`e@D~W}9;2O0dv;ydnZBpJ^E8suxDP9h`dSM2ij+uD;o@2d(sO{xW z>N|C=XP#G-7LS(`_B?s4QUZI^^DTpTBsO?l&WI@_mA3uaMiqs%Q`T_{LHg{ExkX|A zbyIxKc}$uS8q!)l1FFaKdbwfjP2*yPf#6RaT9?lleB7gCBm1 zFKlig2aqgWsqL@+1j4ym(E8Q<#f6KD=J)E-KlLYc{ihbdby0z!I<)A$I+0WLR|%1w zM8|79nANQrP5Zkq3u>y*awRkq&MfYO32whk2E%p#I~ym@nF%94_+q6(^j@vX${D-!Im|UkWp?gm70b(SK+HSVpN@NA(w=6vH^Uip9>QamH7-kbV^d~y2o~8@AD#w zh0X-EGTtT)`999s7901JvU#tE^6aLag)56te(t~{DnxnPB0`1!=m3wZE{)qmZglCX zd`TmsbnC3d1VMLq&HMe+!7l`r?J41&t4~)~E%F5A`UoH0^8Er_7Xp#wb6FW|6EYNe0suL@-!v)OJVV*>x{I2)ildZ$ol3zgQRT zyXrP&$o+lRbu%9~A^($Ajgw+cin|!J2*D*}_gmU;KQrp0L0QI+ysfZx3e># z!0OJwGqXr;ueoz;Ni)>F&bfIPh3s&3!hN&F__KAotZx8ATj&SH^UCqM%yB#+w?qDG z3$jX8o`8vPvA5im-VmL@E?c>V zk$`+WeRk(e%nD+@V}$N%V6;yQ)3q~}k`u|e^te2Yw^a!9OJE^8L}LbxzPo0P8m+5K z(96JB-Ot02k-Ils@kE;2z2y?CmlyAW(gM<{dc`RA_iy;XKFt0JSUZWB#8NFl;caa? zi_x7)e*d;nCW5B7-cr%^;kl&bU)Y5NS4sipi^Bhv9>K*W{IfHT*3DQCqlG29nW>zp zceY+c)#x1otsjNYlU)&+_$C1_Z^%40l2Exu__)4Ak8P*?c1>=sYtvbZ>}cA8tu1m6 zrb7z;Wn@VVq}wdzeVDm1R&`hFI@I;>_<@($jw= zU=LwZ^f^nGI!iv0A{LdneYjrYf!+FtCb8C;jC^DmbA)3W=vW4WG((yC@Le*lelQw0 zZ!&K)_gvMsQob+Zld#Zx-~GZS5~ST4);$+kwI6$qJiS(3m zHkS&9I2^RbmFM6Zoh}vxd`Eb@?w9$)l%68Mnds;XntvZ>=El8$ z+Nle$=<%1D_?Ty278Py}bg=?H;-Ows>fI!CYfx5x3aLLZOVPYD$Pk1=k zzPGA#%ug)q#r?;TrF-tV>Wc33H@!}*4c#Yk38uY>C3o-F1nT!ht0Be7GkzAnd8(#) z@^Y`BQ1yZP5^XE7IFH5ckeDl@<148oW{cug0f=KW_?m@SMp7K2tEo2PEZ81TD*G0F z!pffZ|2{=}gHp-5;>tY^5!m(5m={ig{s-z;eJ^q@1^r#M{XoiizRkR}O)ro~n{pPT zsIXj?1^6|?Ov2-k*>~??F1fjAq4ULy(6X48o6BWo)(Sr%!4J9bV{&1&LcMjSsVGaq zPdZ&@pU`N{`+;W0jE!x94FTUr71Z8gtv3TF-Vp3Wi~YDaG;0aN;9 zXEDv`toYH-F`J6RV9oPX^H|Fs?h0z{*(+joL+WzeDQ)IS;c^O1jtx3&vY7qQ9d7s+ zl;V)hX1*B5y!^RidG;sUKeKX@TVM3Z8R59l7eLE`UHg?f9No7k;jF_tk?-z1f#Qa~yNLJlOTirfbBw*_Hl=Fw(D*4e$4ScKN&CHg)IVDHMbC|Za}TQ;e~1- zW%h+=W82Z!Nzp|_)Fy|+RN=}uma{6D&y_d7eT5~`Wl|8eVG9odRmq{}X2OafA2+d6 zl(B*E?k??qmUP5Vlj&Y4&O9j`cMx^?!|qsb$lyF<++(cn{OvW0(5Hoj1Cz0o{@W== zQ*vEEVYwYr-m){r?hPftY|-%lT25pUzWDgyJWv@=Seh%+u?U@zNQw%YO%$#&p}fw4t(^ZK3?BVdYj zCgpJbRG!u0UaW3@{@(UC7?np3<5<83{%~H$SbA(}mlD(+$9g-9g|vf9WAihIc-N#W z(yV$*NoXgt7|&qBjZs>tG6ca@CQR|Acrq>L-1XIZC>L8;4G8W6o zY(8c0+D>Xjl2t^@Tid&Mw8K7YB}m^Ycx}$zN%dnn`jB$F4r%pmrrTU z@!2&&Ww-mR9qhfcTNHa5Ub#58g00{Snxv+Vx-y(fk#wm(L=eKqA$su?z3mQyJ%v)3 zkZ?51c(cd)Vavn=E{3b4D~zm*Vf$mcx8xpKpT3e&hocXna3J1_pB zzdhSo!K`E(%&)6uo{y8-Yc!*SS0+<^?Uoz;EKbOKy2@HW-4H0rqv9jVahIxMt9yMv zQpyha)%JncZx5mUv?Tt#Ft$bUYo+EVz%q4}El`NMPSbT6<_WZf!4%AY@%qGUCz3<3 zDDzoI{(js7gG@3E@;M`VEzfznSFn+XLCUxK{7T}2wP;3Czwm+IjI0veZZh~YM4#F< z^{?ZP&cWMmK%#$ni?iYLiNbjT!dcq9`CetPHaO^Q)9iAR4Jr=OMSsy>iEzH7`ZO_c zV|7dYsu`ylZs+RqVu`lI~eOmyV+*Mfhs`;k8=GU*fH%|6xqYPYTKgQQ7%4s=y zUA2C!YAecCo2D{25LzkEo#HaG`_uS5+tta|{N9NDldt7vU~dVqey-*j{obpH@=oj!HiAc8n#gh)QHB^k(0-Ixb+-eEU{$ zF1$!kL;PLpD)&ZUqRWBpi#uv9q!uRgbl_a#?Sg-qVyJNEb;asV|QpY;O$Yz$>IjVXt?%BnR~R}m)3W3`*NgL!yJ-OiU7OoRB$ zj*N^a=L_L*xYYmjAS$-(vLslb;lS5ur5^HO+`pr}J$h{XMv&_XQgYU*u_@NCgkjX{ zG-Qn$Seyr~hAxN2aO=B2eN|M6ScS!Y6jt%>TCv?V zl*Y$)b#>8ACPa12^J1onWaP7?d9vF}&5(kmvMPAF7It2`I5XiGo4`EScSFU;9>DMH zb+qLuMsayI)@w07@w9CQbw#8{jOMcecOpA1`wH7P^*P-Titjx;>Wo-RvTmCFiTgM& zQ+)aXa;Y^Kb5o6Cb$G5X$wu(poM!`=03$kk9_biA=Y3mWL6&y%Bz@Uea?WagVH7_5tl z5rC|fv|XO~swE3*Vyex_{7vwdPv7NI%*My?`I_ z9UMvw3sBV!HDxmlHr`7FG7xI%d#hCzJm?cZEGYP#-2EI$X47F`!?pNX^8*c?k{DOX zCgC=3hXXJ)>!tw+l;w|(*fD_xAuvmH$r5ZR@1b2x4Ek#!;w>Mr0IgwTeHzSKfwScr z%?CvPmc%cu=2P8lX5z`D2J{1wQLYB1j-szKSnOmM=_uk_AoI?6JCeL&P(p}EPP$k; z?p(`X*dSnbk&*iRD#ZILP6fyLn`xmi_$Qy|6;$V`%YRTQsn90qPm;wmUUk390?mYE zKCcsJ0I6$_wH~$_!KX{zlTZNr^BRBfe(FApIs?9M#Zm(0dYglEp9jBe{I~s@_&Pey z9jay^0Dcp5DAq@vP~3kS$6=<#m+Wdj4wx`FjcINUuVfQqH&+O0TYC>$p z_%cz_v;O=_{ST`C!wc_IZ`Hx`f3NX4*Y_JZvHRL02ap|sCBEDebUts} zFrh52&MH&qvuM(z>X9#bz1Im-t7CbIEY<{)cs7yv@K*+rgF6|&ev?4lnbDN{if!bp zT`*VYeh;wM9=MbIm+Nl2gZ)Z@jc5?=E3fkrthIAt8wnUhonE{lNK@)kSntyB^}jMZ z@<`|rp@wb&g=aWuf4Vr<1m~zSMCpSWyncP2{Lj#50wdx`@UL?*o2Kh+Jj<|i zctqYr6hXe?gR7g~c#esI=K>jF(zUSG6eRh|l`G)Iot>7Inj#yd$TQef8xqT@^{`X4 z{sBygTqJ=cXH0#;Fo~p6-P*V1mI35E8aj#B|ALRV8&F1#D0J0v*!eW~DjQ(+su|n@ zklb3EZTH|A@fvcST$B~KSW9R6WA2%H3xS0_(-9CvKvmeyY-thQnj8rhq?TD2nsp7un6Bs5NyyxG<~;uNP^0pUA^9j^#-; zoTXS9GE8)qj7A4zJK?#-pb!BQJ1(ow^N+>GEKqNPn`g zm>k(ol_AKHZ^sEAiri6~0z##e6=sxZ(TsY!93koKMwO}@AVfMWZ64LMym3U^EN0@m zy60E}3_40twsc{G5jr4K_avE5y|=foaYX_Mb$dLil+JAq+Z<6dxpIZAG36r;69M=2 zQV{%)aR>ZMt6J3>cK&@cX=y=zy2B%KuP}k$$ zVoP2|8{9(2%6I2m%2&*cVz?`uPix#g>HUvyg)O|JrpQEX4 zI{z|Kk_9R00Cj|-{l*&sKMd}fTdFrpJ`fHORuXFN?~S{tDT54RyFkoxL1_w?Sibh{ z#Q?29O12G&fVRz!r|AuH_BL<+o^Aeept6vCm`|AA*D9@Z4pI%7IY`v6zqI^Mqgi{SB$r!Owp3z1!0L zVD+`k&E;;E?z@)*=pS(Se1U_heRD$5cH9#V5O34kV~AIk0#j(-O(xmZMRwL*^0?Om z$Rn!-W*41P)?ASz1uYU|&RkY*3KhvKYfW9bsc`#CU^X#1Z^}*VM+NjoqQX?m(qrrR zvGbY_y*aKsn0?m-*hoF$t3V(bw?`7s&LN?C< z5w%aEUW;wKYfn9+N4cN|QeWZ~sJ!%o6ch9|WyN4jHKLOZB;@F1`OjG1($>|SzoI5p zr$adRe(A4k%4&(WM{$6vfs!M7oKC2P+YeV-nsN(a(&D_fReALV;e?pXx=zOaUExs7 zB&=F#e1sYl2m)|rq=wqO@|4!(y_!?=eq~%_f$W@>h?c!NvL?0&gev!D8B9G+DiDPz zxLpBbnsK_)FsfL^MBw}2dy7_P_7v#1jpyh2vb`SoQ#r9({rr$ER_uJtrZf*^*6_t2 z?88^5h_D5}KRQpJvM^?OAi7j(L>ImlG<4M?;N(LFGC)yJUhSs9Ucz003XjsYcWX&z zfE+}zH1OZsUs;05{`YnMhA!(r1cQOM-2vB?o6iV6AhyRuMbQQSo$Iu%#5x(~sQ|CP zlstB*o1V#|UYueco^s0ZqTiq`9pZ^#pv9){(;+9yZ?fh>ouO zs=riLjxkgiOYoP?Qdv4!Pg*-ZE?VRUqY%Ky@SV*{-P1>jJ4kdmIn&hx-9SIv<-5|5< zQGTVuAg}0iV>e2!DR{Xdx{6qDR9S0s0yq!~@+S)ajOq%v=?T|`OjKgNR5@H`rvlZ&{U7IN=>+=&l`&N*mp_!J9h=x z?{0SWCJepaM&bM;TS<~WroDTH111B%H#Zr7(-eCsOPi!*z#E6qOTdO}XQY47W83@@ zC5)Dwk>=EjN2U}*+U1R=OabA9*n_WSvcW|!#fLjPHe+6H@9pUe(@BiD1nIFdTFZj$ zqqf6?{d|B8Oip_=8pZkYm3}2X2e_=rHM9oe{l%BaY<)|)eRgF&=6h~LWqw;Uf)j#a zJmxP@cjpBVW@Ea$yV@jp&rx`I(zp}xMl9D+`&^(^ZdDs|dZuNab=+)CGvhtSjRK0! zYo`PUsjD3w9b89u#~o-fDyvJ${*pYH6!a@6UD+~SQMR9YdcLCyt5UL|QkbA3TOv}y zJ6q_6kciB?*Tbqt@=bP^{3}$|@~f=m1Kz6663=~((d=*WDj!uOqF0SRTr5xZ8j8Xd zrPb4mq^sIjQcMX^(w0HzR4eb^gw~XXg~^*~azz8n5cKQ{*!7kJq5^2jZE{pZ#scPAeJNVo;#p_QHL_lo!R#YjloXSMy4CX{1p<2R zP8?#Ow9Xcr+xJN7wnxmxb^e81J7;G z%fi+I`D|Li-lic8q1|!GJH#|64VTLMo-YT=`P%-In)CxNf`bTHIQKvoB+et7>n{-Y zS;HGB==*B!;>aH(kBGl%-`GO=FNif-wn~Oop5`4Do|N@!PIpSPcM}1!kJHQ8WM^Rr zbYE}22ksu@ejId3RqKQF%%I)k6twEsC*W=8{??B}ZpN?k`$a&ncm~t@{-)ED>=HlM z6%^IGjdn4FxZO;NNr>-{9X=273(Dy@ikc|^Es|(?6r+}J+doBKUjczjR(#6}D@_6x z7=F?VHWpnC8hT^j-)r;oU7gNS(oYe()hfS2j(X#kKzrVi1< z5K`b;S;br9Y7!*+3F1E9i)>2lf*)--l( z**QIusJ-1*$Y3Sz(w3jwz0L})J;jL!Zw zN8Fvp{>|;C9|T1714zKUr4y+p9n&TfJkSjhzR+Y*;ZWqzuPZzDt2w|7+i0RGB>PUL zkUJklB`>`4WQww|8(%A5cF+2IrF>Q4tA_UZDZ<=WD}vtGd~7o+%=Q?gL>DpRH$fmF zblBTh4RpTH#Kd0A<9|jk_WoYtv66e?QlFEOb=?#w9r+HtQs_~qFK5O(j9t@adm)Y+ z;TH^i&2*npVS8--K2iV$W=q^_G=(Az?`7Y@Nd$>(#Mqh3dMd~z2J0fa9)IcdUPvxQ z-3ET&ai1KeX|ZBm;zlK;yMn4zXLsUgow2|2Z$bfo>3brpK|wMsX})<9Sl|A<0!w!+sJJYaQbB+EHC5TMyJJv! zc>uSWL02{y#v~L=H7PDG zVQ3r524TC>kLY`}Xd^uGU)T8_$`l6=UavHz*0g78@6za0_OvU989U0PI0gDRZFl=f zyQQrhmqFPsKxhDG7&df1SqmwNBg4}ce=u_l*Ai0cTS~(mL-xwbp6Bt@v5m#-=96sc z%#%^9KA29z;iUUMBBgZMQ~57>)E%}UedIyKO|Rm^-maWV!|%-u+*D)z2@|8FDcgqF zuHheBvcINiWr3ix1#s%&p*!UOWn>h(&>lQq2AS5YJy#RHA?qU}aycT_IR1Fa5?)nX zIXvlsN03_@=v7j)>n#h`>6hfMon9XJ@wIg7Lf0lUuAA!@l`iRXK$c0UAQ!6G0JMx9 z&tqG{1ASO%O#0f$0hQ{xp@%B2Dk=h~=|sqxu1S*TW1c0VqdGQ&Hf1PAV^b+vjRNqh z>}R!#v-f@U6-y1T=Z>oIIrkBY>h49LWMnoG8V^Ei>_>FWB*hRfq&x?8U8@QT`h*tY zkHnB|N^8JuqNMC^ovHIIqsC}hLA=PWOyw&+)<|Urd&o-o>-4C@Q`rEZ^s^d*6yB`Qt!c=Q!| zAECQ=1Eb=X@x~7>GqNsKlVN+7;4R3|yWxFKdP4)lkt3p&FP{I`6?00WRx(RjK>51l zC+v>V!QZE5%dw)Ub;`=sgx^;A`#MrTcbbX$PCHq}rb z!vF9gCVPa@Q;nVQD*%dM3hi3zHqbweD9=saaL;kQFR*1v!)YI37X2M7CUTinBG z-(s9KJPA=L&jGl~N-*H{zc0kJe2 zG`kcE`f+@iK)V7(_dsuPZmGpar(T4QqUZB~`IVVRvAFX6LTdc+kAW~3NX>GUo&^gX zve(5TWgy|BtCBVYw1@(i3#TRz-R@c3*u>Bwmog%7H$^XS$$4$hLi5VLO-q8yEzM-j zEcCoo7^3VsN{T~#s=G)7^-LvpEvvSsGF`Ae9epNyP2h$|GwCtk9;Y4ndg%jU)!T}l znKwYZdG;q|p8!^pW7mHg>XSAPoSa)jXZ`~X zJvZXKzXl1egvk*em*qb&Z~;G34eL|WAz-kP{fWW_5MvyPU5Gf{SWU}Z%9ap2IB6p5XWhTFwTO zLbil1l?4D^=W)Iu3`3X+2||6RTsgCPmR(x&`W9Xsv)?H{ZyFVR^}TWm)X`i;_bXLP z()DEG{)kQp=c_jc;CBadWtIv<)(;iooN7Df3nx6{Ik?9Q(qz5!OPkUroH5A`^*;;2 zvMmj{{{}w;2S*rJLUkga>&=&vbnV^%c(2R8k%** zxsPc}cYRtHu|XbH7^B>Br&cwk3`6E%|D)*41DX6EI6g-;$G0{lgqTqw%u=b5G?JrI zZX(wzGuOy1X_%V|o3kXj=FAwOkcpujQ|1V<5Yl39zvuTq|9Cdf=kq>ZZ$7lW3bJ7u z+(!B~W_KW-##0EPWoIjxFC~Yt#>SD2{#q2})AGI#YVmwZqXzj(z(vfWwmnY;!L{FrsL-qapBw&CB zU#Wd0@=eHAaLdvqP>XCnxFbpRU;T;4XCGR=?$>ex?xv5t2k5`<^n-t>9Jm9%j+3Ep zj%!@iBb-*0yYgISpja=qk!jbKm^wU%6gmj+SX_e?B-%8R++|}+i}>J)hN)ldT&K0I z17>711P)AS)hfYE5Ad}taaC7U5qhw%!8i1+YCFEXVNH;u2@pw?Jqr-!-A1YZ!CLa1 zpnd#YR`c52JXY=QMfTUMdj+(~l|cQ4ZBnCuUrNd)-rp?Ue!zEtA3P#D_s4-vn|y>G za)%q~rD>1e8m5V<2dKn#IT*iSog>a(NU7g$mXLiDa(hJT1yDq>)WyogmG-pPS6@DH zQ#k{{D}+GuLOI$_PWdU6m2l624h6$ShQIWo^N8WBOXBMC3Xtl0-@HR0%~cv^2BYO? zL}F`GtCMi7dbf?O9o0er-T5yDY__GT!wzPx6OH)g4X1DSrl(3l<0Ld?`>&mNZP69( zug}9cXQyMgTqYf9X%6)r9!z_P=|U4>S|nk~sE#yn`h^DEKnTd*ZDsqHg85KEz&y~& z8!%?U`w8gNX(w;L>6$vfEv*hmxLlb<_5x179VaYWJxb4Lc;1600sa#dK5u6>x=X!$ zIp3te)1?ve7l^5IHi7&Ji)s0vM#y3+jp6TM*O0BY^t#%o2^FE?hH^|93D$$Vzj`PQ z$tcZ6`0;8wi+-KeyJesC^_s8sPnXMLr%we)>hb*M0b&1s>cEo0+^@)wVfkJP-2hn?pu7tZGe^SEUsq+IWI4s$QNHm&G+u#k89%Ij|ili$6 zXn{-xwdQHnw%1{oOg(#G?c;g=F6J{+d4b-$tL2Awea<+T)V>)BS-f4(+o_lSdD|5O0=H zk#z*6Z=F?cI{-1E=@HTIjLkj>lNB$QX!f_uSB*VQ*SKQT1{0^wsV>tc^t;Av3d<^# z{sS>~P$P(utc=K1x8=A~15CSSA(vS<`RB!>BkY;_%Mso+Pb#%k{Ow8w^4Py$^Lzk+ zBx|@H|40Yy+C*9vw<1L3N7nOTSBxRUn`#<)-V2&X?r?WKBczVGRoJBRE8Tg*E+9ub z&*Tg;^aQkT2vTC$hrhA0TvAKa6*z2p^Ma6{v0?G>h4-UNYX7O#{?`rF6rVLb|w*H^l_Ah-4nJF4I@`gex0_lopxLDYulW%*CpAL9RG zk(>GMpc)kV!yrbVQCOb%0gP=c&7-Jrwq3xfLEWPthl5^^Z;pNVP)o7 zm-Wrx%L33_Zo4i=ZlNK5E~NXUqv44}5e*cC*7f!Wh!~JNR4GN~O$Cz$M5m7>olFF^ z4dD<<=!IaaQl8w{7@$>PWJ+evt$(~aw!0Q4cmBqMZ;oW~#cPC|ev+jwX>{;m$MSr8_L5EETW+RLP%YHn&_*mW29R`Zt)Z!`K z2j*-0g5&>XWjfJPmdzvQu!7LK^TnUMKyD#x%BnTj7n{!v5`R;G#wlaW+n$!`e@0v| zmU9907clJ#8vBA1b&W-;l7Rtot-IJdU8z8RRdFpCdRMUn7WTf6c~L0aOvp^Q^q=2L z=BUHyw$)i-OrnoRbK@fs+oI;Y;(w!ZXPj(OYZzHI8h`EZiM^&gKSb^fquz$xn46wb z(mNeOb7Z{HdL>rGlz=j`y2i(m@3@Y?CkkHWv+=BR=Rv&YGE!G+qsEi2p-6?rBqWn^%uaCM2A@|BG^L4Ufv zLJ5E~we#sc3Wn+rmgYPdOF=T60J)#K>m!>4c*k!_ERjmnm79@;;-KN@ zA2&M^heA(wHvH?J{$X_6Gs7nz%ljdsl{=BCW+W`Md{pq}vLp!hcYUuN%JYy#1)sfn z^BJ@8$L*w&aG#GZpe}y^3oq(-_ed%8;yEvi(*=h?FC;;#Fpv=9P2h(vPxj(>`&$3= z*+r?e$w@o=snv^pTN3}N$}zK+JOzIE*4-&J>}n2hewh?rvAt`EK054KL@2~VZ>==1 z?QLusA5wBrHd2Q`MEe6|V}n=b_&w!{YxX@^|_j z!8mYTDVK&4_xOH1($3>aGa)}bXu!ErEd6w}z~S)BDc>u@-)#j0KyohJu3{%1TDI6_ z165_PGmy4!mPt8blg z7Ss3Si{s5mQ5~6-x)jTQZGqWW(GkVW$t4>0`NIza7OV=nhX?gh7THiWH`}PVB9t<7&D}P0{?UIJi4yoy7gVk zXX;X^?36wu3E6(u^cYM$JB6sM{}Bm}v=z~}T-CgYggf{(mJJ*&%@mx@7JiPUFg%;B z1z$EfX*;D2M`DU-u!4t4?An8}?i%2JA|x#l-XX%YmbbskT>#K=OYY?K(l6>e?%3Y; z5XGl@ny*;K!!bsZoG|?hSM(b+>X?k-c9cVd{LBC`5%hz}z&{T4X0aY^OVnTb`;B@W za@RSgE=u085WGV9Xjz~?6(?9N4Vi6kwDbS4wOX5V@mM`*NUWtcwRK7Nv#G4X;B%~eiOy8J#nh4_iq;2> zVfILEAZ@9>PQ2%rrYwDReNT1@;30_GV!gtHLKgWTjQ@CzcV1$XCPwT^8&^Vm<3o-M zyevZh#1J(MolF+>wqtG{!s9@{L!+}yb;eSw{=qxWZWYta4aKfVLhM(ln3!un2eb_F zgEMQWlv-z7wvJ{9mRy9zg`-Dj*z9dqGh#k?U5&lDv9)O(1LS3{U1L&}WUM2GaTKji<~GqdBuX{B)=0hkubmJuM~_QlY7UK4bshWF2cLs9%Uxhl0pu{S z|5xf2f;vIDZUY9G+^m)*RzzQ|*i+SlqQ&4oyyjO49ZmG;NPm8~zoA_tdLu~IEad3R z`Q^#OPlEG~-Y)YX*{}?D=BOhA^JxH&5nB7sI5u_Scr_`yNnAsrg6Pg4iDL=9y3_70 zvVMxKtrQh~KdS4Gzt+iAUJ4?v=Zo7Qfu^=wMEVf@#qJaD2)5W_2DsrRD-v17vsN=^MFM} z2CYrq|?U4CS&w>#?gnv#v~DYrJ4@T|<8E2JsG$Ex=P z44HN>Z@vEIp>@uWLjV{G8G@n-iC^QN*#7vK@bd9#9XY;oWz+gU8iS@^>c*P$QnUVu z%t+*tHblNN`xDk^!y>LF86Re?RXw$|(P7_%t2JN4)P#5Hh(;hw`$j5G0fwhfhq_SG zo`d5+YK5o8V4sz_k&3TwDe&+F#Jt#&|pEf#1J@dPbHwWK%Db2zI7^A-i@0|8AS@J&9LxnlppqSrV|031mO`e zxugg?U^CV^=au$j3N*hEyg`6;Q_+SSTMl_WjubMb*irO|DyNRbM!|*x6$jU(nsu!o+J? z{P9srZWt|v`2l=`gdrY}1IJ?s9WT^Cr)>mHj_I4Si8g})E(e`&6*Z+tnGkezG^vP? zm4QPE5CA)H&g+Tims^c|Mw8i|aNZ8|15Bhr9*%q%Ob|<>+#Qo|e83m{`Qdn6Dps~# zT9WXxRPmRC2SH<+)_ag0#?pMZ+`l$5WrCCRvklB_s?{TeMZ8MzZFClTLD z7zBQ=348o-e*We0|LfhNn7emKn$8sb}C0sqawYB+~(a#?24LxQE zfZ`7EqhwQRXhP3vx!4v*e4;M3dWd(LI+^ec&8Qqfip)8%8sdCl)|)SxgkI< z!a|5BS48LdvL;DocU)K6kOKTrH z{PAOy?uq`BXd3;ygNlfapl2i;278aY1R}9Gn=Tk}5jc|FY3e^YS^hJOS9X5>zortI z$`LAV&d{`;mEB0QTXAJ|uA+!v+tXfg&Htn_kBbn1-@NE}a&Mo!RWHk`p_m=1`IU}qh zZuE~c*XS7x5sxmQ-Yw*NW*}EXl&iV#^63hJtc^;Ivtim9 z=zd|6Nd%-b&ApI3$+eIEomSc(1Hrh02yzAzQPv~BbLgB^Rr#2dSEiB}SLj>HBH6pe z!cHeDYds~UpYf}JBf$n7vPt_DT#kYc1VWaZ36c(oyqq#K8xh#=D;v&Yljfna<R3u2<0&q=#+w@Ixm^U< zTYgTDq8~5NE9kSo4W}%`unB7XhpZE8pC!!P9{c`O-jzO<@+z>PW5zqTu z&Sf~Yk`MZX*kEty^8FwQ<{=gI(7zqVWBX+{;ZPR?op!6jAEtk=A9SiR0Sy;X%Qkq! zAc9??rD$0vXGeIvQGGGi^3(T`QY%k)fhr6FBzIIXzPPHDCOV|D(RlBLVNu^v2e36i z=AgdPS^L>`Lp0g}udf7!;N78e`#L?umT5=eoJ$q1bPi)^_dIuf1o-~9E1LgkS=0(B zm102xG^cCyhW9sLfIh7#&)+dfQ}`hy@f2zfeXOTSefY`2V68F4T^~VBke?3-;Q{5~ zEu!01(a~gv$xc{rIP!KKqLw_fzqr4V&)deH5y)dvPx1=g(Z7?2&jUiH#Czsef71c_ zVkopm^vCXuLIv!rne%o1!^# z_1c-X_Pdr@rJJ*uN__Ajz^8FJFm8OvM$sP4J8ZRnWq;t+@LS5Vsn(Fl)S}%_^IKwu zN>MZ=QK(62dKE5|Yq4b66(wjRV) zN3EOXq040@teaWT=9iW(RXv+1T#_>`V2m@aTiC$L5}eBN7bhH2rb{_6@pe9Q3_-fRM+A`kA-WN8N7^7mbv<#L$pJqHN14AATD=LI`2F(E>aG zU>mxj?4QTKs4D2 zknZ_{S+0 zx1QlJn2KUlCl<_mt9x529!swSHsaRS4xkkL5}!-;4FZ!>fXU#_i+CIs5!9eb4pkzV z$(ui9px7>L;gc!&cT6fJe?mgh<@GBt#pTPBy+PC_Jg`9yGMIGdY0znYCMKf6`^qssD!AOI@@yo4O z-UWfNVZocabLatFlYFfuh;Y4-M*ph^cM%yD{_~IU8z-8fok_b-IBU?!l!EuwDcgEr z>wqmDR386l;tY!PgSpbr(HS6|MU6?d>wVaIWtHFyAh^Vn=8#@dyzgFZTrV%UDRcTB|X>mE}#cjTGu{2G1jgsc*BU{2oF}8tKn5oaWj8u{1d4u8gIZW zT8)Nb7q`QkCLMFilZK^>&t)~mZfCxHiXFAV z`Z!m_OAYmGY#o}aQ|Y=`@3S`Op?9O>);jis%E;jcPbPnE^thGPseHNLSqH6ni~Yqr za#AU$d6TQha#Q2K`1IB;Ue;U7nG#e2@xT?>>9tQxvX3ea#E-vKj;3D$x!-lnH7)&; z99!#v4G%mS^@j+?h$|smO{eC-*E{OY;_bl}v40cgif}6h(s>qYH$cvgSa%1I75x1@ z<=L&?Vm=8FZ)#OY;fOc9=3o>#F4EEuud=Inz)d@`l>NC*QodZvXm`LP(?(hw?bdQ{ zwYgq!koRS=h@as!@O9k3DRQqbJ~p>13QJrW5c+^-_Yu3(3$PPV(S&jYR@LHuruH!*M9tfD%S-LOi4b&Q z6$oUrGuJI#Or>=y@>S?>8T?_;l#p*nroNFI;6F>JF?59Hz)sfjYGcgM;WPvEkWPik zd*Zg1pUvP{F3BESk5eeBi;0!zwOBZ86e>E0u*RVB@|Roc%nYtq$>LzfHi$1S)kke> zYLsn8-`mN+UirwUN2S7E8&^<0EF}ZXx=rQE=j-CFZW`F~72Kd(cP#O4&9z>>e$p=B z7Fu~S7DQ}tG5Bexf`X99qOagbKp#|>JM~gOCOY4_Ai*0>3D#3RTGtqrDIRnNvrIt% zVzRIPgi~uhOU6>~QtC6Wp^F-bz%;A%zyjEc*eFsUyKI&$WT7uape4NksUiw{WT^lGRVKGy(GQF%a#>}8A- zCFU2HqN{+~GR{~ElWoR9%a*tBiL8i|Onw_#z{nR^_7c9pE}^(H*Z+jnY}=)&nNmu~ zx$G`NOMt#(kNu*o_*|qOA@x@1j$qHx;Hn^5qD|n2yBcG-gEz8H9HBJ~tf$Rfm~fo5 zSZrvOdu}Q5%I@z}o^uK}Kk)%JkIj}oq|h?q#Lj8mA3QOmt($*>@MmduZ8I!!h7&P~ z49~fFw4ei=k5A^D^1eEGj`z2HYirnJ*%Vi4s#^QOi}3?hpH+3|axfSQ(8^kRbg6G< zX4jQP)$W^FeZP$CQs*`)U(P!*n~=+n12>lxL;NP|C%7U?J3Ff!dP?i#{iX92J+dJA zubC&foQ>bjy$DY!=VcZyF-_9!5=2Q(FCr|==G*RGrxCu7*>nx`CFi*Tbyqz2RmsAK z)s3*dtxYUN0K}q>ueadBd*@|G8|u%|X{uwCfTQB)St0kmKxyEa9BNUhzoq!;@-ss9 zYQ!*UcI$Ti{w^>E?KPMd8QrNZNNK_~d+)|aoE{;(sn46jI3|Q*95JrK{I|Ti3`Rc+ z`}9{nEJz!gBcZeWuJUq&B6PQz3{wNq2Yq*J`hDNGG}|k$3_@&@Yd@6Xj?R+cpdS@wN2fyG?szt*kVW?P}vM|v$J4FIY*q66*XKecXFyF=&1!TwF-bO4(=)w9L4 zU`Zk6>;^d(>gw3^`^~nFD*)16n_ixp2EKMhyy)#AB!x!$Zq=}9hfTB%b8d;*a!HL^;f1*G9*d8%8`EW9^Nr+mWM{f|3_d$iil8r}sq^$&C*!1>kQ2kksH zZTLOvK4cUpP*Q06*%T%P^J$YW!awVYbx>4gl-28H4Sb9pr&4Aud z3drJ&(_p({H)7efv>#SNVOvrU4U}TC&r!-Ok`D8H=(QN~jMfHeARwteyYD~`rs|}X zGI*m%mq6megPxW_5}GCfC#EbDh>5%xM>SJ^Q!*9#D)~I(HGixv_kfRuJ?S>TTD1wQFxIjQQ`m9My7yezZ7-=K7e^ z&ZUGr35rvEzwj)TjLw-FrtU5H@*(vH>`o>Oxao0|X=(+(W z3Z?Q;r+17Iwek0$-p89gaFMtlZIBk1T#`6d?svQmM(!S#BC7?i@_aBD-V~mEDD~w# z_-}RZ(MMiDsp$B1`@=B(4oTafmaTx1uto{3TPDf;;&FdG%tzdya^vN<1GT0g!^%jo z$;BWE#F=b>q3ss$V{~f4Hl2V~c*oR4VveT&zGWx?iVRw2+dLv$LkizrOO-YA53Xy3q{K^ch|TQ#e;FRUine3dCv z(1nd8#2awBC6lM=$5Km$MPRt^AU*rJWp!>Q<3isIbyCoh&@qD|o7Wj^1n10Ay+eaw zZnk9VBR+<9a;FO?XK^0uf3$vcZ&BUw;7zxWrs7W++b!Gg@H%~~04_3srhE=#|@`6mFpH$oex~=l(IC!g2Cv~Elv=x`3fsIxo&Rvk@~_1hjZ)&v);L2 z6cu-@bJ`CCfs@a&m_4UR&it3X{0rKM6 z4!3ky>1C_?nJ{4t?Xm#qL&h+?U=7%0TMr*(&1^X6%2#v$6Jfj#XqGrl>8-px4d88=y1-f15qV`MU7a#_6**M(DMtzQE1)EJd%^i#dj zi5;pUzDxW9*X^^Z5UA)E@>(`a=#EpXCh+ z+jHv<&VfAV@l5hX^|A4jc6YWmHvYL2$v{*)@|F_8>GFi_EnBKt*`peqj%p_=FXcHg zOGJrwT$Hvg5nJWrcoL<$av_r3r2U%ERF$Z|o-PR23;q7+mEJXiMbk3x84E;tMmyG) z*J^e|!Q|N=)DdxasFgfaF5Xle(Yihl7bFy%N#pggtvWX^69+Um{sSw2I;8LL{E`Mz zU&NAI{QeYTHvL!}uepWi=^b9O8%R`6-2K#IscC?$9^~t(505Xn3|k^*Y66FE#TWo! z%cixC8>iL-!)3mDY9b4v!$NfOX;CB$DrkJ?^B$V_gMJpwMGqqu6pO4)jVV~_UX2~G ztkv5NJ|L;l!jwz3?I1EXU{^0rX@yie-I4jw{nvsvJDNiXb&qU+-g0<(O5AZ6Y)X_0 z8kTxhoN_?8Tpa`|7R8T;-*AEB4H~eWN2L*ZM-CS?1 zeQolhQ2v3v9>~L6-_AGgL{sLiA7ZLC948?gH4o{-GEaUc$lZmji(}mMc3d2cRz({g z81-P{I>=pfcGQqXbOVEt@#oWd)gx4mep>0-ImuGo;aYdhAdIIrZb$|s+9-&Kwk;~r zaNUvui(?29&UVh(Mtb0z++V7BvcJ${REqccdx}ei$GI{zMHP+OvzS*V4#+++SMpxlbL4p?0lIIZHb7Rk(pro^Q=R?zeXHol`ju8h zo8NZWV`A4TO7wMI@&~rG{@H#3A~mLea|;;g&~RPkw(f0R;$5=8-^M{7rZ4t8 z4=%joF0t%c)IVD6i~0Q=3E+pWq?R^%+k2a35q&O@Do;G#wY($1MNemrWH;TPUJVl& z;7&#WHuxVC`jn+C{26om|0h2IXz~nNIK9a1xkeko z4#VFeadg(Z&6olAlfCbqM5Ko2bHad52TD^9QWr-(C;)K28B-W?^-2>_zl{?_{MF(O z6C?twoiq0=f9Pf`Z7=Qt4E9`BMi&rXIcUTX$rFe~Z!dXy8NFxNs(Fku*E<#xHFutR zJky|in$>-FSabhuy1%}@6jEP=)Q?dx2KqCFLh;{!4d_Pf&bo&kx@k{ivbQV$t%bDe zNTKHxpY*BonjN$-2zzW2|1i0raEs*EG1o%fEu}-vFO9N?VC{k5%2Qk1nRaWaKIwE z(|DjFM^6@Ex(dz+Ak*IP;9;>(|0z)(R`MP#1mji#E%#L|Xn!r&rTA?t{;R@0-h%Jtr~|bSd3nd5(^1PH zL%U&e+y%|Cf@H_xr^E!D&8K!C-fdQa6_0Z|^-8}56EyFN-8=dT8MWYPAwUx3bM4V= zZ~uo4v)XifHGSDY2I_u)X*#hrz7UQ*RHI zmD5lQQdv{XZ8c*fPYk|7`#$;~)2p7cB~5(CEKTg7t~FX2#+;oW^`z8^>xm3d1JE_@ zsaN#+XOPlN2Z5KA^GC&%b#LmcsT?{^rO-b4<~arWY9KMrN+98zwRMbzkKgJZV<*BL^XNtL){M=}P^LBLZ9~#7<32RyyVAXAkHNZq4d;aTS zeSsg?!fR-EL4ioO=bx6|JBg#1VAYizf${7c(+MWqz4uDb7)`=M`K zk+mIXL7xiC55V~~1ROGil{#sdAb*_Wst=NT4+dTTlPLEpkpU9V!|TB`d3hfr zq9Bkbb5hOTZ?q2wx(TbRmL&SFS1bc?PqEqXR!SXAO<KpG9jFxe=-#)|oazeb2!!2m*}h%?oIEGwY(on7DUL7kPQx zo$_i!?ueOtw*ra>zUYfSkXp^FIFSn_WGy?-L*K zOVTy5plzl3vF>HB`b-uuh`d6d0ait3XhkK;N`0L79z--HP!DtjWHT2hna5f>NQC|% z(``EZLN^Tu0O-fAtep7Q`aabT(?T!DW=A*7R|b5F$wj=q_HP0?pny&%CoJC04u;>e zf}Ks_T*x}BgrdJ<05Tsr*HzB)kXG5BFLiG!>-x0%+4RuSknwuX2B7{P@8PUIaVFS! zz1Hq*St395s#HI3Uw2>JJ|Y=^CNwyA1X-5b_wvmY} zjkkI}C1?k?*rH<#qbzn~dFj>`1Y4%vzFrIy)Dlt>rfrC*?21>wpznn=BtVe6t_+Kt z8;lz3DQoCCo{!BTSpLj(Vs~q>uSRv0jyYShql`x;{Y77vn55X!!-~LTooNUWrhT4STwwlYjfZ@p{v}}c!o`*7YNU< zy%?osD0c{7kCbqX0`nSuox?y8otvARL?id_q1U=8=f9Rwh3oJeZ7? zbsuC{WT_+b)7urE`L!TK<=!#n@#K-pDyEL=fCZn&P1~N+L~y-c2LkgZyW95vhZO}L zDh2v^X7g)+%E0u6YvNnR0Tf$Hb9Y>GuRG}1lqK!zT<~G84HmfAh&%TDbKhgJys>?B z&iCx0@f#$ORq3~(1R6x4_L}jaov+)R#C`tX+m26vMtQ<`{>uE~{($6y~FV!F^IS#kx8 z7?jr^@+`~D&}vvOP^c&&&DQo9JZF&f?ezwB`EWUdv8?ENP2Tvt(kaV;26=c)imO}2 zgxCjxk~qVyeUf7^1%d)`ga$Q%4x%odIDukSyQa zyEyl;Tk;;hZemQm(9Zd_GWX~lzPHHCVon++1;u{f-MdYb8ZO(IT|TgGIgd9RK1Y`; zAS^bij%D;#Y&*P;5noP|Y$;z$=64nTdJr^n>yea&q@pwr<*)3m%2Tdx;<9)*%;Fhf zurfeL3JWp6qF+Lgc)~#m#-8sFZUx+p&COuequQeg?gTCuFYUg5`1sTum3Yf?PNMCn zSN8H?Cnqc(4F5O5VrFb|RxMyYDzfI0-yc$cKblNv;cjk_Tmd*M!_~rQq|g0 z*oeNTx9BLx^%3GxFJ+wc{X0p2g!gwgic@~<q>n;1+d6!-1CDObRy;ch}aW?Dw6G&6dtzWs=ZoY0cpzoJy&E3DiihN`7Y3;bcBNCZH>`$P8m18kyb?t<0ec0wGW}q}T%_B3xuNPITiP+bYg>cg&qj%(QCN zveEBwx!E}>9MlcaFEb!&1RTHOz7O`}hl-zpm2+$65Pf`Ooij*J5fUo`JpXody{Wc4cl1yE*9SrCNSaSx|>f zXPc9%oW6^mF zZu}(%#8WB=QdGMN!|--Q>J@LfWc=JN!&;B9^;CI0mEcH_M)Q8~0o^E9}?ONvb%(LpHMLlqPg^ z%t+&t8Gkp7cAMlykebLeLaYaevs4$5JZ@X%E1xlT>{ zwbnSWOHG}iLlw@xq4#0x^)_e3$k_Vfxafl?iv;YMs{hCQH6E&ak_y%B}p@~+orm)saM(CzGh7bSrP1ZCFCvMLlMyIdwB zRp4SJRsq+qb#~Qanku|N^qIz*^8X4|KLU~~*GBsF*>+FuKH@O6mE7{$89ICxS*Nb@ z6TF+Rgy=`!j~X%f5vDtCeSib5zTP8@#44k&cTC-Qfw`0Qtm?rbjGN+j5sURpK*1vr z*R1MR9&#WOf@)L#5O4D`9AwO$Q1O*myHZ;D;J;xB0Rb4^RTZr~lM-FmLe=$Q95c1i+*RXcL$=&2i*Ivi?CG_PB@ zU3#5*rIhl)wi~dUUW9 zw$8L1y>O%-j-Hyn?b?wZXbhS&Hm}{ZDDgs!82cyQA(6i$HJp=eMf55cMdgRip1KpQ zP%-ZfRc^?8d;7I&0yyCwCcQEJV@{d3J+Cy~wjOS&Sb$h)jUqP#A6vgr`9iIm+|&BH zu^KVGXGINVI?B90Jz?hONpt2LmthihtWX$ji~T!v0Ui|QcOk+Zp)m&eeT z!>ra{-e=k`j*dtHi#Y3l8z;F=moK9nn!G6LPAP15&+eOnCm=afBZtd;F3a`1C(cp? zp(oLIUDt6pkHD9k6Bg_;Nc!)YBI(^|RXo_H$J6$S*%nj%zvOopiE@JxEFiK`(=xW~ zn{%zONgMuqn^KxoYA4_5EBpa|1-?unZ#$S62x`@OT8(KU5W$n({uV=O#pHi|G1 z=yxCynp(JCHsXaQq!A$FLN83y6dOy<{m{~<1l<_lF!2h ztfh}lUB5O+RA7(~VS8#!(}5uQq*`CLFLObtTOQv$Uvm(8TRY+6qB`@&C4Yxf7tGprHdW;~PMruBdLsmw;p;<0|XokP?^(|ZMd~_XZd~ZcI&Ab~N9ERFw z0ac0Z^ZRqHp(tSUwRLiDV~GVM3c22sP8v_s)60TDAdQjIEOl5_e71u8Ty#RZpBhcH z<~ze9UQj`9Z)E?|V-IR!9rK0ldmHK0($dmls#1aWeRGi)EMM8>!(I490>Ic&1|k}E zcPs>fJ|(vN13$P6Eyg(@Q1CyB&cu=F|BvIFqlS)+h!QhbM6QUMv$?Ozk^4S!%o({! zlgTaD5K`n;j?Jwk6Jy`V#2iVilKU9u_sl@4 z>(z=|jo~M=>mZ=|j$(I8|LvgEXrAC)qdJI~YKhxf4f=CjAbVml`@6fvVL zOqLTaqo@A%{%SFp0CZcn@`RxP;8(S@6+)ymQh;d9YKEBgX8;E2!^8dMjl+NT2B+RD zYCmg}Qsua?6~<5P?&|EeT)rw#v_svXdD}0~^dq!wIW4~$Jbi+z3eaLft+D5h`_JN) zCJJa8aTNwqeMKmDll%o|S7!}3xzi0Q+GuPj-v4^oi%VTe*w;QvuailLiB6QQSm3mTerFlp7nAENByQe^a z|H-(GEp_1||=PkK?u`8zUQw-K6+lE z44|tltIhyTBd>qNv8)I6Fn{D-MxKVTzn2$xP+kixn$KO`LduubqcJF`)D)i_F&SIRRO_x6tH>Na_c{e2$V3Fj54_@jLCBd1rCOPt*8v@o?KtNdoV zWIHVDf!J2JZhR#`#9`_s(}|7ua7pVadd>%jQDPtbdydjriqB1X^4WrMBi-^-8rULjUh zv*|k)-*L~aaOYM_|5zJ$pIwKZKV-eg@IP^bY5a?0EE`gHyXT0_BL7jY`CQCAfh){9 zI&#S}vB$*q@mx2Sd*8Ck9SNzNhL~@DbREfr0vAFfq`@c8m||jqnT2+Xf8BMj`H5e9 zQEIKEt>C(y0Yzn3{*r~j@3-Jxl6zymbD^ZDD7BHTKR$rB7sUq!2c_=pMg3!(%Ji{* z!v$hW3FdUx>I>qo`*#}Q;o@OnhPpn`Qf;$mQ{%;vxDa;zCewxLPwq^14FL!vQr#Nf z2n7oN{^pvJ**bsEKf%6pnGh+hzB)SGW{5eztW zN@}}*V|&p!U2oh$9J*w{a05FJzhtk|pK$G5XQ1+4-UJUDDx?d5Sx%IBbTNg=7l!1M zHSgmJy({FvLQu`m0P0NBZ6EuWnu^J=N{mEEm1`W`QSHr5zUpwoE{#|SO8#1|!kOk$ z@k#VUdqj!|uv3Y9UJeH3-jZm->O-s~>{?ko9Vya!h(U-*ZSEDWoVi~vEq5gP^|~cr z;*Bt>WgZFl_HD~*WCH(usL$%3-~_1_eC&F|$!Fl)bGs(%s%yC?tVZ1aY|5clt7ZHZ_KNHbKJFt$#~nN?%9aD5x*nbI z!>E+}dY;{deNq4nEjfA#I-;VABpHG%jgA6GXj|V2b@aMcZ63{(;9>^eKbmX4Ps_I6 z`vvFo&&@3c<>s$11=q|C7A;uTu9DIn1p(VjTkl5iqZH4u-Q@f1BH3XfmkffJ7qx4y z!OL(kGYOUT8iR)(!OE}|DmtNXa$z6Eq~q~wC3NVKyJnf(Gg|~)L69aSRee^dVtCeE zdnBB4OhDWU#bHjR$jjqyk=|BogXmr(Omp1h~;s^ zV--|$y&iw2sFP=Z@)wI{GmFk3Ebqlsa07)nD;y+UGkp#*uB%$snUZx^(PHs0l$VMK)HID%XNPPaf|v1Sg?po$&hthCm|hpa zU|U2ScsF}lVr!Rm(7~X6D_~}%% z^le&GOYVbZX&~hRg~KV^3%sM1NG;G4Bi4tPX#2Y>(oCv$tv%`j=!xi_R4=~bV&WDG zBbTT&qUTs6Cmu29ef@6ir^B#V|Mn-kF8KNq!4hJ4CxCCdRq3xP0YKtyuQ{2U<+Pn* zdHBNJ!x=pXc^6@zco1l)HA=}08s^63@BBCmq_+!`6BK?F33FnsEx=bl%sI@){hlC_ zDN_XC?QyWb+#m=560d;aa>6K9=+rET1ryc!MIxD0B z8ob&?t&Nl?phwHjfG@Z}7sHWPW#E1E*@$f~N&wa-h7Gt9Fyl2fT9^FD#l)F&2H2`h z?NT?L+<>gCAnFE(%P0j~{#*mqIX#r>Zdnbrw7{}xY+0?6EOPo%DoL|PF&7a{JIDKm zacqAk7!50USgEH=e9&duSBp_74w5%@Zk3nc*Yv0;zrr$O>uD z!|J$ZN?2sHy({Lu}))Po!di_9V{}j4~EJ=@xBbTX7+-g;UF*_eFnDr z%$dH|wf@!F-{B$@^k!QI)sGWY6^M2gu?BiBoaiAjIeN6N3tX)qI4e}I%Y&3PPHm$N z-Q0dsGDV;nsOC&g`6j)Wq089VE;vVvzMVAn`M06ZjyfnQCNWr|?yFxvBFtpv@(6F= zy+vhZ4q#IF{#c$?ifiC-R&hp4*V^4$pm0wMxu!LFF=LtHo*P7^I+gmqxyZ)_@tUl@ zIJ5JMuJJ=k;GPy!tI7-6WZjmEkDnkPKPA`hX}4)xq}nLj3Hzc-W3%&p+A?wD_ax3d zc+%JV8tx+Xi|67Uh1V`cjysbszqucXHEqHjbSo?+%vkJ#EiB6%^w~Z*Mn5IVZecEb ztsPXn`{!;GMYI^VA4ZC0tQ{X79!9AZ6O2!up8Rl(W`te@yXeUa3ap10 z>1}TY1*ZoVNloJgV1Rw86lKr0V@s3M^;cyc3D<}(y^tNyfv%n*!GuO(m3Z#e8ZA=q zUx8Jh`lgD9_$B0)c`+$6UgVw<1 zDW#ve@F@ZxPO7g!5BHzD>sR*;MXHjeR~gJ<=|b=jM(?7B20NiqR3t7`6z+_7_o&G# zt*g>)lim3#gc8#jv)t)`)HFly*?)<*mK0;0?~`PbG=#URaC*H6NUogxnDZkFsRpoy zeqQ?9F)1%ITi#Zui0^JtlzU-8df=dV1n&3a=utIR53NkVeAYK%FMm_8P!|!Tx zZS^epj+W*!X0o@Ke8&R&&YxkkOzt@fkL|js)8>2I-(QA`FIjk4*U&iYrf)Q{O!cRQ zed|0`^$#OUysGglfh%d7+mVAsjqL2l5*8co{HjDfrUfly5-SQysxndr-6J@nrNJa$ zDn5EnKDemqz!2x{-sDJ*?vQpIs`W=x{b2i>yLbvPIf{aJ zEe-#OELlB6kw!!99VB>#mtuSf(Yila*;j*qm$#K+fI8ne`HIVC5vFxqZnBR!2 zVjwMv5bk>vo3#?bp6HWLLyk#t7^xHtaH=+KYD=-QvPHb4x#U7lmvy68GcZy{Q*kOu z`R2Cb=vo`NUhDT^Yx7k}NfC{gOTu^bsxkdiii<*8&Q8?nw1w1<6$$3eWgox;9QA8ITwoK%q zEX&qGQ{GeLt#!R|%fl~LLo;ut zP0AKc&L`i`&MP!){Wh93UY0-_P?^B5z1a*?o3YdTt9=bctxIep-#hEthh2Y^*KWY& zLAis^&C7X+LC=+t6Wgk)D>X76CL2`-q2Wop=yZ|9Y5xYj-m*AOwt-q>aKNMs54U!& zqV9@L!-I(y&eWiAln9#(0$h#PbM%0Gx8-0Nkcna1Y!t1oYfE$N_iny> zB+erwCIs%=Lc%oKqc)Nr;)L#^UY21X!hfBgxbhocg7XtI{{SU2SC-9l7WOYVb6<~m zx*wks-E*`mdcBV!a_prs(R z$Q1M)4~WVzSmqW#;0BJ!_Y`5X4nL22e!7J|JdJBe@emchRy0Uj!dJ)%agf_~qpvju z?Le&_UiU)ftrPG0R+od&VPM$O$`Ium_7} z7gbuAUiTP1uN19XLY}?N;#5)hv9?^rGjl6y`BIN z@$SgU(p~wD${KO32(L9k03qc_23QpY!o|!bAiiz$iGd#Lr-e0Q16B?)t(JJL=A1MD ze4L!&8(477w!4 zg6Ro!kwtWP7#s?eM@5%dV0(T)5C4=NAmZT5;Rl>gpGfGHJMW zd3O)M>?p#L`oRB9>1f@1Ydjw>WnWz-MoP`cKAg;Vwa_3C&Vhiv)1un$5uPCZ!Eiyf z{|3DxH3{~qVmMGoS;u9BMr%+kE)1$~8@ZDYm@C=EfwZfyn$X9ngh(6j7ZZ65BI7js zjDG_yFhx!65wE_!x52&aWw|uW zK6gFXMdxSx^S$xpVWY}lmtE%XB)?0t@Ttwe-qd=V3w%H9Tj65$Esbd88F0P`7xw)x zHtd5ivF=L`1tvyL2H&|O?y-UqTQ-kC+HP9yd0sbnE}fX|m`bjjJ(uo9)On;5KrgUz zV^Km-msF;CCB()Yh2S`Rp;zs$$vgkD}Z@)T4?w5FwzIHCPv8v2CWW*gmg?C>H zFR~|Otu+RGk0?Gb@kIRn{vfESe02IH=DAc4IyHZEe&Z0CzO}eg-}REl7Pv9A?E9dx zpZ$T|x_R=!Hj2f==v0J>7-b1aUPMD=@%(lxiGBU&s45%*xAqgKoM4eb{Pc?w}IbSqt{0b~4Wx!*&^FUrdgOMy|!%+KF;tfeLl`=)a9QPErMevy0ZTATTV=+m++Kko-H?sM~O zy64}eZ+5ptcL;G4#dD>(MXU3;s-Ej=v5`M%x7>B%elSi+(hgyEyWswZ$J*)%3iPWz z@28^c9`ShOQxo0tPCQBWant7yCwEWGFEkVhJN_`#>C>zHd2xJ;1rE=*YH3g*8Clgw z1eQUX?37SVtQyq1Hrp57q>(_l#+*_PehJavjdm>(6<_Nb4o75QGxT76e?Lv5z**MC z>Ewl|?yfFXH8m2DhKsp9;+po=rsj>)Q5BCyX-k?_)cY6#8iPjjpNnNKYtU-Lg`=+4 zyko&+m=yW|1(RGsspFC2&jQnu-(7NKbsT8ReP?NUYb=4f$vr;vwt%xOGJBxwOy9(v zTi_qdWMmcoi{6L6oos0C0rO+qz6|}C6&KINg4K+ncgJIBGqE#=2s|7 zfWmRe-F=C}a$MQoN}kEO7wA}TWv7LC>>Y)^=5X$VYy+;uD1PHdR3*~%E_zsT$v|Ue zMfvRwA(0PJz)Y^wJjLYYFL}6pbQ;bE59{n)RRR*~BeeNze)O4P=;Q=)WMg;tSv(LH zorsD2_~S}6S^K|v&d^z=)=vqF~o6+W>{HK|ikdzjRq-2@jS+-AzvB*or-u&}NLKt-TxCGpdr=3*GpI9(lo8Q^Ph zj0Nh1ICi>)HiCM(x^}5Cv5Z`lOoM;s4;~SOF?8}1$E}OSdf#m5Mk3~3Wpy4IxXEiA z?>aA?AbJrQ&H^}RKO%6sL)YzGk7ZDMlCkJDyXPKn7I`ue2VsZ4Q|y~M8lgxEGL6U( z>+>Qm1i#c>mi{EyOG;a)s*5k1G*2h05TU({<>iH_H=c7!7FS#`RGZa#qtQsS3OUP} zjG03l5v97eSGtJjg}~izNiQQ)aBa=27&i5J7&ebz13l>Bmlrl@Nn&ID6*o{DtN_AY zzNWw?7tT~u(BY142C-|v(*{OgxgnrIW?y-*1B0UoIk@dyh9X$fC>}ET$I?xL8sS@UJd z527#PWBgdgNEX%F0bp@JeV@1c*t;nomM?ia9-XRnRfR=bg=J?%OtLZ`mAUkuoa8FY zM8lylACxr7vk8YqF7Tm?nA#(aY>ui|FR~lxi83hRuFR_)h!SsWHpW#`GsR>D~`RbrADWHox z0vziF)?cMiofo{YxD0{M-#(sfo&1e$T(R|OT>4vg#C$zBAJgjJA(@!a1~Wtz`reJFRX)>qwM%FoKFjc2}5@Mw;>z~Z_VDjFd8o>mYFXq~vl3btJq&AjBQ zDW+8$ENkeq8F8|U6m)&rTTm{gcn!K>({B_%AR^G6TlEmb+Gk@4EPLRYDA13s{J8&Y zoBrLi&U>fL^O(tEpqdbyFzWNx9OG{ejAM^L16TF zKPfa84ckRqx@;PGjoOe&l$#=z^XyEn@k*!GiCY-bY(XN-HUGtb1s+2vZVixCa|>EH zw+#u_=FUKB*)Z5!J+)lMcN4D5ULU_w(0c`*(AwBD`o{8P)mj}L0!-g7{XXotT>PyO zoz_H-z`DaeMLYcX6L53M__Vkj0>PI}Wx( z1({Y>R@mp0Q;cNmD&pCOzoC?E7SPC+Q$=HS0ugwVKY>)EQ}6w!VXL4JHDYv8EP#Vh z@hw3ix|p02KmZn5T9;h6(KYM>AmC56un?}6Q_j|Ol~YJTj1-6zOn-DSi*fWhm%N@5 z79OqtK_o)081^(1_G3S6M8*0ym1^rj3QnQDAK6+7IXPar4KGL~5*PT%ms+j@_;g=s zgjZqxpV}2FVU4`7zd-Kk>7Je7UsIp8z*<)$6#LuO2xt6lPY%OUwlsRP#HalpYedB$ z8THlFK%@evewIdaotF=;W6qCo=G~dZ&b6B;or?Gslt)B&cb7brMIAWaZ-f_>xFXkN z7!!((8IK?!gW57;cx{Wp&B+J=a=0J0T_;G(v_|zn(QATh1jbSK+~@``nH0y8{;x~( zU}$KWMig%vX&VVNaAn!s-SxmN6wwKV_|PZ;S7W{3#y&+n#i)ql(|1R_^jg<$BAQ9z zq_Ug_Mb$EYV=DtCaxWn-uVO_mEl_|2^oR`k5$wB_^NZ#35~{hp;*UEm_@h)*~-oYfF zwy;O-Ib!te<(+Jso@|o^1=YE(ee@-h&2na5-Pm<4sy#T^Wy~EP+Mep^jyX9jDoH03 z7l5S{@PBInYg9*5{bd@S1jKgv%vpw6G(~)1pWcD zp?pRm-Wb-i&;ko&(nF}XMRk?})ka)bZDXVNm%M!1Cz%ZNL1DD z1hO9pFYtU-BgVxQ80QzB6$e>Zx`RZVS!4@?rBWnwM&+JWR^C7a64J?UJ9f~* zZfHAQISpCU3*b_>tO@~*GLKA9ZDX#2f<(vL8=_v|U!b^JbRNaZR*r~kd-K5KS$?&& z!ZZ@Rea*+=$G%?AU8RA2?h945ir}1I^PFDd&9lhbcmXNW;$&K*XsK+WZn(C>>(X0} zpAGx6nvWyf#zU^cMQ&cp_5G;TTYFvTbAJzLnJ?s#X&>Nr4%0j#rM~miO^CW0bmJR> zDbu_BuKjHC*T=Z0nyA5Xvnz8AK|}5Mn-D*T_x0@?mh?R9j8ak+#A?6NTUUcATTZp! zEXMj`b*UDLZ=ajPh~t4fmgIc_bioc<8pUc=qTOboSnlO=lZ(f>cq}s`f8r79!q*lG z>#^8#sRR>l0uN{5@bj(edWSK1b6V&d@Y~x&|02jb!PP zfe~R$SAD|036xLKQ(kyK85stke0lkBGYv}Fyr4vuC-?I=G&HR4hQ;t$cYE;JKT1=X z*x%ed#a5D@UQrN73tT0<#32Hww5I!2o-|gm>$iI4$@L@2vyA1Vo&3|epT`vBQHs|v znHNvFC2*C#bNdxog!(YWsgqB|V`q$fIIUpVW-JhEX}hXweH^ zVJ&UWpZihI+TI0d-%>R%FXL0Xs1*u3&p}W$Swae)?}ew_&>ff!ska#MRS2espVPR& z4b&?{rvO{!L?f|4NG7HGR)F8`#@5q(bojFEGJ{GW^c)?{iG83_AH#TQ&SNO*!clO& zCKi86tot9KIme3(yty`xiEBMHyu-;6>z(Yh5E?id8{$4B-QRyMI>O8yL)(9+f{)%>VkOhRTC1*uhEsP@2H16qB_dyIz6(Eym z@hoo68XBdHqY;BOV1)Ll75t5%3du3!qnl)J_KdXnT{~RaVPymOU~~9tmhARQ2li`c zV8B(Y1o}~mQZzah&L3QV)UCix@104FgH+0*g3reBFHA?6Wo{rdS^2a@kz)YS@;3!1 zAuL#uzSeUjN85W0TqVK<09Z@(xehd)oK7YaX@oT(<9~GC>0mEvj&N2UTIzp92H5w2 z)gW^DRqp44?%D8H<<=<;DD6!neW^qi+cRYWEv;N8kf|!ccAM3n~ z^ZA>A6>V#{#?E1{%Sax;@m-e^5KYaz^nT)+oG`4an5y_fytlxEGg#|_V`W;f2nvfg zYW-C_A<3EX*W29V8p3V}{K8tH3v^omcW*)IhApc%v}(scpsNp7^Ie~G@yhKoJX-=X zo>V}8aJ%gX@&wLgkpP;nHsc5`XOp~{!!~4@>k9^fNJTi|hGBiykZ%06_8w@!ZPEJ^ zwLQl7#2W%Mq~{!SvhjJotd`v{spqaX@-4{V#*C55LTEx8)}gk;3=25da5hDPaP;4$Akb3-(3(cSY_2 zh#?&_H`u~SV4ZGV}iw8Pa zC7?!y#*&aH_wv3-ei4drdjq@Ahb7uAz7*AZOlW|%*%Pd)HciGt)Ap6s92^~Il1!|B;)JYh?lAgPXiU$x-yLD~S>)d`=Xfb&KejoLRp)|6! zxcE$etwg=u4;GmATGpz}PEt*W)62tBLaHpgA|Y>M(e(@+swvhIkp2*%@zJQaRtgIb z`UpkkSnLJI_}SvZhO_g8(bQ9Y zMz^qou}u1Zl-%JcvDGL))hxs1+Elu+7~o&ctBl4(R^?su8AAD79|%YePFrl- zIVw5Xqn)hI9Tn2H+UOK>+4tzPLfE}9wS;vfk;Y(9hq~!kV8;i`{vI{Ur!D~UERct^ zq0-T|N@SYmfQWpeYMMBLZOm>VrXv+_BF?k49?(5}hWDv-0HcfI6;T+&ulbz2mf6e) z2WMb8?}9fE@an*t=O)NNBw2AO2x#}d0u(5PtHm-_Za0PL#Km^2Q>^IGWG{WFpRol7 zu+I&*QRE47Cg`(V;3(?BQ7C;Ib@jzJ6;rY5frlH+?EN9&#%3-~g@!h(urv!Z62Skt z1f$x_bw>;O97UXzcTMacFYa{HsYMFYL;#14t(1gK63}b6L8nl0!ao5|g!-KN(yd`T ztSbxG3jHONX+2zRI(lw*tta|slUMMtcAuYLv4^1r5ErEwR1ZFf{&%z(T14DH($NI3 z%+TVu+qpnL>(D;3pO>E3frSQZJ!1d}ec&AWAS?zMd!>XNyHD&n*b6(3ItlMso9l_r z>yLx?^~4-@_H^s~a*RDz?BCcu80~ntti{i_P~=*hf-2Rk{7U6N_CIE*CwAah91n+L z0Z|m$zi=FMvU^1DCai>}!!LZRn{xmPUl9r#D5xeQmrqifZN|P_8^G0h`*a2Li~396 zTnBnVVqBN7K5-7-D_cm(0$wN}9}GP#kc=L=$7Fxc`3;dKBLpVzoxpZvfTZhWvDgg< z4<$jLx~`)@6V!B}b+|(2Kv~=ghtOB>EqKnuxN2t3vo_m*%oG2{{r*o2UT|Stvr0<- z+FEL)X;JXudwK*(W!M+_@cU-zMWHK?+hiOgO`7Clde*jP(-Y~!9NxFLzo#Fifht-i z3UI+DT}sacwbw1L6lEsu1tLw}nzhQ&>_%N<&m8C64I1D0#=T^DIr&X?{^G;4Fiu+b z=FtXdYhEiUZZ<72z}(`&`R#kD!xE{jpBL~ulr8w}mU9F4fHFB$T63Prk#-KmUem5) zxwNhK8( zw7YXpGBA_6w_@%-vYI|nU@u`Qf%^&|?;^He6`oBD$hDW_b`Y$gz$ z_esB|({h*GS$`?qwWbu#hH8ZHd9p-&;ljzh`jKGupCFrH7nW^^3#W_OO-88BfU=%+6Gv?mB;|h9zuda^{TD{rC?Q?@DHk5m9&NrDz{h@pD@N z4V2fj0)^p>!-Gl9w-R~g_!YV8n&tMN*lwJnYOWW1;=*_EL#KZLmRi7F5{2x(HUAMs ze4n@+@Y-(9U)QJnLK(|d72}?xWA02!$gp+LeD>!1xwUlat%j`|wpUrcz2^M5e;Bm= zcj->WWhga9uzoB~FRPhOIk@5OB6kyFK1Pv5EZOx3dxF?))q^DU;y_sbRnj+e;}*p$ zkyo__&HP$YJw-tu&Hkm9XCH-yZO^8;7jSZbffK6wIrv}i@?UQq+-^K6XyYq;+XpTV zYVu>W|(q%@U!xRjd}|8%p#QZC%it z;b#h#jDuh#xHW$BU|cGmy0buJ|Lgfp;{BoDxT~ho3(GVUgD7;(%>j)3vyx2E$@ELt zLFXvS$P&qcqTj^k*DsrUF}&1UBY2g4$+0 zuWSyoZ>Rj1Y)CMtKte{5A&YEqD?Sux@Q^Saljlf=j=w(M9a4`z+N2Kfb#*$Z#UB3& zjI5P;Yg1T|=OsDzEpm$z8Rm?@F(E zCMzH4pSY7YkE!o!3n_yvPf^!9*QoYZ(B9Kt7rxGb*qH8g>^~*_Pki`5@ zgwD1dJG|gip1-5a+o4+xK*QA0`zEKl({VCm=7ZVyBc}cITY*8t$Xf|L+57WU7Uxaf zc|Wt6ecxk6m4C^d%`9{KJ3hOD5;?PsG?uao(gM#uUr4x_3SJ-cvDtyTIu(lqIurX>`jr4Et zngf5Dd7A}b)Yyj1gADq<9eh2;-pu%`Dx~!G2^*X?3S?(&Hn=5^X4HH5sm-=#x!Ida zh!2XociXS6eD|O8_baHtXwZf}&#V$|gNjDPwFLG)Yu?uUNht_*7ZE91`Z5h`6G-JM zoH`2BjdOR%8#`?EpPJTr)fz1_>z1%as>7hv36ro-qN0A;q zLD3qYQZo(C>|DG3)oJiFFVsWVzsr%b1zeTQb^|ni*PVC9Raak2)lqU8u^zWsJM-x` zO1Ey_?{C*l4>|N}n4d=qGAI5ilKHET0wJ1;;WC^`kCrUA47sVF^m{+%YrVJS63)uo z|4Aj!fRRKXC)_e51Py#`r^I=@s+TeiGVS=2fK$;;`&lfuFV`C|D)!LPHw*z5Lv zCh3TI+P&HM^34fo1${kBt*jgzYvCk4bxwttqD2){FPGoARD2E{cpwjsqlo2tB9f@;^}kR(kE`&b06bg zQ=c!8wxXV<>mywZ$_^I9SYe<~w^bl2rY}BRRcC>}U^L0gv5);A=R6}eNgPZVebrE6 z?OP8|mAK7D)k{}Mz4X53&+jWAo6aJC-kld_Vo{eh7G7~NDy#2}C;z>Y_-uc@Lp5h~ z;oj2flr+3Tq9Yu$@#6<8E`%=l;1WT^#Y1>lTW>G+kbEru*PvIc(tdm;N+k!0`}1%+ zjf-5@x9SLcNTpXOE^hxB6JJ>wT62U3*y-q=hJQ$Rb)>1h|JCa*SvQgW ztFfCuB=|9B=;H~s4^~g-w2VBy8^iuohDa)~nYfS6_79)RFymxeU#AeStH}kZcuq+?m zddXjYe5`B}Ieqh0c9!g0%`B}7*M>VqMTTGf&UKR%bRMa;3t&Fz#Xp<@Iv%Hde0s~r zi-+FWr$-BmomZ_3#x-_`Wr}bLjMrn?D(6Qv)70iSgitPlb2!el)=c*_2#IA`#=q3=f< z#hALAEVI{F*DW(GIU;(iJY5vepW{0GX6BI+T(HE` z!ugZROf5*5+me<#&kx+xy-%JIbei#^W~a1SV`p^eqJGN8K~(F<~zUA9dMli+ND$0j7^3 zX0D!|H^CvaLJj?w!%O50E3Wv!tD6y047~8rw+GFG1|=@K=xCg-(Y=#7d-VPBnW80} zwYUE%4Uv!rTHblvnJaM#pQa1u1PIxC%_L9^{|>Sw;c^SC zao^duUt6FqXr5nRf87!g8y>E&&-9+)=V!DXv|l=fkIw7s+Iix_0%5VVKuf>n1ceu* zSu(@lhCiyTUG9`3{ZUp*x1y-f_%&P5V~zONc#6RYo%ni3EpLA#j2C4RrBY&M;QS~$ z_OYyjx;;fsJjQQ74s{io8}7bDv7K=6DH)9F>fyR)pt6X>*COsOe{IIyap7}fLtwC8 z1<2Cz(5{(GqT2PP`%7MaNBK)Cd4CR$NBTtUe4fS)4e_11aict_qdWc4*;Bi-#*FTj zzOY*_A;4%@SI@0#b$zTwa_qJ{Im~lhqXV@DJ3%UBL(LESH=_3rlMHitKbHlwCH zl{O|Si~j`r*q~T14Oi5t#*%R60O%gPY1@z4u*}cBV8*wgVz${w;W5nRjo|mkI&m5| z9xEo*$s^iu_XiaIK)+?D5WBfw%$l3O=iLAGr5TUKyUBl;-R&{hGxIBEoDm zdEay4HcVb3wQ(6q!;kbltiskc5_u#Ms6NuEgGmzX9YrZ4Z{7si>7;>$Vt;^4$wtfL zGoxlHKbVW3svq>Z-cZ6{Mg4L%0IW~2{mJZZs|3pyiRIMn|p%8v9g_ZOxEvT z`8a%Ba2L_LQ6xA7c7FBBBGj}z3_}Gy=S5V9I6E)N<)mXbXcP5&ogTa}bp<3$-AVTX z2=>EUZ$Jucf|`;>WQiy=2^Npt<8*R6zs38L_34MA`z#o$6>57H7I=3;_kCDgQu`$* zxD&Dq=DIuJ8k|Os9XxZ+-p6ELU-|PBFGR4o2tw|GnWqlVb4c9#6M_gITjZK>@OXrK zu4Xjr8B{;ua#)Hz_4|HS4SsgWd?}o=DsXG>as|cKzl9m$s^+~?mo~I>BPV-uxEY6A zlP z(-I(w%+5kw8Bhrxi9IGdiVTlES|R~6HrG(eVk;(GMb3=#{c4w>dqJ}VNNQOGAuQwP zv?^A#UX|VGDa0KmBIzkaM~Sdw61Vf+&?HM%ltQ`xbD945@yX$sXGrX{KwZcBx3s^|DM|57$Roz# zzAy~+9cju;q(lhbW5DcGN*3UjhuExkQImH?>GtA+)-~RQ_9^1TMulv5P>v@5d&-G0> zI2&y>OHuNt|LbUF#Yo8aZWI^i55`Fk4_=-Ee(eB;T&xX&{Z7AaLq|Z%Ezvw>RKBw^ z`1f1K1<&x&fU!H@(PvT5)UHPZbTLn+_8Sq@XrYi=%WhwYhkQygy8G*$hJ$!iVOZ%q z_k@G~hAZtP*J>7-gJ=;J+*zTJvxKjZ>xcV4OC=ZEmqzDi>Y2G(-4j+~=v4?j61nAY z=97M6x@nlCfuud5!ZcCA7G7a{d3!&^t%2M8mO4Yg$H~2P?-gR#G?T)DcTk#=J6opL0dUZ$DS;-Ooa=X_bE;DE$3KpEA+bHGm1w0KPFwXfr6 z*gG|ne=fnM8m8+4f*EyxBUKsQDu0a#)M(cwN?6PE1hv#JeV~FgwzoLO**#ssGgkHv zrgJ$9!fIOd?$*B|r`q`aTEqb(*u6d55$(S|fb3|>@Irt$*sOJo@*7PneYjWJj8no? z!@ni%37cZL35f~(%S*@@W3r`C3&Q{c9FTy=F3?x97n1MevM-jn6ixC zbbklkGNRcg2SnzKaV`UP@faRV?lYgeuKfnD#m*VZ5-ThbXB z;Y0r$1@;bTW!$uPu&}~s`6+(gZw#ELR+F63k!CpOKw}%z(6OTWNX-&l_D&f)s@3a()-M_mS(+>NgHC*aX8k=y^21h$Cv*|Xa z`KDDwne>~@Zo;b3v)ddcOC>8h2cO?$-lVX&Na^p#TUnswYm;BYeK|gRk+*fl8Gn(FK@}FL zYI4m-GqL8`;HtOZsF%Q_v3Msd-l_>yFgLjiB)?2`3Z~Z; z8198`jmf3t3NXjXcO@F4s^@=4j9e3c4d{?dFD`0W%VqFrTzGYDX)6(Fu7|&Ps{aO&<+J)Xwyc)e^iqCQ zgNW6qm)(ci9M3{fF6+?w@tn8l&G(SgO z_V^WDw$@OpK7|9?px$WjOd_j5k_l!_`c z=9$-3g$Ip7$Zm7;S%wiYw~mEx$gW`(ghLaC^sYuUY}_XRdyKtbu4@5)A1x-Z5$Ay% zh72_?=fy_oNTNpJl?msIO_JJN)9)K@InT1*W@q`x+Zm$C2WOJ5WSzQwLf>Jw;oy%E zu6WwE6L%cV+q2RN?u;M7y$In=oA7V6qGX_f*8jP@gwu@#I`ZvIN{H77H3tIL)n3*3 z0`zubMTeUtm|rr;Fe_rq7p21cw{^k2cxLPaa$Uv@ar+4<=fXGaM5&6$ubMIPd>T)` zS~X2XL)^)uf_qH>gJo}T#>h8GkbQ%Q)bfRSjU}I$I87-O>cJzfGa~~tgba#TfY=$! z3RefbEm;4!fC%pbY~EQ2Eq47|liaDR+==-={%q>={A0L@11es=GSsnZfpE(jB|gE! z2~EbEm;;TGR`X-rPLvG0b>5_u+GJUqK%;=ToZBGlK-h?VONAdFxoKg|K^#??@_x<8 z)6oh~b!ai^D-DTj2R^$-LO_ukR=Z)2;iVKmU`YB1s*?E9(WbJZG^;~uYS8RE4iHle4Ds&&Qh9n{#JxMBa#gO*G=>YtZ>u25Xj$iJ*Ik~Mzy#V${+m%>fic0d!+a3* z6R|V$`1ADxCm}xQ8{wOYGtF=)`wOlVG+R&JQI5Ec)%{(bf$P5|mH|1*-Ln7a(SE?@4)vS2g{<0(YUVR|Cb#D?KDTtcoNg+|foK_*-Sr z)TzMpj%u>A(gWkiWZ=pxoJ{qRUXR?vJkr1L7#M=K<^m!T4o{+meM58luEqCeSXi*F zkMqKwe?l-{UXRK|7f%c*$eoUT79vm!|FLSohLl#l?3Iy_iJ7cpH4Riu$2_9=W@SoU?J3lLkluV92{mS!1_7n%ZVLFXQ{U{kxz4d~Z)Uf&x>= zxA(%@B$zv!i~Y@(k?jNq&UQu$>yn)_+m?{f)M^=hxVJ4gHN1S=x7B@}as5sCgUO%@ zm`Ku^ukZ9lgX6-zA1%U7{NqvWX`g>;U72WE@E&wTk_|>ic`! zguF#9lOae)zVmG(?X>G9IQ%h)bEV17!?;U}D<&?><@Kvo3d5(iGM3?jfK>w3w?ZC& z>qgpcWml^uqm>JguC<-_3T>o$nbe>VJz1}7oMR>*S?SXUI0^N!Y?!>PV3>c;?N)fd ziUT3(mp07v?h>BUQ(3ztL|FN(I41)u@qP~Igz(TwW4B(zyYCZ3`9(7GX`xC2jad6c zkY7X0NR1HJW4~NOGd)7(E_i=Y?GDINK_NeUjdUKuO6(v(Ne$)ZDVis)7`@8X-X?EH z*#4h3r?KV6s-J5BjpzXJ$n=^uRb;VPx}G1(6c0`L_);p~1B#EhhBNw>t&C=w~>TUpqkA;5EJo zbBqJq%XOrK(hE>#IM6-IASw~trl7hEn;5(dH$hjPQGO{KNIN0@Y%&{677I%C;{a8y zvIXxq7$TX2<9~b=o`-OiLJG8?6f9_AE)&NdAq37@b*8vzd8j~*TQC2H3j z{YIn7Hq(WbuQ+ptA-Y25b4xJR03EJKbja*hAgQO1VCT0RtMHVJbKEEtDzC4#z;Zew z;Q>SItrh;3ltrFWrY-N4U5!2RaJ8+7O#Llq$z9b5?%Jy~4<>j2Sq>@D%2Wh!49&oI%v`4V!eE0RqxY=u-@^Z`hYYnKJ&Bc9{>e?pv*=mZt zv{FsoOpbC%w>dDhWwr-)PC_SsU*pvsuy_^pnV=@Z$4$N@u;pNW_Q=a}Wgc(EDMf1N z5SBt5Esj--1wES#obP71lxmgV4#>@JVHQytevVX1{VZ7Ne6J>1c<*mxIa@VEq$m%O z8QxVFGLp6cmR3Mj;+a447X5hNHLxmkGovrs+7s%FGNNaNt`?U1O>Q@^)+rq7VQWT4 z)%tDFZ4CJ!U8SKYO;XARG?(G%OjgzxPiX+mtwLf zq1Pze$=h{AT?io<4aqNlgl238CKzijejZ{pEtDt-{Kf!eUVl+c!LZ`?#z~ng4pO>En*j@SQ{Y zq)K5kM{jcqz=X!sN2f8FAn?J{V(EfEs=r<0x@ek>x&x^dt5cJD` zh8-${UJwO^u1o4lA2+xw9xTCr>JM^BhxHQbfiER{UfQ0Ew4)=|Hh7@i@;;?ihCC(S z8yi&?>O4g1>R||ts6&nROpO@<1#^mpKNsH;j(lsIq?@RFjs*>3mNEZmE|x3!M&2E> zH6_~wO#272XkNK}G1q;131bK2TuS@|TJK0(+(C?HVf7fxtECVxVDpzAUqC$w)js!7E;IX;;O8XfN* z$({4$-LNg4WakyTl;Uc$d$t>`7*j1mA3hr6;DdFe*Q?U(3IH8b5?m4vUtG*`!g?w8 zYh%uV&V41sG-Kbks{YT4{xK5dZ4IzA{~+TB>a1g-pCU*@zj!U(RIw>Lw|GmTAvr5c2zAaZF&Bk|n9k0&Khe8U!KculaUrW*no=XR7T+?1EqcpogvbHN zBuY(QUjFFHpOmBg%5gjH1;%#CNjP=+4%#toK6|W*dN|IqFJ&K;jXqvVV5}bjIr*>7_yGxTx{Z`8~I&U&4m~_g5i0JVIXG zH~PZnl4R%kJfJW&MX!1BWB|}QOwmV09T4+Z7^WEAt*KCgG_ene)2bulz(az&MDDjQ-z{Ia>Z%{n9_xNjT1B~sSVa4is=?>MN52mM*qJ!uds zaB_>3W?X)mc^nz=RtPJZo z0Djo4d-8Q&lp^(qp58BYzwLX1 zF|J3>BmW~;el|FShtE7N;)|xTbd%FQ1}pMibVyv8*W8O3fN1MFoKgZu2}Y%(1Ru_Y zKOziIV~##uoo_q=+o}R1tj)V+&uy72Xe0WSh%P_E86UNkZ}NPxyKxFKvA4sDREKTZ zT;&CuJn0*E@`8D<(RXu1Uzg7v(G!l^wI;r!d=@Z`!DnSGC=n9m-3UW2{fX@gDp(wl zO}W3Du)E3OO3ihV^Ec;1Nz+xdnCovX2@m1^ofa#D%chQosiQ1a)G z6g9ZQ9y@CmpTAt*+Uhb>J=VX-EwIkM=HiQpi$|&8tirN763i{Ow?Cm+^Uk z{7)B~#W?mV?ct|i_UDjNvo`C%Sx4Sk(G%advS2{wc}i%NCGjt&aPa1ZWzO|9sA3xSj<_n6(ga1Ro8dCUfr+=A0|HThS!`x(F2Y5TmXDp^ z2m-EEVI+(p<0vE8x@_bLypMLF7*tKEHxxy2gdeUpNia zI}~!E`xU7tm{&$AP{UnbUGz<)1zV$4MCi~Jjom-E|5Nt2b;)CH8);;~<=>i!ubMfP zk=!9BXyekiV5e%2rP7_T{+5iR(f|2ATF#H5C;e-5$C9aa1lr2% zVZtH(h~2(-t#!TChz$ySV31=<<$RhvFRV+9V3o+DGXvx5&V*~KL!+{+~9 ztBrgi$LW|rhg}{SfOym>RmUO97n^vBI2CPyOlMBu%vgHlv7_Xe6N2F0;$n)-k(mzr@Hz9faG}UG@pP#ff^r@$L=0Z8K=yc z3yf{Hl*WjIc)whRg?=nDzS5I7W;#(&{K~6N@pk;=f3yQK)ljooW zQia*jP?LC^W&atmhDo}H{~dEmgwACwd0O`KNz0Ld8{2*6M%qqO;E}Ro9vL(2hMhkX7d=KC6!Xr34HR3Hu(dgbk%J~K*+WgZ;2~&d^CZ;*l^n-2a z!ZO2Aqtbsp&eiuSWuh)1=bUh`v-aCt;g7BYvd8{Qk$y*fCP-k;m(ANxw7kLD7O?0W z(niP+bKD%4^)g5Idib(@fc#zNf$JEH6^y4uf0WMl6O5=QYnP~%1`(kB*C8KTQUC(s zo?MSVMdxC~HMX5(T8nXEz7XuUHDaW|Th0s|J3@^)`;{ zy#w$w)RfaJBCn$F#X5$;d_f-xj#@s6jJkz^2Aahn%zCsG7bZVoPm8jD9=-0*F-VMT z$A#Y(%w9T{GVtH40p1p=gTkGxZ63OxD=g7~*j2AJfP5bl1V=9&UMMe^cMFG`gI`2gAFB zowi)eHyg_gllhR*4=@6!1sd3Q6J=nS+OZMBber@_R{HSmAv$1EcuS-Hk&%tjDeWB zqnK+Z5a(ca=zb+m2a^9gfgkc+hXLv+@TtewE+AB+FG`Kn0NjLYAHOLKC0Ek&+jm)MMoN!@SIJ< z5AhcUH_mW<`dU^d!jPs3ODrcg$voTE8EVv&qf35yN>Mdor#5u`@hsQ8tMpIUB?^GN zXvKX>y7{T|om9Ph55sv*E+tVS1w3J$HPss+RX$yj(`vTZ^A|j0uR4qlk{{gPmn1I| z0!~36*~?}o=^1?ZCvuwG9kvg=5{8sGUHtv6v18BRc8U!@8o0U>nk2EzX~@_NB_sj5 zYm`jnWpPHSicFBfiIZKFFeNt+HD1iy0)`cU)%}4A@r3Isy=%}hJq=JEg3guil=U7>Y%QG{NVH2oABmYDM%8!#M@IdO$^+Au-)>o zr8Sll@FLK&P2DmbHTZfEhg}Gsj>WJwQtj83)(jVTXm;Ra8ZwK~Uq7AXSx^emzu*I< zAAfYA2{3QNQW>6$e_(*L(kuaPji(Wq*Ro8o)RsZo-=#4YjTOwXtM1;@zxX<%d@4xY z6aI@j8Ivp3KvuCzRJwwv*Ki{01-{6(HuwIF*9&_5?>+2a;7bB#g+=tbqZN@E;V}Kg zKZJF1x>;toAVwEON`C(^fV8Ay(f@g!p5>n4o)xM6HsRnveJ-NO1=*2j;Un<`mLd^Z zc#iX<7F9jLKXfO8IMYe)2{eD_mFr7P(=!IIvB<7)4qW7nE=?>6`BULpkT}FY09-Z! zlU}q(`TV9Vx?r29v1|^-!ZE7%rb_c}jU+X0Qn)zE@%C}7H8MY}V0lF&Ew0+-el?TQ zkEcDnO)u1m{~O8I;8Wnh{w@TGC$)m_t`p2PF){1DMOa~EM&E*Q_BqA=*qFr(>LO0V z%-0*H?{V58AMo3TaPyvogX%-d_rJ=j5MIFT+D{=DqrR2Q!6U;yMTi7_qAGv^&GpVGHKY~~`ZCLY_ISEGH z1nBqqebgamep;9s1n33`V|`q!#x6mW?M!?=Z3Ga=ZYE`;2Msh%_Tq=F{~18-UWtAR=dvS2qKCik?c%MmHhI_TV-7#ffbjd1yZ+ zf71q=uOeNx&DkPJpnN*>YnvV?=gK}de)tF!))4I`(b}L0TZ4jeKL^243NJhwg+(Dh zD7>?y9^P_=ExDM)jnLuqux_OnGpjZ%xS!=u(%1oTLVO@XJwV(I`z%qeWbXj40~g5#9q7v z=PQkGkMA{sZ;Y?MCuOXRSFr=FG?%+6_H7AAmgq6oBrQ2BblGY6{3AqK+jN8N549_+ zs~)7ETKA6gs=*)DdQJ9>y}_*3|o8mCBM2`rtv(Onvh##yZ-Z`U*tQNzRAhF@;CWXldi;s!~NG4(HE3uKl=~c z^=jr(*WKIk8H!h`G4Fj#uV2RB%0q!+{Auv5gZO@&od|t;z}BQg?biN4C&r*>q=o2Z z70re5LQK6Mjn{FlkGoAQH)r0To+v!rA!l@d)ZV@vT45AOIzNQ&HupcM2?TYg;Qg*K z8)$5;L5kYZX4b6a9Gx`N5H-6u7Q8NBf;gP(nmweGBL`DYLTHOx)wEbQZs}LAG_3@9 zQcE{KO>@gAW1CcXmQ+q;>Z)_U1J;Z6dS%^s$W>7~4U5ec;Hsmhc@-}I>~Q!GXlDw^ zHVb3ST!)m{CISEr?6+K|L*)4L=$T6*Dl*w}b8f7?KaW!10XV536QrJf)ZfVy%IKOv zR{ThJ@1XQ`o;9|p>h(8VdPyFCd=xt8?)E0N#AIVl9|V%O$F*Z~vu;h+)6G*M;@p*mu~W~b6YtobTPkAtUN-Ud;QKrM z%&kwM69|&+nU+5m1}^sFGL*OLlS|}11_JeKMeBAjOzv#jqa>NC9ibR(4KTaU+m~3M z5%uw>Q61AU(?aF=lnyVW8*mp|(WAf}rM7c-`(wkzg|i$s(z9fxc{pla!4BTDS$Xj}3`F_vS)8Hwt0`7S6 zUq$zHPyR~IRo~P#Zh_Q1l;k_m^H&Ks(W3r_00CN^BCC0M`+Z=(@43;6M)f z+r4w=K)&ILQH-y_P@c-%_95mQ2XF5{z}m z;cRWM0)7O|&T?2Q^X~h#32u|$_n<=6S57r-eBw~L9@3BgagxEB-)$=4(NjT0Khwq7 z>#ARrDkEtoJTU(N4#L0y&aE%6$whwqZd5D?&v49+2_~ouZYc%3E%Eac$?oyHB>^q1 zE@ARn{rpo<79SU?|(AU_}y|CH&x38@UoT z{Wzu&5!)6#<{NmC(&`PWU_(Ic8d1;RVR5Z*?A_dTg2>ujS-T5j9IkREWfjW#Qf-G8 z=?DETVtNy_wJL_%<$iN8H0>lUtQip`?;{XQ6fUl0>WQFb5};>$Ai=NT1e(^BrUBAL zrG`~(;3c2!MA905(H{cHxE{5rKG8S7E@RQrpxbO7dCgd-7PNVSp;hKAS?h{elfWU(1x!k|S zk4cinprDSOZu7aXL0p}Gu4rV-zYz5M8ZY5<2jP5U^)T#k+{woT9b5ODhoLqWQ?{Ld z{*eK|lRDG$U|qw%PkuR8!U*@kcg?ih%PcWz%_|tJIY?XwdhPQPu9|dOC0&5=yr{dC zVxrOXq$2Iwc9hLai*J|;F387c8pYKJ}Qo?W-t4eUY`|LWa1zgpinv_`PYG=wT2F5e% zw4qqK$DrAeoE2QbMPksHevHJ)L~v^cKUYh6l58)>;}B|2*<4`~iF+DpxaXkq3H#%)2W&mCEYJNV z-H{fdqkkbTV1DJ}{q?s27knnt21okUj=g_MXX|xfLaq})eg&Cvvwa!B-;)i&0~&(? zrV`?;Nm;x(W;(_+`r`ivoPw#YB3Eb=F=vHa0&gh6Qp|lg82f}H;RGy5F3VO$Ao+%z zj(@nijJ)RjA8pvo+U>D30#^MhXEI^EUEuG)>~Z?;oaiQ?u6Cj3MF0`@ zTV)s!LYfZQxM{r*!E5yk>!t65ymnPLRdUvZ$2@x}$()hJ+BQ zLY?^O*r=L1mUW)(%JdqxC0QjTII#G7%x$Csjur|4Kp5HTMu3`TAgtz&IE4Fi^G#`R zs;X-3w_oD`o_Bg7B9`)RxVmxSra73TudqHf865deevdnMB{J3gNz;v?utaYv*&GZQ zX%2bZK_Ab)U$E?v>7X9&wUC&oi@hg>1EpJA44&kU2^jbrq**c-go9gsWsVP4@N<_M*&KSe;@bV2+pX3wQKwXe%f2_ply8UHN4c5s4u=mlhK~!}>f>VooH^aQ5-2=s39# z2O6*s3;GvO(=ASx@{1hLTtjZLS}mMwj?gg4zAUFkDLmR>*qe?$ACR-E2$Uub0!^~{ zr|~|X>5~W7CJcyIJFMv`97|q^>Urr$mBeUqE`|OH=d)+aHl#h_YeeEQ{jx?$dcL00 z=(WXi=zmJaN};*SoAIPbO8?^LkN9S~$(_*=RA2c*tM(d23$oGtF?fvi9sA|`8Q_LB zC;tU>5|=^eAfS_K7I@0a!Qm1R5w}+!FxfD#;V-XxE{OQJDCe!!`Kv5z_(zm1oYH1q zV*V83jU&%1=PGuJfSC7@;|_SO+E>Lc&XV^pieqBn#@fe-)(^4ozu zORO(cq`4dy$;jWHPxm=IJTNmUAEU+$$_-LFkxk~f^WLE+nJ(1+y-j5+M%@t3Ht~OV zqhWAU2z2hTAZXei#W5;BJ5053we|EZy>|mD-Rz&8mWynx3~1y>pia*7fn;LFK<9NI zi(hJUs&pJ4P$(v`X23P9a~GESa8oD2BlOrd!EfUH&f$-&nFVhBu|i)Vhka+}mR zUcMc2SB5L*!`kZylTW~=@+`UxT=tp2KL#*BqV})Yn(jV2VkU6R>`ZLJejbf^ZLMM8 zbHQ+z&2OdgK63>*tJ${rgN=V(b&6QB&Z6J9w@-UJA*Z>UiRIqvGy;SRSr#e0{6$_? zg#vsk?)o8VGaVk}7J%J|c9rJ6^k4I7ndimx`^epb3%%(H9Y?alQ`pm z=C5HXrsNcy6cQF4^7V&m%B8D$$0}?tz=X-gAW`A!xu}@5yz!{ihk*tMzG8_o3fxmE z7_S5R^$v!HuL;_l#3_lm@$|T1K+AS=eGf9_bmwZD>c6&iu$N>og_75D^Mb6*yJON% z^7zAX94|HfU!#|9Uuqg{S1`4LyCZ+K#@WvI0d_uClk>xh%^jocFCT44ZBHI_E&iyi z!gD}uafhl+UD4WScR1VaOiK9znlpR_1P+?#{8?l^mXW zh|jeDfXW~0bK#<1!O1NRMML4c`*U6Kf5pUt^1E!R`!PbJ5%kF$K-SaD}p8k15dS~V8CjWl3Z9XKhJp0WAS@@TDncX2&ocaO#* z-nNMa0A8t{Wg{9JpxH`geml(FC^7x-X37JJMr2Ki+U}8$K)_=b<3hs9< zZ$w#RmB4quZAtQk%d>AmQl8cYL>d3KH6;+B>{fHAHEV(>eBA?SP7zGxWbc2_l3$<_ zxs}VDg86`32OWU*x5uU&z<}SOoYD2MbEfwUhpPipdxpv}z4$+c)$hQ$MW%yo7}n`( zRUEq>H-@7$z#_KN%ql_vGSx#4(45?i&riBw;b^NIj{Qo>Fn#z) z#IW@H$eH!W$_<$r?6PeY(Oyub{=3|lZ~9g15V_!_r5=5QOBuMR#yk-nS!J(#%1Rl4 zZw~WhyOZRNV=~LBfG3?>19gpv6W$5Oo*81`;b#$g6O*EQPsE``s@I-}LpE+N%}`da z&KQ|f)MPww%lbwrgVVDA=*Ff^%vgu?Tv$8tM%+%?$xcLM$wuT=#z@2n z1uR`60hax8^-vJ-Ye`;Nggt&$Iycv-IHTvV)%oaBZKydvu;bt~TLuX`ZLz4_e1@JO z;}6j7xVv=kQ_h7cUWG~foUemgTFYuKUk%KjibFWo4X8{f(%T=R{H+%`61ejMgIWAH zF82a!t{+@6K_41`;fh_V)sd$1sBW)We#}#R`2vn}rFb|ws3$7Dd8Peeu?sH%xlCJO zE-rXXUpfsb4dg(|*!_!PEi;<`nq5W)yTH5S1X{Yv6InBT6_Na9yUOH{ph*%d(-tbz z6T-*y=ymt(Y!zTgQ_3fysbs%uAqUOO(2y5(%~&_F=qcUbK9F@|Yh2 zdS&@GY}_El+qLwaCzKTF3w%Kp2ArK)fiR9gvNJQ;%t+?0VrGWxz5hJW$qx?C9sSLo zl;pSU*IeG@T*avU&yyHxh#;)n4Usa3bS|iF50Y#nCnr3c7KjjnMK!sAm8VjyEg;Gl zGz3(OWV8lX4UGv6Two#6rSQ&fBkjy+$ys7j&jb2h-@xZQzjPK?%JMd%bXm~_n`*(6 zHEjb4*N^fk`-cbYwm@c}m3$;^?$cOlMViJNTWEwspgpT8x=2UrA19KW!uW+58pW_fQYN1Xcu?T@hy;Lb01s~lNYzNukHjk>wK zo=jBdCqPQ`#S^WH=haUz0IA~rhApv!jr~@R9$BfO$nG7B4kkLnzm2PA$K-pfZG~Dybd%ZYu znAZKU?sguxzUO zxe&T|Hgd!>l{nL$@D?BIxD?Yp|8Kk@&?!^JB4KBX!5I+o&5}|>SG;HTY7jyCE4qLW zes49;KZifFPs6$je#dBD5%h*D=Dcz@9@(8#*>m!!Q?QfrL0ptvrys413G)1)|v^cknVIvQF;ba7PjLpZt~uD zN9(l4H_il?((%KEqVBZ##%TIWuB>R{w}YBxoK~w8aY5glS>`p5u##K;4U}~Yo2{ph zQz^T3E(3qJqqeB)4FQ1ogH5)Yh1@XWXbYd%I3qR4VRVelclgD(w{dnci;GV&Gu6Qi ztzpV4;icufYjmouiMVwDi5BQ6ws!fRJmazNauz4=^9%mBlpU|NmIv~?hWu(La*q3t zt0Nj6+;9W9obN;n&zT+_Db2St1zV#K^>NzO`EK?t4~I>R!`4smZ`#8E1MZ4f#>gj< zHuLj{F%oltuSS$0KK$Z5=Y8$=(>2;eRJoW2&h*k}Xq<-9tA85xp`pi-NB_LjHdHKp zM`GT5iRKxIwxx8J2dJv6b?d@c2{JY1r&UGXaf>cS+cK&S;@eM{=p(@)runvKP~5__ zivCvcX1oL^Zije#%lBGWk}PG0q2;_@G|oMzhy>v&j?QhavoZIFe6(!>>0}E8m#7^4 zX5x6jl`jgNDM@fV?w$%OG(r|xYt5QDE3nzRq}8-=%NZtedo&Gdn<@=18Z)r?;_={- z0IZOo7Y+GhZ?yblaKJD;;$1^d3fFg8E&V}s_W+=1EHvEL=ys&17=+h59Wd4Kq%6zN z)TCn&WfW6@9Z^^m{59tb_FZ#=l(2qfShrRJ?+%H|a$-|o;k{{PrI1ANc~R-knM=sx zj91Ti)JL>jxW6^)$Z7*M?@4gKnQN80;wnuJ-Qw}6u|Csv2Ax#D6(V-pLeulh`@S=K zPsJ0|&WQ-fnsCpX2+`C8yo8WR7RbKRKN&^g*#*={tm zMB~hO4UscaZ^RRhHW%wSMNs$I{rv)djbFSHKGNiCz`s=l1VTNJR}g?Z@%8|W<^6@U zuf2m(dzg(jCcRC@P+nCxL&YFK?p|UUG4~iwD~-A>wuFx))K@a2U7ZO;s$ap_rb8)l63n;-1*C$5f_f|x8U1r;T=2+{qV+@$QuwJj9rqpD;6eXdn7UmDpSY}dogAecu4U_P=65vOcaudNU+2gn2A-+1fPs6o^kdFcHajKO}uSvOoYNv{O&4b61n=doe)~Kt25pj3K z`ITOQF(xhgy1mi+k%pHbfI8N{sTJZAqu!E?A9-AzHFV8;j}Y6JrQLK~4BylHgWa8~ zIrg7}Y9oEwH(H9?u;M-BjmqtOl}4HI&jmzvL*EX_)4D4E=UxIJ0E9H=Q8mqq<7wpk z@5|**S2$460|2%i3!;xpp-g+Vvul%c42PZ~|Nr{VNz*}+{H`?B%}{E~S2Rng>^1~k zZqB<%Lr78ny{C4Xi%QL(I|53%7k_-cvI+*Ypt@^)UxG|Tz~vPSNUsicCnV6zhc@{vYmMvyXMzW$p{&vvOc*`NknX+&P?0Uq43cmqV*%5 zvAcD6(13{D`xJcpA73cNym|7reb5P0x#Y9v=lkrAWW?jO+KTu0rG z%j?TH4@{enmwpdIaRLFvQF7a0KN17>c%Rj87DzsLh`3CPpS=4_-iOz|w)>cpQ{`Id zb$9dsEL;oD<2Q`uZ+6$*wi8B2YNvdS(ZtfXx;V3_J2m``zwcK;Ac;D|(V=GTOqTom zaaWKkZwBP)*b`t5BvgyQ0V1*w%Zz_Pd@joUSCn-89~@>b_r}Tj6>?18B9SO*VH#va zDV0cavE3am`S}^Fn}djs%FTBr98}rUOnq zFH#nj6WjvarVY3_3*&t>k2o!j^oRJ?MP?Vd@Nk2EYcS&C&w(jUD;1kw|FJ**0WJja zc|ef-r0S6_{Nez-vr9;A&}z{$s!UyV9_f2VtSJNZ1Y`d^PZf6W;Z<&f()`UTe_s+S z1I^#E?V~wL6)7QpB#u(|>_3BMXM#yAz#mdp^u;C&{Puqk*dKahTO4@zoPNfUNBWHU$JV^>V?_PZEyeT4UBMnj2kHB<`09J(tv|c2SQkBvD(UxiApzqGy zbZRaq;efKk8f&J18uj*>&R?rmCd-MMd#N2kc>DeL8 zs~?^A1uXf^UQ+j!oE!+hd=UzeS=JK4>-@d_#1A`1sNw8qa_00D%zK?BbIinsVCeg# zCJyzbxOrXkJU7@wO!^#4C{Z8zRcPM!1V-uOu-C$<(>*vVBpi@-zDmZ$VEWblZ>58V z0~s=0k2?b&yy}nv@E^05g%$9tc-&o|7zAUKZgkpx6hJFkOaJ<#E1|oV_D_N+oavPs ze^plcS(7mgE>+LxyY3$(uM8p^-l%n|TQBUC=xpQY!h+i(8y@|wv zIsg(-ZTRW(_dEX{cG)E8Ni=<^ItQFbWqsVx5Ty)bc)OlKQIcr1uef4acfXUG5q|SE>D>0#q$faS^kj0DRWK{qL_}t=|1l3C zKwBsfI;Xo(3OU3V9xcV7!YMUdKetg0hslNUXIk*&1Lb-I2gVzr$#I+51;=ef^u!pC z_9Ux5oLy3mcm@PQHBYJcN81t;IfmD^0oubBR0BcBZCI+ANz&$^!C8!zuy%I~`Z!k0 z;>z-ia=yfeH~ixcHkzf_?d+pN6zC2$ypjxPcd9d+%mm>g{D<9$wr}{gOt(9 z;A1=@Z_1^ga~q14s1+jeEemyG_YI^oqgSXY(sE~%ePR_)^FumgW0TE>I(beGEChK$ z^TSVPznF=^eWaqQHC2&D1j3fmLLV6rN40s zZ0gDO&nrZ=|M||Pb5cV&ImR)+2R-*M@j=pts`6^ByI)V4BN=rgZe+guOGR(BJ{019 z+&SAoB?p8&AlaFid(Tx|%n)Ds2^Bj{X8pTS#{3uX@izU9{Avwkr+i>?{`h-pK;mrB zy8h1G*4{S#QA4hVtRZdku+heNnxmKY`XN@?lQ&*x`1P4huu!;#3UXU@f3B@ct=46r z`Asf8@C>IOYnv9lOj%v^!NED{H4d>{* zW%;qxRwO)#5X_Em=7`0U^C8&MxB_2Q;TN}v4c9DGb>XLLH7b}7}? z!NB*%-l(s-9DcEc2LP#ogZ@-pzyOL&eh_6gTL+-uDut3oz>-Er9xxkvw>~IHCo6Vj z)kE$S=-bTTJM$*$gtL_iMsCU;HPn=sS-us7%nmoy6&4mym-P&Pnpnyai@5+5ZIW2H zpP;qe;(S@~Y&*odH${#)1HPMcyZ7@tYUt;H1Ht@t?n|j&<>_^cl)^Goh)n_vtN+A! zWLN6&Ul!tUA5UX$_jA0NF2U`wB%A_mr6iz8$q+FZreno1jB*|?i}%TDSw4=XK7&5# z`4ZBd=|2ZKBk;iQKPR3zdTYPc@3Tywe*Gh#-D+IG`RT6^O zrGB2JGbo%y63`g8(NrnDq;}_C(y&DRi4C21ehIL->8TT1{?kNt4lL}J)Y)dpF-|zR zF*^7=ahHfnPqcqq*9tivXYoE*XHjVV6 zSa;-;U%?$7knPYwK1X^&yti9koB8AJXAg~ss-QEq-umax>5f%d#jD!_A{=~Ua5sR{4z`?G`l}9?()SJ*6$;o? zuyv+vPH4$@f2Wzi{(oM zsGR#f2P&(OU!HN+Sjgw@8o{J!=J=z9t;_RdlX9?YhZ;p#xPPOt*eky5JuJH7M6f)J zIkQi$^4rOF2;2cq73JkkfMZgvx~31x&A0`PHEWE=4!H+UR-D_)&v*%zM4|&MRW6vx zmcNl}+HEuzE&!|iA4TUL&-DLC@wvn*F_&ufoy(?VzPZe$tlVNExx}bUE)j*?Z_QoA zNG>Itd%4Y}QGLx^noCyXn!DWPPDsOI#_#j{e~-r=dwlkOf8OucIp=vg0%0zhH-quJ zO7i7(v9rFoF3uVUxYz`4l`)@o-?&bpwtj)?G4@$1Oue1oRQW&;k5^ zBiU8uh`-)-4fLsyw2$vr0W=*DMno)b|JgZs5wqjr$8yer^aJUtu|W2I|6lmc@d_&=)qGr`m0F09|BwuGOJn=YKfhU`xSKB5RapGB}(U`RPX&@dPC{* zr_M^E8Ecl#`hT)3V?jeb81Xqqc&S)7CZ?l`_pi_6Vl74H#|76eGV{~gFdkW{2q@9; zg|M**b^ldKvr3n+DT#%@BRIJuhc$oq3?2_d%}g=QIG zlsf4CrC&-ea!4M*VqQ_ie^r4sqmLk6{c{qOLGtOLG|)%Gn4>ow%#7CbYeGujJ2$6P z4)19lDGR9E;JpMVQpkqe#JRTwNc{gtW@fLXzHE=NTya|AdzQsVQveZKQkbigy8_dB zE`oZZYRIh;nu6^{O0r`O&o5R?+_UCwo){RgIEHy>G%*n@6zQ6Y2#rh!K0zL$(j;b( z4;el+#$4-=p}~n(pX?i!^@eAEh=9LW5DeSa)6YL^0l!Q-ZWcoeB<+s)3S3FSU2s{8 zjy?rW@jdHnc+U7`$Z+d{PE)q1z@0M%YW!yw57K?ZwK~AUu1&v4>JVKXZsS4WJpbo! z1^JC{bk^5%HN6)%fW8$n++lNUA>-R6)IqxOmk{=!c_P|`4|tKe1LPTs?R%A74v-SQ zgxUMMIjbQLM7aXII#lmtJl1V#V*#3#3+7X-dke(*S5A>%uE=looAG*1`2PGqmV9qF zdGBX3du*E{vHo(76KKMhj((LT*R|2IRtvt5BPZE|wwVbVo z0s>i1US%AXS0i_9ic-hU6W%6`N~)E?`+_6YmVK(kU;1vzgbgqI_D^+s`&ck z52liFW5HL33%WJ3%J@$}iFH#FK{5)_PU+a16b5EHJp>Jg<9e=n|Kdl%LFf=h@7cu9S3i&N82%hRD>|3mVL9G4nDWH#l*$P| zkn#CfyXMK>4`-tOJ<98NX_(OxmLau2!V0^3?x=9Bpg(n9LqFzUK~h#6YbdDzF&+F< zNKw%b>E#9N>CddKx)M_qca?jdXI9%yL|I~ytg-EuRerm4_S%d?;?aG)aSsA{pJJo- zUm!2JaW|{cYFk41Wo93n^C`F%nE8!sQ}vb zzqJOl{$CR*Qhv&-7xA)A8rdmdGkADH)}*%{WA1x50`ngHKy-Z|A7A9l=d~hC%bM+> z`hWa`UZFnoOwwLh`+l3GQZK>en94_QdRP@^cUNK_TT?yon$JSqPkcK@f*kby)FoZ zBH6TC-^Iklh--Y-_boABxK~W(eeX_cezKksv>NHKKzHwcc3$5f;~Tlsjp)?n@mdpD zk#h8ukc#JfuA=c3?fc}(*MBmsJB_nGK;Zl!L}~q~VcPr$)5}=Q61`rV%F`m^VWxe$ zmPdp&2};ed)NGnx<^>|n&pM_rC+0+*5JL+7U8t#0R}m6(>CRZ%oxBF^%)Wp9`sIJ* z$D%sVCXj@g=?v%gNJ0Vw%q1)c_T(!_G1&(M?^mZ3bnXjtNjLukmA;|R%Mj4z0U5P9 zT{F^%JOzpFyHX{(ULeB-*2TEaC4bQN`3*f4t%3e7KEvl3sR3Qp#UvKkJ1n^Lj92pC zWq~5f9_Mw>MoQ_Nm3)^1eqPw=q9xz&Q&q6RzD=90vGKMEz7gd-2n1493lwf{Fg4EKaEi@Yw2Gk_T(#|n z&J|-|7|IU8dl(N)^sZidw6_)rBtJg)`Q!XD4h|Z3RYYGq*S=G<1yWp!%;3>vHj70Y za?eOeX-kViqZF~oSBc$>?Hvh0rtj$LL5JcY5VhZr|4(WY02CzKbmMJJqjSb_`uSl~ zXGChe*nvh5Z=7emvvHq53~>CfWZ8UsW=8-4;AQIS+FsA$!baT+$EVSY){_^mfE95u z4W+MFDoTFN&kBFSNh39e9;%VIX12HI=YMh~dnlf-Z2^k0J2L&wTy>E;7FV%Qaeh9) zR+8;uYf8C|xY(~mmX*Gy&YfK97DH>8lAMyh=&W!)jB{{R1$e>SuQ)qSbe5U{ihIU( z{_f9+7L>E>dZVSM0fdEC*LgF?A-@~s0C|0_hVZ|8YqWy0v;o>*0A#O7WG-t^*JNdp z{~7=2Qc#p!X9rlDk~=!y-tR`r0}hZsdjPO9j(m4 zvA5qFYpB4pJzEhQz2giPj-uXK)s*sm3HG8$%RQN6KuYMfy4S`iDE*Gal+ZqQ|vpyq=^CRbTwi(3M^)xA;l&YNT0z0%e}z z^nmn|WU%4UcIPON7AntFf-^pNStnGdzXCnYN~&JJ=fbN#iu#0OY6$H`WooY4M}%>I z7S(yK)CR4fzIOEnHCL(l85IrjD1C0ehczm2GKitm2yIBA3Z2BcM+_er;jZtQ{~}~u zbmaipp$z`1SZ7nT;dr$0nUOj)-*W*M-F7^LuT@7j9;CAx_C7N9lZw&? z;`;}i+k=+w6P{ih%(^Ed0KyUA0F)XtHS)|J3e?lXvie{E0yS8}bHi(c+<)v86fzWX z0A$GycO}WlyrJ!b5ii#2M}@`Z?4o;;UX7f+)kU*B;g*w9CuCeAG$f#wM%NkHqWpDd zg~jQw#-7BL1bs8c7_5Pn-p3kh$RB6E&%2_$kezHGzv|mKN$juvP(4(!v2i2je2^9B zf$90vH}-h4EYV@~|jIq{-o1^w`q{`Zknm8Zpz`>9|x>sYwM!?4?KC^dfU2b^G# zw!K1sRb*=P_UPB|70*kSRn3gf>i(i^?Bh6~`AZ7ffSU?`e%6@o$W@i6DFj|h!(DPvLAn;0F#^+D(id5BX zbQ6$3!p64yw4-CeeTCDhqZ>S@->xuvg_G=#mIS&l{5)rRy;*q5la)66?Z-eA4EBGu z<iJ*cRM zJYMt{9iI$?3MT6kq)ucXiGh%_2dyk`a+EIn7=t0`Cy8!DxN?dxk2#4sw_vagdngDY z)()J7pxx1KM*s2UH-Y^#v|smCJK5`Hl3zRV{**eFgptbce)ls;{h2XnB{oJ-3BimJ z^G%ez$UOu7{^DNIx~3W}aNLWp!i_3+QaWGB`OZ$s`gDH5^W>!hy)O*tXCF)Nhs6*P zk$3^^ICX~&#|3o6uPc0m$1|)3nxFEkyh1R<9AWc$n^$2a(Wqrf2o#HTMdW0B)0B!V zy`&ZtL_ozttE|Xcm(WYsx~^`@mMXhMZK|JVgYNf#@A^E_o%|sF^DTa<{H35T1WnaX*IxU-p9iBvRLF&LH>8H}lqMQ7`e3t^cU=XF0gwRm?-rf(F zF5gknyX?n=jPNlf`0;}H5e6vZ#`R;z9dt0U6(JW0agC5zEWEl`ARluSLRlj@kx$9{4pPik(x1vsV zW)HcI{Q!s;fwoFf!-qbRD*}{zJJTYT#fn+PZv6L*6drQ+s(DecaeOLCF9|=kfhsCE z6;w0jMWHg6jm1FGQQDeXlFqLx{Bnz2Hv;LC6Pm^Q;p{(u|5m-eZan?>&t70AU|QKb zUkp{3m?xnR{)~B{A11vWZ~`B>S!9u56heB4dA9}{?tP|q+W%e;^W(d4m7oSYmSn_! z^Mr3E&I5T)=aXG!lr+CY7vK^%qyi! z%l`O~h!fcoipjQF!Bf>CnIbQFn#~3Mjj+d%q#r=qt}!%(!+CTB#`oSB(+v13xJm%$ z3U{khWn_wad3}l9#;`^aXJlhclRTgw>_trj_@JVOLH^W+iyfbgTf?_x90JB@GmaQ; z%PcYjb19}$th?h%5fJ(DGkqH`6!kjYLp!GjtzzySy}H`58|w1Mqp|dE(()hwcw}|o zE!AC2NtcFEW(=)HJ(a#=6r@k*HQ+Mi{2kJV4!+qw^iT6rMv5Yl|G2u(4ow9vSU_B3!;-g&cK84x6CzqU< zC2kl7%rZ?@3TK4(PHTEVAJ;aBB5@YX*}ic+!D zvV9UjWjiN%Dm)}SyiVg{_}XN))4|UC&2CX||Bo=e&+mTHPwIb?U;|62MM#^j1~l;= z?(8q4Vs0fXe+eBA49ZL2VprN{Rxx*VRf})_Z6$i;X}JoP{(pTv;q2@k0Ek=Ub?<*_ z>VVi|@x!|r{O4@qBlV2e@!fQHk7x0^iHJG{cVhJhzaKcJ=rOz)pOc{@P8-vbKO z8a@RAb2Z=lGO#D*TGG+fnKOi4U7JmK7uF~aYL6;*R>n$s53<;5Uw|g(fsl#hYRRi4 zyIciBotQTNUuq^+xcdpnFQj7iF9^=aaS*E*x{`JQD^JZGVg_HSt<8|oM}Oyx6fP?b zUDZi+weF@|mNG(S*!;T|Tu;tk2#~OQ2IV%mlJqA$>@xpX^)57nU`)A5(G?gAOGb*E z^|N3q^cB%Rg_$6~LfySWeuQ{oa~+KZp^mDrAv`K@E>yzS?W-c`xOE!GUu&Ww$Wi7e zK4bAnhk1q9oy*5lhXeQ`?Y@kw^9jaa*r+zsZ%q>(8K%R3*4LRD&j|?&>aPpF3TrB# zem1E(pTTI{-$XQJr>}0SnI3l#QHy)y$OU*YaKQ01Ogns+y^);!aE;?z`^KqO(lrz2 zFm2F8-yTYz@2M@yIu5So@^dV_eX~0QVzU+r!72lu^RT?)BHF@2zeX#x*|_lPlkTJA zHm@We7F~_Kypb=jTq2r?zns z`_qWjQp+gA6i4H1+um!7jL4FF+rd=Q6Vlyg&>0H>r(fcvb{};Sr6}pM-t8`vNk;Ie zIC)k@IIde=9zlJamCuVXxTGf2$#fKS9L@4~HGDs3eWLIt(%HKcl*--1EG(Xu%t`M> zLf-~q#Fk-aK!AHV7m#qs5%VmcLKZNi0u)V92TBmMJ<5zWrY z*f5Hl0gXH}!8Y&uYLp9tdrM)Y9@`SeO@#5PZC#y5zH2JCLb^^gX8^A531e(R*(i=b z20H8Qz@;Pbcpc;rtJhFoCe1dAap5aNrX*w@la}I%ln!pcU*#O%G)OozVSDYJitv@` z*2$*voN-c1+n=mvFyRY{`$O3YeU5=nRr$e_&@wU;IdCET5%*{J3#Od}`vwLS7IezmN1X>xG+|2;$oG=CGtMPfPOi zYruZ>0E0NmrQ^SNZT{m25iV|J>W8oY=&C}W-vsS9a`Fy>_-?t2yJX;!kP$IuTt2WH z$sXiL*6P~sa($Ww#b5)#Vs_H4?Yo(HE!!#kL66+qDo+~i1_N@4A+^#7I3p#ba&eyP z#73(;!w|#rm~Arn(e>EY6s8{A$rxVs$dviI6<&Y#@1kTw2h!GlvXXUKh5HQ+z?ili z6|Xw}vYfTp%8yZ9BF=y6d{qS~^R{H<6ziY2z>AY>uiCcRA=fw2wVfF_l?o(;0LwBS zNNA&D;+Xv&A$N#)JJN?8!6cLkf)iffzF6)p5!JzqM%9vFv48$LJgy#FAydHh-` zxT)x}jKtUnvDwI@i8>OZtjYNJCX)aLtBA%W+kr2mR*j}RyD%)CN7oVzU!V|Aw>%vf z_mV{{G8d1ESF<&X)t3;RFuikMKLBkJLry)CH|zso`r;Tz(Ie-~O?+h6yNu(bz@EeI z38_joc;ntdS?>)i6ZP*tQ`iJl@z@$YC?xa%CUW!i0>n}5!{m8J3R88oZ$ZY^!nh!_ zc4JJn2gWT`HR@>C_Zm#%$*(`lUBVjLGgN>(wtpD9!u_m5q7RgQZCVN;ndn#W z;#4_bC8g7)2wjJgpM6Cheo5?YO__$N$$MijPs{v2qSU&5W$}lJ<8;{A2FnKgw?A3C zk>0@BY$ZD1Kt8J@!Y*KQ7OjI%pr%+aur>E`0l0{6szgt6<35K7-Hq$fp70!IvBz4L zKZ8)@wTA5>1)>UQVjWmgd%wM{$ykr=;6^PiO-@Ts#O^k2YCym4Q$*Ma_yRJD3!-sP zp*37}nVQD61x7`M7&&==_si{k2UrL2xgF-FT|2xfOf5R z;a)l(>hR0V{U6Xyh0G}@N15eJ$25ha^%%Z6j09RMB6xHo&WPSHr69Bz@NIp-=mio) zio*Ved%&rFHZLK~!vt<3(!KkQup14V~0ywPHT;)lRhawOcM3%50e8!Gfc( zAQrO6pmbgq4rJM*O_yTnteNlfMW*NzPcl_gFX4VgwjH|_5(L#41Uio!!$LyzvXg%{ zhU0g~h{Jj@1PLNA%~CKUW9A|OVbVD%FHZ|d1_3$K!{6xQX^`Qk&gTU_Mii=RreCgm zWyfBLi`d8nw%gd!{aQ@g*Q$o(co!qC7)O!b2x8-vyNRwE&{Xrxr}d>}M{8dDQ z--%j4t1#b8{&;hg6JM@u<@y2uR)Ny=2!sLcTbCry6u=Q6P}0%V6Dg#@Y;|vf;F1oU zFR594yTD^Lx>Wo9Yr~e~-;_iYB=tYu_v4ny*OBD@2#LMr2y4(2aGH562i!K?C>d_x zCO}+B=SO?+YXaZKt)8J1 zWm7YwTG}=bYMpjR9&sV~F@Kj{m)@1)#^~I79d(&g^STseZd5veb2mX994(YMCn(u? z$}uqDuR0OCcCyniyUw3SMePC1!JR)@@`LPOr3G$ZFwX{RNTT4mMiT$*;*s}4i=USc z!7Q^^6=xc52-2ySX8jpV@`3cUvuK?dg`twrssR~r81lYU^Q1Lg zK#vby_6U4&lvVJ%=1Re0w#hTX_=|XEcBip%YY3k#SM%>CfHnXiazA#8v zDKa=s3~S^w7KClQ*B!;xNp&~EP7|h?p(~^O)}tq4iKwCnaz!xA+%)wzf;)N=2=xji zhipt=S6#Ya*rzKXIGR}wuHn(me3?PTIT`X@VQ4yW8!%8Y*?#g?n4|M8K1(j6i_Xea z&d7x3*QU-Wy$z4FMw7TyyWq8ym$lWmkKWk&Eu@0Z{HOU%&s+ay!xmpjuL&=}TZG3b<;Hu;AyTEyG5#m}6?7R)!yG99tIdX;fLE~Nnk>@y~^HRBTI&V@*F<=LyKT%<3C6(-6## zj+@=g+4``gA+v^ zVf7R;R`6R_r+?;!pkI_OCZjWd$SsKG`l1u2yMjQ;1SnGG(4sz|Pk_Z$+c#Hh=U)(< z(bW43YLV6SmgVRIme9Ssuv-$OPJAC?1nd@Jxv&`trCvL z07N`g>vFtpUFOd1;Kk-6JcVJE6l#Fxm$ZG}vaqqFw~4ow*$qOnOj%)N5oN?r4S7V@ zxbU^$n@BU}Pr;SjJ3P2q9j2;EoO67v#G-M$*VrXtQcZ@5tHk|6i}tnPt9dA@PU7)6a1l-2yfnJh(hr0oZm*X1!jy+cf-o>@7^SN6?@v8oxn5c?>z%sZ;dW z;@QYa0j_(hyzS_$Ywm!*Z(S*U(yiFUu^Ux*yDqB!hel6iw9m=bWOtD5)wpQ~%JWNI zSiw%PAweb~woSb$wd`{gU*`29>Cc2hfrGGeWraKHc0C}Fq4ADS+39~IRgDv4SWm^^TyP3n-zW5NU)i^m0B36uqFl&evk= zu)voo$`KQ8_$=vpKM3K^IG zqM9yYsG0&T$iB{?|APfHlD-#aAf+<@Ia6u`zCR#1KHi*>lG2rPwAJVq28>kVinvz? zx)v;XS#{sxgpfq=!_S*=k?zIqnHiX$0Anotvfsv9fB!RC4J|Ed@U7`Zv(wA@Wftx~ zjzPjhIDAROIG};y--kv=GdsxS#o6jEtUs^S27O*^aaV-ENIM!*+Bp*(c5=|EQIQGM z-9bLD?KIe(0v-Noof2O14IlrPsHZ^mJH5N@6@fLso}aRiGC8%5A~OI~L00C@-UL~4 ze3a;AKYrzDPC9VLpR%*h%hj^2q$%1T{ynJQSU|42^Y=lY6hdih>9eY49l*Xb|EDB> zaWPKDU^SMIibv;#c#xSl3WWjKv(hx`|;X~_)eh3kti>|4iQ_@c;TUjx_Ry$SAUf%j{!_R<-=%vZ63qCe} zVog*n^0w=|b8-emKOMvdrbM*HL(UpK^kAKx0&OdXaHX8VCQ?*Ow{18k!P@h$coh5n z>jxpys3UfrkEhm)*2(?oq9^rqN{5kYM|L+^1+>;`DrGp(afx#CSiuecDr1?Gw!S1> zt>V$c{>B5fxj8rDHuL|9rB$M0e_c#k$gpa!s>x3mPqcyWV~UhWB(I=tuvZQQY;O6Z z{@-U%^*4l?tD^D#AgRw{2$8^LM9!IOno8wv4o1l8QNHVMezk&p1pj+epVacOkdX*` zPp!zO2k{C;Qg(bq%I3Y}G`e7SzodLhJ24@is0+dB+(Z*C2Cj(UB>TiH25-y3k03S{ zDnRQGJWp{82g@IUbiRP{8$x?o9xDB+U(`RiUNDA#847U?vonnaF{lG+2*^SxKR_@5wwx$o{#$r)YX<7MT%g3sr<6Lm>dDicSmH z%Jcbsw$%e3o7$|yDDP(2rVK+huf<2G0{!<(CUNROf?Fb_*EhS^xxV2tJ?wCTHB19Ag~Z$c)@f*IKEBNL z%3^GO=2zQdYwKFD-hZoW;yfC*xyc_7*ESAUUD0I7qCjslQAoeF2OwfE=qf;Srv z|9Bm4B}d4!3Rt&|x{w;$5f--fTkM@c+@kZhpTGBZfFQ$Pv$4R#%ERr(T(iagW!_>k z)2K=!y+v78UPLcKxm&^O_l>BQcW%1wLETpPTjO^Jb;VjqTA zRt%dlkt8R7rfcrNR``@-BJ)#CH0k+`&iF_x+dBVkk9tk59In5d@Vtc@<9?q;Nt!%w z;Bzww+y`?n~#l&K0_cJbCtkF8zq$*j?zS1BZ_&foYJ z*Z$v4`-SD1KeA<1wdI^t!sqS4BmO)!6V*^OLtXKvQ4~qnn0i^*=Nu+k?EDto{jv(? zi_Ww|KH*@0KeTZY+sR_nsY!xBRu3t;4PuSTfvW5l@fAg9L)*Tzn}=-lr*+{R0;!OG z0=H6JlR%Q&iBxg#t8R_27kj3-(pY1*IaS+uJ1-WiV_CyQP4Z+$rz+pMKWU#`b0_U8 zr@h0*+F2(0J8SCNIacE*BLSDdnm^7`;93SWRk&s#Br$_PDy8LOEv&4Bmyr)a?hG0z7Pg{K(53!n|>z~Y+ z#summ6{*j|!gfNA;oFC%70`OZ7 z?g2G%T;uoRiupqwGK961JTZjSZ{I=V*Zsb}_>NaHql$9(Ld*HFL6?t>+Z^6q;l}7X z`eTdV=KG~Ctj7SasAo-F!ukwXDRK(3h7F;kxHGv^%B}>1o0~(`MWZ^!Pnz(bwTZ2F zz{Fo?lKAZ7Z#c`Ay?1{oxGq78W5kn;b9gea?m6+u{>I_HCZO@0Ig+?F$@RjUgh(S0 zkdXK?)n=i$Q7DzfHB!s(=QmcTzcLm+Gn_WoE&#K^Q8OQR>ONK%YQ%(m=|e`co@ zm9KkI4etAMd3DSS&am(*4jw;_8lF|p0a8J{laOcAJN%H)3tU}}A0b5nk zVJk3`R1?q+cGu?J=JZ#q^xw>L%=7Z*2jwZlR5S$ew>=jpM=lhBON7OdT zv`@c1nL0{79|J-E4y$h^ow&&RcehfzSbY%Z=%`4tFv2=I3Wfs5GBwoY^G}=1acTfT zWn7-tw}?+dkujcvOW9IIw^-5f!yR&Ra++qZuBOoyRv#?bH-u&sf66v>3Dl}A&U8AJ z%JwJ+V8h%5c7t#~Ic{U{qzV_CJ!TV0Ff<;Sm)e4~4dhVLZLNj^oTZL%wrLSgf@0QH zT%ebEK9G2{MiO%qj*9H|&nC3{sy7$eK?E*o^d9n>{kt(t>P2;8G_(w&Y);S}is zcj62&@bj>Z;%XwN;YHQvVqU}8Mr9>HeP$noa7Vj<8|+L&Qh|H*_O=H)BKS!zLsg_q z*5IW=;+FXj474D)JKA6LK;xz0zkErLrfc2^33}cMFxLfR`mVsc@uSHhglm|=G(w7O z@seju8%j$0ej*G=XW48ZC`sl@aAm>bBi@ma(g4_|&)+wr-U-%4@FOmh)gi{$s9EeZ z@`qVJR-I(0m@J=Qfy|)WyiUxUJ{FGXdOx8rxUJc?`L$Lb@}*xAs;U41;Zx=b=;_z6 zP(_^elX$>qb@KHP4p!Qr%|C^9zD#c7X;NuwT1WuvKxBWhH5pKD+U+>7?_4WMX-= zu0z3iYg1$Yu&LPFqKS=Rc+JYyAFnPRS#>?4BzWK2tO$ns;%xUd z%J|k|z;#}YvvE2{Hdw$PSECE0)T5&ANdr2~Dif*Os$JA{T)T#%WAT#hpox>#7gaB| zN28aJSKKtZyB6Ms&GsFw-t)E_I%>eXUT066g?y1KP8uEyZ?x4u{JRUk zXJN?Y^kY&O66MIaJ;n)NL}m~9ZD=m=^2#|j!EP~V9v6-!-n;33k*SeU>wjhidlLN) z$Z^q+Q@SJyi z3$|(Mon)0V0Pr#(b|LBgMhG=_JvNPahDTYrPR*Si{@5!>MPDx<$nUGeBWe91T+kLi z_h?CgaPMe2dQJn*%`XuhXiyLpW_iYDNq9*Hq?5>lt_wu}|zc~zWG=IZYby|nU?hf1?hVmRt0joUDwHr;g3-O@3Uzipv z;Igaq!t~k>_xsxQ6s4QX--*(o(Ab-;!`h_T#)H+yTvMUL9oFG?wJp!4+0^0I+V)x? zUCounW;gCL0ms*WxXYW)8J-)!LHl2kV$!0kaDTxmK3Na#yl%KaM%oP)acq0DUxhn# zchc#m(+$~iv_=sUp3|_14ApSilgU!+wj5l^aa(7_NqD5 zg}V#-`j#ez^)Ga}jc-=3^8xwci7lYA*;7g;(*KtMvb8~y%+xmy@dD&I^es$Gn4+z7 zJCInS!aqc-#Af$Zy@M^uo;(@9ymRn1k)9sM%*d$bNLVcOfgEzuoijS@j41Hdw`txy zcTRNjL7b`-z$3x@3_OmoIkq}{d(73A?27n`D=*iulgQ~bre-Z3>e*84}b4Z1eKj~ z5%dkXW7l3f>M~LQ4nCAUWq-WrmJnIS zDWMG3<(fI9I4J-k(^uSnXoLSUJQmvG^Q+8dMdythHaA69N&0PBY!d}X6mcqSw*4o# zW^!EXmjWIKLd}osx0@63YS@30^}S_asjEVljlA&P1-bKpO!zrL5d0f82>tiZ5c`%^ zbU{WUH$pfgcmpAaD!%sUHVS0Gjg>^*0D*3K^I!ItXHSHv$S2-f5Ar`s2%DmA05TaN z+>W>wt{>^E?WU=vdGDYDV11%Yh#kBBXpb=osn8Ji&rwN*yf1qVYl`-boZlQ4<@wb) z*S!P9DeN4eK|hNOKbUDHRw{-}3^~IY6}A|Id%>9(Tm$R9puhdKShGrM56tGkL+0fL>!GvV*3`6|iQV07t-&p`H~#m@XE?px)r(XVmXpUBFUK$x@L zX*Cc+YJ7uQy^z$ge9jTJtB#u1>eZ>ky-JlBVtn%jz z|Ct2}U9sJ4j%ierr|Qs1Gho;_9aCiG|vjJ zpIpRSr&9wwM$_?Hc@l1`JiSA>vFff)UB`eUM|>@T1e)=$s$3e|b}%V$?!0!kWV_vD z<((j#VtmF!snMP2jLwTnH(n{&T8?X){f85Z;z~uIGiBC6%!vm;^4IBVaki6jzPuT> z8$I7C#uYlY4kM&GgKiKqtSzsvgX5EZA;9G{3Z$JxQOh9iR^`@})Rve00MQEwN?(Tb z1%jE{;UV-5TF(@x#AtX;D{qKM9Fw%SH~tZ6=SiD9Z%}7EBN@KG<$VJfO@$xM2L9XU z&IVMXdr5%j?eI_K;a|nWnLOIsNVV^kyy`IzTi~`CMy>#MIvyqJq(v^y-KSmJ{~Ccp zwOCc|S?nkp3QVhe_b>d4#fE(B1Hq(p=73EX8nqd2Dfp@LzcPt=0yiNTc5@6 zIH#oITnpt@OdDfk*u7Gm3xYJm7n1i7I~3nPY#kBiwD#^V~@b z-EEAEa%HEuuhx`ZmpLi^N^r1P#zCqNapb?Dj%%0XDu-53o(rSy6BUa{n{xWl>KHqa z{whSX%x%L8!&dEDkmWHhO)wNQtc!(qMWGV{d32-#l94kh!`By@Fsiq64aJ;bIit`P z^(^1%C^Lww)5@~sp!#K&5(6(tp>h#nyk-cCUZ3xhxYzI_m+k~Xdd&mGCF$#tlIOWp zB%O*;CzD&mn^Cclt{1XDr4FRHds{!nYrk|?zolOmWo_FUsr6+k(#?oad`cH8o;4%~ zNEY21xfW-qrLmGR2f`Rw>@D-x6_MTWX?~AmTutKoybyI>nZ1#0p7eqk$RE}1gSYEG zenF7eGmR>P3hjELsFH%1#KgoFbuByjm^QdT;xEHrF;EN!Cxl9JJ`(8%1}h-SzA_9! zk&#Oa3`0X(OAPqT43E1oR>y$9pb7Dl51b?oL5YbnBnP=2FAJecO8w6>%n=q$$Y)vL zx9V{7j?rwU|9aO}*;(?9fQRtT#n}b*?i*vqDx-Azi7xQMQXeRmWVe9!#`&{7;QC4|*%-3MR{EidaxBUjZa$a4-N~fq*7LI zbal-G3Qo)XCp*(IPMKV*xsxHfD+VgU8g2JsKrM_~Y~XIU*ql1bqM9#(F(*Ni7>cXN zi2{Ewi)r$*w;$?=8tz^on%Vt~y+siXdP7PHr@q6cjYV8$X2SGJHgBxfGb`pU~gbeq8ywVgt zMY?7p9uvt-lYOu1L*q0=?CuP9nUVXId*Hl|o}5U*#F?t%R&pz@1IV*aN3MhHjHI7B zO(`sPXea{Xj#fb~MRMF);Qn5#SFDzaALw0v)Q6h^B-)t7Q*8|e(6M6rZN9&=tSMAZ z`h40#+_wKW*Rx(Y{#(79)3>O&@@TkDWmj{PW~}T-+HHO3Tp1UM_wS)UESYIm>?i-d zNIQloA?zGevfHx_CU*mZo(^ML$=jBGO2yA#w;f$17QzghYSMmpAEhrm!O|>dU=S~h zZ{Y6Sav5JY5c~jh){wCk;97K@Wcf~cVsi<%tO(#Hzic<8W73jmH++sDojt3-0Ix^Y z{9DyG)PG~JP*wQMsXCp+$SA$?6RE}7MIC8=8@5&hVz4@+4G*LKH_bi-3wLDeM}??( zVTdj=?u6M&>z<&Mi7mjYDALl%_Ulg7(=?Lb4n@a7#T|2%L1o{-zkiL%Sk2evbE1ZRTep1o@j1F5@#w2FH=d_`q=whb-%r(r|yPan=t z<(%w1C=NRNjk9Ek9Bg#H~FEDw)PEDZl*^EG>SRFVG$w68I*L^X-ZKk zRcD_ldFUG!apW{bc^)8g4Qq!VZnow{usN4a^BVSdY{0H29aD!pw6(nhp@`k`x=;I? zfmeE5jUq>E&I`_$7?c9TlZJ*V769*(;i|RW5K0!h9$vq`>g&kBxf$QD_X-*r2h3-F zQ|teTD3Gy8=P4ockO=&R1-vzDR)^thdAY~WV%e-G835W2djUd3-k&t9qSDf906cq6 zkb1-Y1gZ)#JrZnVwg`Qw<7HxD+7DYFW}6Gj`1)c57;KNRjbZ1ETnS0;r`INvDQR^Z zt!V}20CP48C?Rp}K6cjeUD1{4u~p!8(yjWc)f2vVB>T*qc zlJiFIt(a47a$WrQXNN0wQn2Bn!2)7l!|u5c&m9wh)ea#L9jem1=5lcmCx0uew-Ad= zNXY7+wfVJB^fPFuD!CMPzIA+Xz$3$q6&5s{rZTw0vOiGIQRo2fkDQs!-C*r?mU5uX z6<36I4^sIZ6X5xp8-l)y(bzK;#XARqL>Is!#cJemh9fv;het8CYoT~!w4@FGN%*IU zjgG2y+kwZ{rxA|%$cF~2eU>3cy&Sy=t*+^cm!CV&I_%#<9Z7_zaXZ|>+NlbMEzHF3M(OR4P>I^ zuY*xdZ${Nc-j-aEf=az9$44GCYriq)5KxJ)-n>xBQ{fT$@2Ywo@=Mi&BpRC<^sQe6 zUG?!5dw=Vj&5w4Ekouh=%fg$d&tySJdJ=MvK;TZJ65C$*7m}~C-kBd+ym7oD$_6T?``+dk$9*do5Fh#}N;CIKOXXDeGf?Q6Q|a3 z%pF#FJGDWZ3{LDDq<1l~6=*_JFcoU=GLLu8<=Kw4=6zNdY|9;>>`VY&I|vf<+OGi)gV)oiGcf zXFeIHUqhCBuVn3lLtcN(`u4GNzNAI;3j^vpPG={6NIFx7q;>teDKE*1&ukIA+W)kIJ5Z4p{>d9zbCs}v#f~SR;7o6;~_WE!YUxqS3)08f`p$U;RBO6 z`NWH(?jD_?T9in;Bpy&+t^}cEu-NIgkxrY7W~@Jq{XP5zne^=z&smYDHUii5t-gE( zk6&DjQgEeMDUp2nSh!@@Og9SUo2p5hMsYRbEO+M4DjO(!RWOv$y;!t0DIuw_UgHsD z4!=-n?yW3y%GDD}h?kv@B(OhxfpTLL-=;;&4)k$_0h#MZlxHcR@m?Eu@;2P;CN`t^ zNE}V%?Q??aImcst?ouv-f~Tr}hQ%Cy@9*#LUDtM9+upCk{k-qT?cQ_0ad+h2&`S<8L#91Chj%^%u$W{k+D-P& zyQE9V9gx|n6~(`GfUGevH1T`xmaF05XT|%MBO31FX9VAsa%I)goGx}H;0Ar2Tmw$X zI&pRxFs~PK8__B%I>9#UU5;t?kbL=V>vWDk78znkSFN;k zbYQMgDi4o--MU3{kx@A?+4?jp1)rzZ{b9^Dt*ex#MKX6V)=RT=u?naoc6D!$?xHR{d!KNT z8Q-CWBj-l%5)0~$?gcTp|~Md6p5tiytpet=;P7pXe%xSjAyTvZ76 zM~slFa<(&9L&j&6O$sbtCk{6(-|e7E3wmf1Nfo*n0ePSiLDN+mZTFb6+ZJx! zTYBeID%6+b?}U3V{V~9Y2GTa)c%WRZQG3+It%6HH{Zj7X5gCtP2EMnMna!5TsDCpw zprfg__2_7^skf?F;_=~(5*DGN^dTxd?3f9}{%xQe0lH=>*bAHolq1IR>TBs?@5Q<}vV9flF$<8_$M-!hSamuFa4&gYtnyCO$x*f^CU z^*VfVeNiZ-8-NYvd_TX~ITk0$Yo!@m><$DEjumdv@BAFvpU$@t`LoizOQa&UHA9(i zjo8=?K@tNh+3HTBufY9{_kq=SGKCq|@aWxL*m(mynUMKf!~DEPhNoRg{YX<;e!{Lw z=~!rxw#hYaHMJPFQ`(?8f#)+nKWJ7<#(_1FLhpdIVOc&*~| zixFeJy9h;=Kr#7FFPH9!+>r-hu!^VT?gp{9r~3#GCA8(6ver#qiGGfdt%`Y>9Kj)* z^q~3$TY>=boiHfj&4Ug$S@;z-z;$7!t8$PTXLm0k^=ZXt2N^$GBun^*%hp$##37w0 zU5}m)Hl%9d^HOUsd%aY>-O>|-=!hV9I;1D+e^XCr(X%-2QhWue&D$%wV|w*(cI@$@ z&Pm2pShA8xq~}O!_0rf-GU?aY+o}oTeB8$&T@JX5$+iE~o)H)$TXHX$fN63*fxdUR zDtYw+ePGFFrF<}?u#CCdBK6yE`lHCO(9i+<<;|mme`%~)Blu-hcX>@C z>Mlb~Xo}JT(T_S@?xkmhVh5;3FRqlxpzzoo1H1lTSWjZ%AV8g|nZU@=VCVb@lX##{ z^Kh}eXw-$J7(a@CFLxe*XYA2Tc|`eb7|hKb(kRWSmmG2G^C;#nUs!N3SI7VGiBqKI zjM+W^{oQ?{k?PUorKn>jrKqS76rs>ftbuki@c0Be8*3@BJHF4%eqWiQIB3Gneg;w| zH|d2Z?QKT3R$or9t5;SkSjSfMkJ!haJh0Pw5f$Beqx!4 z&!=b5M^#%^T#grsMZ=-WOZnx#&&VhbDjJsgnGkvST%J{(%jB(e`H6Ic4_Us}7rAXy zMT?0(TqYc+&@ov%+a#|j53B*KpqB?zPBdLw#@7z=JHJddN+nv_D1xS`Bz!puP>HsM zfq@qTPu@A$$jO#Y?Ok4koU1J_#TSn7@%nJYSBNgXoMxBfPlVmOO$Q4gX#UogLDM>H z5+AC_^_9Bln2qPXEZdt%)*wUPKc%FP`N=KmPrZh@1iQYm{JG>~ZPe8lM#hVEX8UqM zAw)xc-0*uL1WiKH@y=m21{iDIQp}*^pML2z1~g>N+UQOXsH}h+U)Fz}zgQ%v}P_?_~aF zf>R`U+bFsJPfCOl{AX}Y^s7Opg0xupcxVtko9%0_+2sssNFkCbz_Bx>3B=s=5LtLH zSecRmHql+XER^5d`)!%KE49MyH}pi16|ZaV*4(binDMXxt0~wb)6-K3{S(n?srWTP zz^Myy&8(6*^OV$FXW9Lo7?Bsk5$~K_#R}@^aMb?@X0d3ja-9dq`nIVN@ZvzzTW-9b zV%eB((frDSC>GFDNI(R?RL|@L?t&9CNVa2mvKsH(N(q}h+NT>44}mP}rIlddAW3Lh z#ILt9XD|SIow_}1L|G^HTLV{5VW7`^c_+f&Pfav1@ZTZ+QOofRQK|eh9wS|ggU}m= z$o8Ar^nBBs#C}~HLp{mWW#LB}bM*Fhx$f?{!<`dncb#qKU!cR@0D>oqbnEW*c}tHZ zIF|NiH}ZG6y$$Sadfk0Lr6ssDJ^$kB2>0oHLz*n>(5$>&54oi@e{yB@=z>%J=-$ls zzy006C*i(#&*Y~P{Y)AuW^j>TBX?eZ2pPvwYO>s|UIwA_ zYvk*%)KeZ!O=T|((lysNsmtWgHzCsiAKA^KWJk!XkOnxpHpV1G1|Yt_ONUJL)+hBx z0wTf>jz}0UroFsxG7OmlpH~W4?0PpE+Dh4yyN@yO3XCAI@1&=fA zs=n@j%;_2QUkUR#+}Vl9rBfFjbITxN|M~t7oYwdiC4_Gmds!a`F(}A%-xSvC!YxM!8=I zkv*l}4y#q==(G3U<&>H6r}v0KDv-YARw|YQW`>%<$Fn#B3+@{FGJ`qin*O*2bZeu7S4eXuL^( z?DRwLH?$OPE6S_RE{^1MJvpyG2eP{=b0rk`)y;l$+k0q&cvtQ2k=Be_B%xm#QmU%6 zze1rW#o_1AyPh2N`36#B>zlHeWO4eCEJF3G#YoeWkG zt>0KE$HAL>F6C{ZEFyYasu=BT{|v2Sa0$GxbF=)hyN;YYSD9{Hmy*;cF%LO`_%)Tc z-VVCa&#&}!#66QSy!ovwY_;k){Oj{vwV=nBd)PH%dFG9t%)bji3OZMvo3e~o{3nya zQt?wwX-8a`@S+BQ(7}0N#3?eqmQt31n8vG4nQt#H=E&c;G6*jxz6ZZO->61qd2?FxDchL3@_5cf~vAop<#|O7>e`UU}%f26l3*hfB*MSeQ#?^+2^oVHC><95#?@$HeU4h`#>9q39rw!B~dRR z6}i4X-@|`)gM^nJY<@ckc#dn7k++_M%SRp;8q)tl=)&u*LF>ImDhd7#g1PBEy}gcw z6JIBC6=Ul>*OEDE#LEB0q&wDSy>n z*pHFChFGGdBHs&`E=uC$b@;t|?iS#P`-3LC0l7m2z;+if$g7oCmTw9IJ+{ou(D;3S z=ufzvmC4@p!VeJ%GhmekOK7#UwBFLzhrT7tI7Ux2RtRac$b9#HrrqU}EAE|&h>bx9x~*M+e^t+Q@<34- zZH~%V*AW}-xRAo`@NAJB`5$syn66|bhk4j-Ki*F&{>~pnwNk*(E8_)h24}~#uo~TN zzOqh9aGrCyF>kIYa3?h-(ef?!iKMS1o`RQe`E{X+V7pbYc#M)M6<@^_IKZJyZc5g%-?gP0F>2f-Cc^UJ?Uf? z19{+HRpllroTV9nS}Y&TbvpNrNOG<&z2A*!5aXr7R*M--p&XJ)dC29otbwZBPB%k% zdHIjFF4S)`ETP`sdRd};CmGq+j0xPv2PoqQJeJ{4ECk$?LD^e%!lm_V{v8m&UzQL- zvB8i7uW{j-sNZ^Cnc%Ddtjj6nc_Sa)aU8=sZ@Fz;9X&I%7lXE`Wh~#3M$i@`R}AV$ z!jfSf{*V*A+K1x3d;a!})sNhaRjH~$xl?JrE29iCqceQFQfn>xNu|vw2KLYJvtN8> zPqC9;j5Joy6VN#M*6@f#v+Q}L4;2zwtVZ-diQNHZzrK06xvHUIE+#pDJgR|4;C1}E z>hbiFmzOlcXu6Hx*SZZJ_Y-MmJ5^f?FXI>MLhE#WQzMypxP`pDbk4^XgD8*gcz-VK z7F%ewvUm2DxnS73v%EVexDR69)y0-D=q8DM#$Yzz3`4a2gjjrV)_O@Jn%Q4~hXGlY ziQ|7;JHp36%(#sQ2T7MP43YKuLJc=HeynpEHaAsL-N?)s_^61VntV5M5*~iIzdviF z8VTrRkxHr&K{M8vfQa$dTc_?7%09g7Y=wc)tbvWX2$PAn+i6)PeSX_i*#Rzt<;D8L zks>c4e<8ywtJIy%&0EwVn{kql^{8pyI4WnP|FcI5T8OpY6sUB}F)F7nld7$Y%HJsJ ztO`JB4VV@$VlxOj>k!tMs~~zWg}?yku;HbqJ>S0nEH`JYuT%`<7RqLczMlNXrHeP_ zq<9E8@NZm=>2E_g5-DK#(9u87sGNO>#3HdeW_#NkYz2(-VjG(D@FPu#iYi4B-ipeH zzY{y374ixW6LwPBO*kO(tWL|B=xk#H!Q^P|m4lX5CAIY!O^2)c2;KqXfm^36uK99j zTu(`1KX=kqjsKI+9l`tiF>{u@38~55*@M;9Vj^Es1l}|)p?iRg3`iumxEk^Gl8MM; zW9>N0Ve=TMksrc3cZ+Dr3QDv?$?Tu}In`&839&tV&$T*b(Ur{B$)C1J5qn#vh7^1I zA{xohma--+Qa^G^oI5dI{eR}+SG8whsj3w^!VNqcqH2P<8U2SR=D?=9RmD2a2@b+3 z+zy=?uU}SrOzX_LlmkOm+Z?6cPp%w)Z20bqyJLT3$8-AqF)`Eq`EEo(=cHv*qNpR6 z&%t@*)p!%JczBiUUD?ZKKl(9ZoT6f<9J@Y9Xu5ZRK0s-(j_Bi5p!v4YcsMgLW;&7# zPIG>M39!$amG4|w8%s?{NXY+^u^$-WQU)&8IsYl&DL2cM)K8^SLmQN8mJ`#;0_NL* zVaN7HIN9D~@jb?&24Ekrb+pB3v8S6UROaM%fhpTskyb^4IgJt^(2yEjp67a^%?oTp`JYeDz-uc&_Q< z$+H$Xn>_lh-euDA=_-P-GDNT!S+aaj4ECh4@QKy=e)>LO za$*Q*aMe-n2I-pl%A8l$g?7-}5pe*}gGhSB*GxE{9kISS=EwnEOD$ zT1dDk)fYl8$WA46D80h;?}cA*L*8_9(?w>Ro@}#j%I#XN(>Fsb?d-Tdb*h)T@s$u6 zk(0F;uFz|yJBW;_UB@3@*UQ~qt(g)Q5{j9Yml${~S2{S@d%WHHtNcx4l7BIeN61UQ z=q8TWJSj2mnn7312iu0UCX??`-}HBzzFaePKM|dOj<@OoDr}kqL~J3x>+&x04LlK0 znkpqxXUQ8Alk%CGZ{c#?I!%Upnm2Vd#lfslS8^aOTZ7Zi<)Pth|2WW+HsmYQ4kTB~ z2Vq8oJ{ci7kdM!R-t^1xQ%;%OYe?f|mNkA2Yyw;3b-q=XqVkaIuXvR^JNUUEph;+% zxd$0op1MxT8|djjM3%eU+3=MUFPuNkVlG)yA|b4P(TcqW*THY9KhelC6bg1o?#J>3 zM!x(|1^FlcGDnL3cU~qx7P)qGw0qeZ@2G6POfpk>vmSwPT^_1M$JkTsfkH5q&%!6`2@^yBE}zz>BZ?IqW=L4GIpzg3fvFfU+iYavUAw6~0+$UV!>7Hqu^iqGSI7*F6F{S#pW2z~5`s?6fv?9;xjZzIP^Lbs+7Zaj z$SOz1G3`$t2igOkTr6($nU^UZrS0t|;S`;1RyhB#u~NNoU@`nJcxviV&hr2RSgsAn zCrH@(;a0GZEOLo}H>GE2OphQNAxw27jr6b%KVW{FCMUh@vjUxC?&u1X<=3M$J4~w^ zjLcHt*_vJje7F2K;Iy9|+T(ONy4q2|qFpJYoJMn2ZRAMkYkJEk(wTAnb447O#fN;y z3hg7xjVIqjViPNfZjFRd(}_d-BRUY>8C2>fE&3MHU`9Fpt1Xirm1f*Ug}wZnq*2VH zqg9Vp6{)b&Ec)jXf$N>*DNjawm*3mA?Tz63W?a0-eoFNDT;Hcrr=#*E(cV&G!ka8!JKq}sT&aA8;c&91nxmPP^&$(Fz9sd+O`=3yF zbfFf1`sCU4{osoTP_Re$5J$+>p~PR$woykNb7D~$k9gC6MT2vk^${#S=ooocQPI>Z z6aOx;s3@hz34cu6T#a_P`6T{dmztLUNA(}i4qkUsfeuM0%amw?i%K8!qnnx%i(JTsy3b>HMYnZ(3yf}S?}cgMT!sxcR2!o3M+ z;JRk)GK@`yQ&)K{zNFskXe^}Ejy{TF0%E<9>fygH4FIs4SdS`&xbALjXP3L#jYg0G zI~lM<${BPG*#Ju(;2NW5dtAJ?f3UHbAx*G~ZJ`9==UuUPl!~NC{#G{g7GZaXrt;Ws zm9I96Sr?YA9tDPke$b8zV_Mp(i3z%)g5z<7Hz73*q z3-IXQNq7$-?BvhxgkG^98_8Ry-F^hx#p4+YMi|moU~7S?P=?X!Ok~u>)%3kVx-$y9 zBi`6ePfWC-Nb{t2l}rFtXR@*Hh9dv)ptdkYk#b*{gsk`eZsh^$9RlsmA65hZtldR& z#27C7B8p-b4)+6FDPS;1v_sQ6ysv#$)A0U}sVRyX7wNMQnVTDHVWwJSnCu-9b(>FP ze`k~eY<=@U1R&Bk!0=#yU!^-ST?#X37A&^`S6r6}`AuqGV@5Phon8oU$R_WsW~ODi zbh{-&QACy9DQL<68h|2~akPAr&ogo$0$5*_S)IHDxrBs-Y?dkZ_AR!p^BGwoulSOd z4kNwa&}aRUd2V#;Z^%i95$^U+xOY9_^3wrQZu`PUsYZ6!Ug#jBvz)=aGJ=;K%vg-s zjaSoiz}D;HB2}(;s9iCCCT*LdtG1_d9%#kfFFD1DFu<=W)F9X7dy{n77%=^5kpT}Z zR0h5~vGIP2ed0$q%|%@;oqK`ffi8*=FQ7f9oM8K#7a$j(4$uaiOUqcJL8bbz_br1Z zET$r?+9GH_h5A=q=@kqWUl_P44gdTC}HB?W13S8f~Mi~hXc z^{p-yW1D=xaUNMZ!Y>9|BNsYzBL|g+2SCb5*D|Sk6vO3z_Kx!$S{+dE>-TK(A{nOf zjkYCX?INFtxpk63-0iU`I#2J3eG&jX#g4*2#NcLb$0lvNGLvlM6}$)vuG zrE7qH*V2n|tM^Nj2!eNtQVZ?esmnEe*@ebg20h49qW!T*X>3q9wT7dR@2otF)4svl zSHJq3Kk;9klxt+2W!OW^V5mKafV0#ux-Ch?AP2Db&V}65aMI``?rh>IQ7`!aB%c;^B&QM) z(#yczD(i^5aKzx|v`c3#(s06UD{ksq2*L4(vSO3y&uFlt4HGTnPxqcq6->M& zs5Wt`x z*=#1sm;T_A8uFTQPjI(^K%%i|cxTMymC=(TDe!ae#j+(%b@Dj;i(ZXxIwyNh z^ppRkXc36yoes-u;Le}iKM#DKm<1qB{jNU#J2DY{$=5;V$=~;a$g85$(TOY~Ke!gq zD{JiwO1Ypr6KzQM_~bH`8a^B@gDy{w4%UH<*c+DLE-UhIgZ#=Z$$%t-!|;TaK_z8X z)!e$Nzs)xpKqEaNlCel{yy^Z~m`L5*j01{V&Vx>`Zu+Zujy6)VLKIXBL&wc6mNtE( zmR!57Dw`OlC ztEeco5)OsfuynwQXRPb#ntQl{*Eb4T3_;M?|Vct*YxSNJn+{d_;Jd~-)+ zHQ+PMlQRwuIqXDE7&ICVDr6fEgjJpr*&P9U7mg+n6Ize=?=BLO{;cEQo15A6`uz1R0+^Q4}wFkc3|*Ui^{!lcBuq&H}WQP?*Pf~q+dGODJD91A8bS;CagylO7q~V zbHy58%1OGjATJ5HnP)?OvGK|Ym_yWf0P)IGW+6;Mxzya|)4RNDmVCEGu9{8OUora$ z!`bUea%;ZUEbbO$C1#kxPWc=2`YF4sGqMMjjNC2tSU&cDaI?D}C-6Eq_H2s~+_?5y zd|%0qWgnD!8dQ2({%a^C=t`U0Z%~w!A8$h76w6RPqI25QGXDKVUP$PhPrQ}g^UW({ zU>>q9^$MMg=uO(Tw=olb%c&24+mv(bU0(IUapJz05iw7+(^~IF8MRQ(a%}BBWbs{1R)4Tp$&2Eyum6N0Xz~o|R3a^l&)xX@q zmE%^UHZ_Aww%x;TiEp3jikk-7IP0H@ z*ii&|5|j<(d%v*caEgqU#YN_e%Z_c8VPAYr&40ra8T4fBzOHOF?xgT;&A^y&M~8U< zqn$kuzL5^bGuCHX0Ta95gEHtNW$SHtPr9cf+AyZV^#7UYM+ju@^=~ zk=ZmoN?udYt;aq#iM0W12Rah*bJ6OqXvk@mJfdb*JwB9osf2!)UkRwn`r zBR0zFKG5Zad@HZ>JIOt-SuaR(yHPqHnLF~jelJi7BIUK-E}zd6G`dV>GQ8k{3GKX% zk@gpaS+7?QvxuECaG$2?dcaQn14l)(gx?0SyX% z#QM$aWd;qi=)Dp@J;pmj4gFOqPn-(by~=#R)23ZpRVxjAP_Xd6=gMJ)!!=YX)%th7 z=b)SxkhN)9&LU%z2zBDP;&03JYMz*MkPNmmv;HQ&CTYMNS?8g@qPcze6?=)r8C|u% zyW3fav{C(Gnq>JCfZ&+9Y2G#5G@j_>)~ZUiOa5aGj}9Q~TjX)d?G{2};fjyk$zM3b z`cFh-TrAkCZ`IOgynA49#q+? z3cAwYPUeP+w+&Z@yL2sX%ajyozSgV6g^V5^wjL`0e}cU+ z_wm`$R&49>Ols6m1l2lwZ->bk3mEtv-V}`+-1H{^z-(|+#$bJE!I|-r5McrXmpM(K zOQ9aJw9U-UMvT4)c(~ug_u&4eOZm=jtDzyPlmeyz-n`HI;)zfiz`H1O^|bRwb<#&%gqKw)49Rn8=$e0Rn29>-Fn%>Xb>9TTqzNeCOUj7 zflD-d`8P*TSY>_IcPXyfbe)phH?-o4S(#%y5vtoElLJ{^n7oRwZ$GVfy=AH~+v}+) zvaTC*{)yFvw;Cu7?!e~c+?k`gEAqV#4xGXpzejTGCP854+Vl4I9>lyI_PGxF1J}uc z432OL&YD-voJAjrTq54ByCVyApg9P;<&0~OO|~gc9mCq9Il3>&-bM8!*d_euj+4_E zOC4t-E*0>u3y8-QJR!X`X8HK?GV5B$6WR2#{Ta&H{&lU8-R3+C8&tFdZsycd`K9Z8 z>m~mMUd2UxR8bx9Z@d!Ca`WrcU6wBO$7-x?E$hD!?6S8rQ^)rU@#>ncT<|qVE74Zw zTw=HfYahpb174IcEvdqG zZ|^e!lxFNdDr3D}j=SAhHzFde=eFvfs0*1H7c~Bnq@z47-1YSPk-k%njtauUY9EH1 zDg(yG>bgb^h{g&mDJ8E$b*Ib0(%fZJ^U0DX7x@+1)4Q;w*O4%uISMT;54FX*$ z{|4XgA06qKSX_Hr>_(C>{k?RHRfD2UBZk-*44=Ydfr8YX6%~;EfbWFU)!Qs>i{7@L zqmv@D8diYhyfX{V6WL;}MhKL(FTTB%q@4j%zOS<2+u^4{4Q^}ZZd=<15&I%`FCZn3`<=mA!= zG&)E^R5Z+$8>G(_qZX6?+tXE@dx}>Bdmh~FPl5##3-zSJT(XW64b3vGRWc^Ze(N{Q zD0YBO+Fj(j>2*I>&h6cqEpsogm#lvSFmtF*+=g}!bwN`U)N zCVH{4#P&1YzVhrKyF^%{GucwjU34Wo(fxjpqPDp%R#7*+JA(3~Mpo8s;Ky2{q>c6a zREL37!qwALL6e^RQ_XbI*vGgL)QbsSSOA15pn%+nkZVKftyq6d+nWEWNGpB$uCdLH zywFZaL5EqG9G=>H(9M6IBOP=;u+JT3s`Yq*D2aM-g>T(GC4$Y9`F9|C949_E#!T}w zgz>(9O48yL>2$QSlBKJB^y3_H--&#R{a@`flOQK6<>YidNg`jd(F*A5i z#Cu*3$t-)+8i9G8o9hSFZ$`&hUryRsu?}n+iS>_+txhi!(E;* z_`lKp!M>$%QOfM%=BA@032^Z>MSZ11ta)#5_B^V+i!??_$-IB$<>wE@gv&z`u}SS& zTcqW@mj@dgZ=8)fQZ6;>Afpbb7l(HbR$UBEtLGd5tgPIT1SYd@XEdN2EC0n(OFdo3 z+7GTah~ua5EId9{KOzofQYb^{KlRr{`r@H+kpg`Xt_+X0{pc=)h0zgp^+$vJwlj?= z9x#3<{r3vTw-dFxre8F*dnl6yg0nk;dNws<+{78c6#uKgQ7Y_S;KLEO%L1@nb#*;n zRDCyWcT+|%?XbSoWztu&Dl;ZtK6w_7s`h)vc@q7U2~33@3owIwedeR6B+hR?A|dfD z(zorTYC#TVc=B16@h^#!11BzdyqS);7zXk%@NE%aFLPXKQo*pg+g(&g8QhpR_XW5) z)6avDE6QJJf(n%mrTlIK?y|C)PjB-?MCBURVlEo*^t_PQIe#8i8aH-nEWCigu)BC5 zKll1<-w=R+5@Cj4ZL;UR7TU*OR>qY2QE`_J|)!-RpnW{h=I$+|! zlS7je} znXSh=(#MNNq=2dMmeS_bL3w@6srs>iYqZ(Tk0PFAaoh`V|4)53ChjIaK!}*=gsI=2 zn&M8FJ@6HKJN@^L7pvu+(7oO6Djh6+=Rz}O**#7_MP{r1!>pqMga<~;rL_p3tr)3t zn3eDA^CFcQaG3OKZ0JbQ>XyZZ-#+$(j^y(G3@QKE>tKLPyl>7Bm=Rjf5)XTwA)v+H z7C4fhs37iu_j5CL@VY@Yea?8d+_x(WVSWF$V-bWbI2|)5x#xJMp`-oNu&J-36T3jX zVaa5T-|xlukkVq&j^^WEM0CRirv#9}Y zahJg&ugL~VE3d`n9JGsd&@qHdcZ?6p6Ow-3Rkyf~-RzmvoS-9wze7#Z1+(0Bo>nPN znOo@TB6S?#@q+JW(J{PgdNr0+*VE`Jh(P7%RqrlUsH5%G7o+xsV?byd-4tc{KN!xc z_@5lc9$KQb=tS>wLVBf~jM@oe3fua3x95|hJYQw^hrsFmm!dWCw72@$p}#r((TRcK z<=<54+c!nea?!KUGMyElXBE4Lvcun4>#_W4@QVe#ZR?Pzs+!W~MY-2i$9gkxy#0~$ z@HYiEXxEADStv5DAjhyv$5>bEmYReG!7omto|zYrASl~3Aps8NR@9x-yt3suWP%Kc zSBqP2Udf~8-_ckJs?KrjXBCfjLZ!fCMb0J|>_lkB1zTBx7T`czaD z6|S-HenXL}EZ(0Pc--)3KBOf|el_tUvne6p&;IvFy|7T^&DYLA6m=bung5zcavzv~ zxJk;>>|`uh|B;H0{w(e9@7nt+2&Fjapha;{&nyhqpYs_FLi~3zl--*4qb~C3;Ony- zj@>Q|Nt_NnCF}O2s=llHt9rzx@2H%_g|+a&h59<%DXDk#ghgZDRa+&Y@`*SBvngFe zb;6mowWA*1ss+5)qXI?{4@1IwoC$C2?I56xnF{+To5bp=We?huB+mOzk}u6<5nXbNX4wA8Wxf-W>Cr zaZj(CTHh7alO#4tm7eeaAtMv&rlpn5YvOCqo$xN>kGV4QH62h96e%${uuSk4^d>p^ zM7y`|&Yuriw(h(7qOs8q6{aVm6G43;6S9XZ@>JY% zyi3DLVKn+@4(FF%@@36urJ}FTeAD-kI+iQ+_NcdVOMGCS@QyK6inl2*jkOStjkTzRcV_qjw|D(!5=(cRuHm~R z=D6o0QJ{BH5*{deyYnkH?-JJ`lo|_iU&>#eWsik+Ofo{`A=Q?d@$R^1c6aM?Jzy&L zgav|fQCNL*Es#tn2(yVN_6BN#!|q8q@Qs!AJ%&?;^#0HH_yZ?y*x33_;5?mvx?>;N+8S zP&RcD9gyI?Jk!z!eQe>P-P2&pd5+BeLT$T8w!z(0k9-^6)p2%M1}NfdnAVRGq7~R2aeGI<3C{}@XN*5fY3v5M3lNQJGNm&(&#WwjePszx3MPrWTnOZMvd9 zBOJ^MxBH0POYiKzoC z+F*Gx_Ke+QZPCy3XJsKh#K?u9c=S3O;*Jc`$w`BajSuHaZZ*nFzy zqN{caZ_fK#oTu9)S-r;xJ*jf`+EtmS2A-pNIUOp2c05(EQpbF?v*v-X%bxo6gza)Y zaCdPna}dQ@o|m+_PacT}bcOYNSQa+RJg{nIaZSMKqeRasvkg16vs%ZpC1-`2_7_j7&b1*w6T)+eOdeKMmB+2V*)p?F5 zd;EgYz>OJt6rOANMelt8WiKp5p7FNLDoy$%e9#w?psRoH@;<1nI54}By!w|dMbkLO znk=`Ct@4|D{@(}J%g*`@mAOGTv6ayh&8_yWsb0&C3feU0U}M?w`Z0}iv>bIP+_tc@ zN8KEpB{42Op31_0orq+HDhXQP8g{*pvwbCCSde?xz{n48faMTyjdQB0u8Xwet5Ov2 zxUsU8WzYc{tz*^Tvj357H*x1;-I5DziFaQh2gW*NYMzXKJgN(; zkmKD^kteR$zL^&=P`B`ajh%q|Ok4A*Z%(rWIb(FNJ$nM}h2pGi@Jei=oXpb)iG7F! zki@+?mJh?qu|}vTJu6x*%3fk0wf)^C71ugJ=2xIMRSsdaND?y_jQa&=Sqwcb5 z8V=&dYV5vfbP0Q$0hu@lGH~bX2hU3V8qeu378U>ahz%4tw_T6wfwNZTJP|+mc{ZkU ztk6zl{jYXEsK28N%oWsl_wT8$%Yk=JD9V~WH}$v}X1<{!<5QV4#-ibYdi3nvX37T? z_8)k+g=Iz5K`dQ^3)y3R`$bjRTj;%BThl38mFq_%?VNE$0+19X9mC6q+?5MbIhUG^ z^tj>w1P1FpJ$}2e0qE>*j?=0`o?U>Elnd}wP-@qUFU-cRJfhfBq@|05)_9zIpG8n! zv@HLvytYTC9Ibj6I#F=B1iA{6mlu9}v9TxYzhZ;)r_JpoYz&sHy7+4U^22$ z_OxEr@RL_f8d{@%;y#dB@3{sl73u%dZ~Jg2J-d)%Z^RQsu8*;Pm=x3lzYCL!EXfA{ zgnN;4Jq#KY*2Z3x6U%FRTW`2g&Q1k6A(fKLndL=C0z^Nt4-ys3cy8|?m-oTq>Egiu z6}js4PsX;*Fl{dNyNc<>#*BKw)<4!{tL&*wREr>Yg6YK0))es_?zg_q17B7gxLE=; zQTQ*}&VSBdb)^!u%!-DFvUzAZZS?E~*nqeKMP#F$YYjkL>favQ%{OB;DPorur5ZX_ z?)0M!=6oPTl>KOP0G#V^TClab1)O&$bEMNpq9K@8JDSAsge15;WZW@YClKyrzIoSnCD}=fW;dO+~E?YfWW8v@iLafiH zo9pX6@g#Efle>m&*{hEpLjw~Qz4K~yRtUW?u{S&gLEjLD4H>4pwl8wluD)>;Ao#i2 zoWB1u*G*Qvj=-{_DPuv?kU9-^zGz`yw|XOBo#5=ni9K)PlcI&iP5k(#n9AO8VZG5* zwyAG5BEn1*IX%F+CUU0l^*~>0lkC@J#S`H4h=A#Q?dPU9qF>FT9X>w)%dmR;V30HV z_*rZW!=7O#^4C)bYHH?o4R4C4D!j5U9dZ%#c>P6od;~!&`hYvJel@cuwUj@t zY`_|@-5Sw-Z@&we3inT#U0jc1@`whtCUOuc@4F{PApcsryJe0~dVX2Ar7#ZPF}Ic(3FUy|=0>V~hM+kroShXZ z{;7YDu0(9H8ClUpAAXgp&VfS_R_-p^`Zuv!7tJLx`6B_jYx6GW>*}bEm(TtpvV0>n zhpP&vB(yIw$$B-ogv+`vS0m&B_+IKsr_yiBHLX`H(mbL7$A*&pFe?VBZ_B(GGm=A{JWe~x-FpA~p^3d zwgX}(6YcU4jXNwRD8 z%%cmT>m~E*eJ&TL!u}IhU^oBrhw~hl<8K7idAnaNh(O>;&(3wP{LujV%J6v+qtdol zPhZ5=YbaX1@kvcvcUL!I8xm+-Xjh>+_SQL{2M3iYiREy_;;aKPN|8zz#Bn39J)|To zN~K2itgTQ3x{ji#&!Z?mZ0X2Cqo~EVwu<|F7PN@uPl|6bct~5bK->#Jh-5g!o zfssZ3WV83Che2+4qn1*j8ggXmD@_|F8G`EXs1f z>px1eI5*NcbP;0HGF&|krb~wL7K7rkASjajzM-S3f**AHQ=-hLoLjuN#%t`3Hu|rC zz`P>S?{YG&^XaZ(uQ&x2L1R!u&X3C+<^knt7JL7m0ks{{tDvVGP_I0m$kx;iV)-yw z^houn_n7Iqky3GGKfu6sK07g7%EjK5cA(IkGPxPNLWE*iIisI)E-O8%S+^5jH)K3& zi6HbWAOE8q;eQ4bl=JH0+%4@b!rvB1qqWag#)0RObg(uVxu{8VN&~eeBP8MD5ztbU66#hWvsP(JqK(b0B7 zNaP7V+F01d?`foyoE{Rm3;{*rSOB5ekw#licIw%Cw+><2>UxYTt}BmL87!e%_Q3m| z*=6mhmdn=DGKTGR2CWVSy2ItnHM4u<`m}u3-1P8pd6Yi68Qxqypnok;-TczO`akA3 zI~SA`FPiF-vkc3>1gT&oYu)TH0NwU%U#HK?zKXvcOzW)Xw*JDH74L144wszN1TkLk ziwx4@Jap6DB_6CkC_tb(zB88Zq4_$#%X~n%_;PRFLr~ds5>LYG$Z?_9K z&YEYMR`+}nW9^@gwMJpjU8<_L|3W>Q{pfM{=vyhGztY=Lph>YPSu0 zq`IRW2K-%4C@ZAq+TL`*d(`AEFf{3u0xNz`eNL#V5>T9&Y(r3`cA;NG9(MEda-^e! zgoR>GtmDq#+uuD*a?b69qORN$-SV~BMHjj&3Z_PTH+4HWh-j)->n3g*> z+Ee@tXmmI7YaK@HA(x;))%9DIF*zLqEVzuNvGvofMimAB)iN?&DbqRY0_Id6gc z!AhXDf$Jc@Z&Y)%K)UWP=)J33DxhMPT-NBd!do&Eeg@bJCZ~I3(_<0qdN5aX+^oES z!<1?DU#04*m+~~U9V}$wq{*bD6>yannJu{ce-xdIKhxhI$LEq(iAlax7%Pe*_j|}C z_mW#Rw-9A=zuzS>mx$z+OXLzYQa0IKBTJb3Cd-ywM#C_dnDP7k{((K%^~H$ju?`wmm-gIxO=C<_hB~e>lzI&REgH?#8}SnAjPP?@Z?B z!e1qsXceJAK2|-IxWB*gby=*N28}Cd4t36-^;S}-?aZhmE-5@QkoR?2h!YvY+xlu& zjee_Vu;LG!JLYo^)>6v0pAa#q=G}$Zh=?zD#&(+N3McW*@(0)Xt$wf0tOiAPI4=aA zHJ0Fe%KX(c5a5_6!jhE>;c66qR;*k!VUucRWkB;G#8Q1w+DC`S5#IK4+ia|#jJkwF zCv#V~#`m5Bw+^+;cF;LPN?m!7{8v-ow|U1W!%sBXEyK~}$!35^Yj=^)a+IotYAPaw zy%ZyBgYisl_uy8233vQ_ZTl?i=C_4VI$m5LKQyfV9DmBNG3~svGew^D&5Ih!;EMAO z6{*Ql6G(N&6Hxs@k|j_X*(z2ExZ6qRvKfY|n4{wIo>!lkNJH%ZWyDwHPe%J)n$ z^^qo_X?~FefB*d4^k8{XP(k(Vx;1@oGlB}FQ0lRl^5cqKT*^GC6GU0k#tEGK=Eq2t^ykotAVO9kcGwX3N6 z0za=l_w*MSkIhM}w)RmZFXi)qp!o0}vHk6#Rr&AY(o@&3-FMd~q|_S*^|*{{aJwH( z070Fky=v~G(QVAX1=qB*ogdur;zZt0Fk*d7$p)>LnLOm=QeDC+=0EuOx_l={V|BM# zxUCKTmHu|{TuZmbarm)$SQVxwj zSm!@vHylPe(*kv>D~7I82|Kiu6qD0pdvTXPtFSiaN%WN!Cc9>A7l|Z13l!~*YaI~Y zaCrq5i+}Oz{&JQh5J7P;p?^#a2VBU;f9GTQ_eJsnFgm<&%^1e};A1P(09a{!rbY*g9Qo}R^jtC?(5MYO?{LL@>ORi}?tGFQ~Z zvOJ30+hUYo!Z-3rFJLg<*|nvZ{20a2Q0K=ZR<=9VUi8=Va!V-@^#0NA}3^C#Q zn^YATKM!HWQ&{z5u9`IdyRKGP>yY467tR}hQ?m3bLRDd8}&5y%V;v( zp#yeRRk@l^% zid(>^&xoMdKzH!*s17Y8g^IJ@LU~>hEA4Be4}3+Tbxqe^x7^u09a!S8{p>KTCB)gk z&h(Pa8PP^VZW=`38jkJrW$th5`DPQ-pDorF_9)O@xJ*@(LxJY2O2DS5w43Vy`Zap| zeym=aEJQ6khsn2k;p;WQ>qej##OITW9&25eV%kT@nCCwmgT2)FJbXUYU*=qm@-X;% zO_QgWpemr8-k9D9+ykBqzddAXCoq?v8|NZ1xe!pQXa*Ev0r|&|Xg%i^aTU$F)+}674NjqZQI+oKV z;($JgLTo`8Jg#BBgUvy7!kzC>fD9n=Yr>(C?9Nm#NNT)spOe}cGmX({x0)YKW4AZhIukG9L_UbDd7z@FssF!nq1MeQ&?nC&b79?)+{04F|lE@0XXNH?k*59 zw_Pt@8_F1KeLfp#(XXrU6Ug7I`_WH#{4sN|KF3+zbe*?7-4TxWj?n^!Ja-IZ;yW_% zivQ$k%WdcIjgeHzS8!6O1q{Cg(K_7QZgrI5t<3W!{}*-;qEnU`K2YCe05WaMW}5kbL~Cn<8yS0%_v%c2<`u*+ttdyY1q~y6Tq_k5nodzC z%_)6mB97ljV<7wUVWCevgayusJPjWG`-wndO@5m+rhd!Ccv|%r8Sqjxjq;?8_Om73 zZaQwri}Ixq^X01KChD_#GK&$mjM!{hAlwtY=0Yu!J+pWOW2N>S3CWPux>2vmRqzm5ZgOE{q4nX_cIG?G33m^Y>PS0uAJazX zH7z}3f5LRFTqRwR;-`ZJupjFyTFNs^!!#6V3J@DyW$1;*_HBg~8q%n|F0Pc5<>~7) z2Ylm@$0ZH3Gp6pq3e30YCi;SRYKh5M^~&zNl*W+uIdGUU)^!Z~n64s*+}+sLD&c!) z-OPR0gA~n|%w5;71kJiAAf=|l)%06OqN+&&2M9F7n1GbcC<(24fob$;ILHle`tc={ zqx|tRaNwP@@Y~NyI8#k2M`>+m{t-A5gX3svvIWRma7ChwVyeuk*H0?28ju&a>qn8! z0bO>(N%k99tv8g;Z(tk4MNpI9J!8w~2xW=)6XA-MlB7|i2}|HJv=3d3EuZOucTUCJ zcMq$q(B~Nim19n6yaO7-Vh2ZL*Nbn6H%_OnZC+`eW{bS3U&#} zqjmfxrWUBM?qR#f%>@iCPPA9F8kZX zwbi4UZB~5XB-rx;A3057*j|KB4q32{rzl8Lz<`%<=A9{r1is0F%A5P>76CzueqbjP z{mCbPZ5)2`G41G=#&gZBevabR-p*biOL|yArcu+Y)_Q<$H?|UHi-v#Bwi-i%ri8sxeu=0Rl@lSlu~hjMhY0NQ7? z>OK%?*gU!841Lk$z2b57EbHGmuRNncF?n_=0tk+vPD2?`z-b~!{+nh|+e>-cKGl$ zwf)Vbyw0}D3B!>fF0K?_St|sfGQJNn1X*8DVkL0p7p24nEC-vzPIlS#Xw$w|HJt@X z{#T29(R|u4B-5PW3>{h9S^+4m3S^AEu?KHYuEnV>n6HR5*ECV3AsAc|Tq*nIyjns~ z9}gsExxjF3bP6lKT##*Zlk)lgdz2P!6f;pgm{bt6$kCYhpU0Z+LQeVvr7JSZZeyns z)hFx(4S2iEUgq@AM1b3)+hU<9>k8t*=5#DDWiEc5x}DZPPU%klz|v?ORgfAy8?MegXv1 zVzlJhRDJUB0*;i^!tT=8Md|uMODm)B6J9=Y8YV`XH8%aF<)^$yOm4KhP-20ZBYaLl zEc^Pore8%WwBlgD!}7Yn1*x^F*9{e{(Ixm-!GEz&bl%HyoYn8E9ZyamXlG9g_%BDe zW~3p0M2aNK_e#THjr3qL(bs+q@>Q2g4*$u*Xw`@o(_*>p9hdjKoJk`-@?3Z?J~q_sh{!Te0#|y&`y#HbX_} z5b?6Icx~#m$LFH#>{8u$I0lH?Ndy6}Z~L@gKK)dzrB6_ox2-qvLK)vhp?k#s-PP5V zt+pmDozlv_t3bs^CzDn9v_piB6`A`P^Je@b?>t<%Yo8aIZV1J`L!ZUjRmxVvbz}Dw z8){Qa3H)@m6UayXnqtX;CHdv&jMFZS!ySFRhM9MZL^Cf6yoWjKB@L^O&{;Vax}w!U zz}4r6der*H^aqncCnZk{8MJo*Ieb9k=+?^mj34rRPL-U1tqeN1c{nt|?#-#s2t#*O zvbmP{Ez)in7I(s}l~V5xO41fO-U*i>V>O_?ZmljZCsB-}N`*Aw;dDnXixq5q@1OUw z%1jyCQWC6BntU#HZqrb`Yi4C@3qu&Frymt?-pH{zCizdqycUHxxIg0ojlyfC zxETY0AN)f-JxZ7Ah(bBQ?$AF~ZJE-f;a2X;{L9VOc2cB2-z5`MF;j?I=Vfkhi_6+! zk{Y)!N>7PKo}cebmaa>W8XT zfb*%R;}B0}+zihrse8XFaiplLra4m)Ryj3+oYwnb%jzYC!YN>2lxvKP!L`EHW)w7O z8!%~S(oTfP)%^bXJzcAmViGb|bkBi&w2Z5(vq)|56DpWjGK8ZL{C#`g*82lzC-Sb( z)&Teh3$KqD4WdLGy{_dT-wXn$?Np-&>_KnIx5u6D{%iF| zbING{CtwspxU6ER^f~A9h@bIVf{2Gnk|8M{u6wBzwrcp<6EzM8P;Uxrgjb_s7{`ok za76rfD}lb;68^fsI*g8HS(Op^Ye~+hb!m1mc`36T=T2jSrkB2wwN18kmDhGV{5|aW zmjN{WhP%VyzzBCzQQ^lmRUmo)vo>`_QNqm@}hyl z1#c^(VkY6pT(aqC(Gx9UNK0;VCsLnK`$%RmvRQf2$jWZI9%4XwdJSfS2#vP>Z+kOW za&^r{e~$lK(j`ts0ZGk@*t^6^w1XkoMo6G_hteW#e=;mB%B)ZGBI;}bf0t$0A5&7w zwCF9Pa{2@HnM(@F2h_uwLqKN~a2D>ZSp~=?u)Z=E8Bb~Q`JCKfEnc?5P4q7juH#-U|Ty=u!~e&lQ5#DE~~ zA2tF{>nG*>P~b(`Z-*3Q(y^<(M^*RO{mu0Gy&65Xfj;vSfyvpcx4(IQcWsZo;vavA zgR^E*pdB%602@unevdxb?9LjZx*Q(Nt24MxSsI3)WE%U^batpZ1tsU7=_spg3Gl zcM9+^^ER8+K-*BLet6v5ozl!)Bnr~A(NqH9cy>>J9 zjyVN0$PxoDfKMJPc^lbsgQ~bTza6Jjf+cjg*1L9eRLl(N?O%tVNj}gT!p~0YTtx*D^uEyCXPDkaF0X5RD2}$g=o38;S(qyVGm4{|r^k_z`_WaqWcWRSx2aa7~s0JUD znU6Dgpi*|UsGMV}v}#?_>8gLlDMNLu^PmsSuOPt$7`C1{qwoz;}2Xc@B z8yQV2fb`!B9kb}O)e{KL=1jKyWiDyJ89-2%v3YMQr|JFPnRisS94+0Cm9XKJ>~$+` z=+`>G$exasQJtTA+R$=}Lz%j8PViRB)A6Dpaefc5Te~xrD$gIzPsi*Zu>677W0my; z5+1s^9x>op=-&*)7>F@}_0yQf^L}iIvt4#>GsOAk5dp&tv7qjm{{7Am8fHFnww z^ighJL`Tq{j>=*KVdZ&w_32Yd*Yfi?zxgeegjavOQ}Htx0OZFk8ZN_8lxSE`Cj6!J zVDQ5_N4V7l%QVyO_$loTv?pkJPds(Oa}^NCwh#f&K|6mJ!^fH{U2-|x6GW-;6=Ugs>~MDBPwDPwo`6}+K0wIO>Iw^cXJ=N} zXJZfl{aa&^uG(xJtR4U_?%Y}2mnzhFjFyf_nheaqywCC}upsHiRs*~w=DLdbjWwQS z0<9)eMwGq7pz`Bi)+M;9YL=9?#~o_jJgN*)WoTd6+nc9n>Fs|&F!$4@62mopOT}PF zQ&i!kWCefb_lfGkTn!%RYmra!kEz3+ICAyQbwB}Id^GWOdx*>8+S>{-rMHEVvXuW_c(0)4^3ak^b7v7&*U<(T&kgS7HdEsp3Xq>nwC0VhWX?R>%tm@j zsm}R8pDH3T$Qxbw<_Qlp%*%oBZ2;TOVAU5cz~K{+M47XlN8cQCJDWv@BPMn+M5lbI z)W1ss!UiO3r?r&852$M$t>ds+l`5w=HhZU>aZRwIcqj!DvG^20?l2QyWq<@XJwNo{c3o=xMYW7OEUIC*bLPK zgAbxwG=y#Hdj0{{nRdqEKYUq=_ZKG$kKUI>AW#ve9~-g1xiUXbX3mG`m_Uu_lP49w z;8Fw&UOCK){L~cUcnOTbHCo$S>!uVkry*5^XKto&MR)kg%*SYyh~eshT{6t1NA|~n z?5D@gzeNFm(_~A1dZr;E60fwH`s$jOFU)z_PIbaE^O~_ObL2A<+8AZRlcde{{JUa0 zhAoQ2?>N3}`-r%2R5>*;JTTBRj_|pk{0`+%d!z|dETV62)&BvFY0WnKsiyK$7VY3p zLXhd@FwR!89Vpc<0n#U!^9=&oxubaQFP_Q`R@B5pi7pSa9ni7w5(j_9>8^=)Zd+Z^ zZhns_Y;@?0RZAb2|94H|H11ex-6dEHBz&8_I;zA%{sp)Qci@I511^KQ z)*p>lmBde6Jw5tx$uP?+uxMmFiR+xmOJ!w)3)OJ*_fR)YujUSF!5N7Cxz75G zkMW3i&k7;mhap|OVILmJeM

r0#epni?$>3uf!~Ovwr;jg&e~8a_g$dhkkC=K>PW zzoPwqXKfXR+jvux+;Pt8Kviq&#rftM!Wc=2HW{cI_w6I+!7q2KsAjiA{g^gi}jMzul+dntq`A<{*qFUGMs^dwM|0V_`drt1N- zOmsuTadD9+MgC1mxpnL6 z5S2=y)CBJJyB>@ImeG`MxAuCVwZ57uDtWj00 z>eFK4!;i=q5it<2VZ4So;^kJ*d-U_MNxM~OX+6`?FI1y=*f>dH==H;*YM{$qi3#Wb zC2*1u?x8dHt8&p-MzcoRh?^Y%0uq;f?sU-g^jgBvm2+F8btr>%MT+$Hg7%I^5{Lnq=*mCykyQzeUEVbO#oX z<{|Gp5(z0H*vOVG$vlVNZV_$Qv$5Q#Om3bh$Jam+++^0QHNqZb4OTqm&db4DWQR{Oof>Bi zBl>bIE_+$Mu2Z3j9l2OlP|G<^L|jf;(g!nR1iZU6L1fHaLE=uoZ|3xU_^iTXpS_K( z6WZarVhfRR(Xo1BwIulz@oiN6J+FKf|6aF^W+C*Cym zl~wnm!ChLYv;V9HhlC{8lR}uSc}s57t#@{njJ>uj`eZHV*+)KINA}xovU8yL55pz|WBgccexuy#D2l^M(`GHICep##|w>@X?&>istVD%O$@1 zx^ZO~8u=-7Y$mA0XQViPs>og>`5-0^>+DbJ|Mkh#F=ahVv6nQDHhVsT*L??U!7aRL z(Fk-H`*)jQQ!A<}&+}g4F2Hn;&~*?vo|}HKM{b@8xFs^lKOJ&7-wga*O72xfMC=FZ z4NS~vBuyS}XT^&`#rgUFpLyaxJ)G}r&jsVW0s6I21&tQ)@PhG3Kw~z`9ptQvPn0uO z*IbHFKAB?$8+$ABk!SH^ON&u}8)Sk&gS0Y3+T-78Hp`;yIAtSuR{*d@=_+BzB*>hg z`Z<)$8%*UVJS#Sj7f~dke0e5!JZk6J>_2VF_WGnciLWYR28$%JlX)Rcsz!zPydcfj zwyW`6!sg0`w2k3r#`PZVm8FzhG~6u1wOuDZK9iuQLwPzSuD2-i_uyxf$EQ1A8Ok9;#(o<%E<8 zvb2!uA)bYCOc7`fjpR=~_2F|}s`~#rFG&*Zith0aX1k6qPf|COhAc=~#VAOmUeYo*7w*$C>6ku)s&ZwX{ySE?k6iz$y(gC>i_pinQw zsxu)L-pHOL?y1U?Vznb0#>!u&)vB${FWWJ4DDpMp z21#yYVA*ymQN}w+b!?!2L{!FEe8aS1*#w8 z63IzW%7)dIbA0BPkLWw2xyimT`u)1h{9CmCFha_mR`O)R#_Cvhw)fEkq$${qX3BUk z{wVGKzy%ohePYNROGfIuYS1I1IS~1Qeg-gi?-mu9*PiJzvW9cndWB>*Nk}e>pX21V zH8SMB<#+kAGDD=zMp-uIems2otqFHihuhm=^G$C> zU7Ie^(0Jfe^$a4qj`a*{-6`$cjclV(W2eZb{aX2U;E~}vwZ5n}my+<&sVuz%n2C`$sLCdHiln4+YXu!x8mKt7TcH|@BAL)m^bU4*5yks zf~PuvX}yZ>qooXW9Bz{}tSkYOP>KVSpVF?$CGx*2bSDXibxexpIMuod=- zvw2%bw1j~$CntvZ{cTzl&DE8f?5$&IYi4HE*{KEHuTifuhn6ETtNmBtYuI z*8{<2A~}9ewFCmE2D3dX-M}a*+eWyU_U$7O zg+~7NtEN8H|G8gCo~q0nJY9$sIkvMS;c}IsnIE@USTc?c9sox=|0nD;sM-5P`SP*C z!jo5ekzh?>;TTjXDKwH{2^yGeG$osXQD7!t@?{E(yz&Dn8NmPxU_ALK^SJ{VVk>tJ z;XAR$1Db8^?Kj6nSaI#smB%zUbPImc$-#14cxc=&%;xrXT(~YY)9}YRj^^rqwZnh2 zw<0ZY-!S9YD|fiD(HbPXj#xG@v~&!Zh93S4CgF?dn88dw}5Ay_Q4 z{1!*^Ah)aLi%Z3IUlj#SxpZ_Cm}V$)^FPuBdcxujVrA-g?5~^n&zz5C2Aj$JW$_v9 z!CejB>^=5Qpa?jY@H#ED++%^bu@qv0Z1(hq!HoZ zu7SxhtX~quBj0CxIs4tjLh;dGhLyOk7vV_)pYM!0$Z8|3237v?c|+3;WUA;|z)MqKL z`T1}Vfjx|di)EiTZ6;ysOCBn0E&Dg$nW#qf{g`E(@WQsa@^uH` z3C6pQ5f&!E}Yk$OwY6Uc3s?^kMI%^%u^_8v^apD`wtZT} z8N3>!duFEf18QPyiI+!;mSb@6Lu#p>zj91wt_Uo+JIa=d!Mn>{A2nOl3$9|snK4% zlxT((8B^6d4@q3U1X8?x=FgYcS{PN5O`&tG5Qs+3Fs;gy_DYZsX~~VWLSvf#3*)e6 z=_;*nMV+Yu96Yxe`R|c!?HwI$9q&0c59V*hJ1vk|{pmY12L}gXIB^1vg#(DGhhlxe z7;t`m`^Iu_VAL0Fg~_tR%`xw@#86#oNpq8q?zBHJ^ImeN6=3V5TU!60oQq@O8mKP8 zGJtS=HKv{7mmCltXWAn1iD8gYp7*z)uw~p1ah*qXoTvqDJz-x|G}n3ry0fOv8Aa>U zWR#pJE$|>|(^5qxK*Qo}lgh02XQrEOuHL84uO12rfKp{aB6Ubr@579qOR!ZhMg2!M z`Wygo@qNquxH*NWc{SLo5B|zVKlI^NlPt@tyq0L=QxE&vb9^*wB-$iNS3^2YvH$_5 zG;$enxtUlR^?2Rj1HQ0tox|_Ud{DLDGcnh{5~psCN(51~89z_&@Cl97+Zwq)2*T;_ za8WwC19__^u6d>EXV{qwp+yq;-Smzx*o;4JD1DlUtSi{LxpU?D4hjv()p$(@Ula?2 z^m!f#SDbLM!)aW4B;Xc=_G}*>mwWf!bat9h3GDSU+Qc`kn@$j z?mD`83B5yc(|!iZ)y`FnZpVo!&eXRx{3+o0Zryfb3hsMEze^?JXtnlL)nxsiH-8MT zXp-u@h@oPz)E_44eNAfF(l}of@%S3YaW#-pde{5EpROP$Lq}NprU=`NB4VF)2y=mG_CYMe?upGQ`h89*fqn;c&;Glfgg6cL=Q6rGX~~8U5J${ydw5 z6_(yc(XpkaC1s^mhqrWAXnXxts*BJTFj*A#T?x?@&upXXD7JO9PW#=tu_2jIZf9&l ze6UzepFa+|aYNsESqDeUe;73RZC#I@U(-~7b*KTYav_lNe0Fo})@}PkHelN3*JH=% zmb!Gr4WTm(N%#Pr5^y|Et^AjOvt`VF7HefiiNCu~bLu}0v71u;%~ThgI>|2`hinGr zv+2q5=(t_r07vN-uVyY!>ct+!wCQ|?2ONYsmUie}{_ZBqsBzN@X=rc&vIW|qnKjHA z?u@bZ)AI!uj*eOY-@YwI{b@NPRmU2K(|!)t#n77bQ<&?(jL32M4~!WhVm}4o0^EfO zHG($Ho{^D&`%Sb-KqRBPk+}bFvs=eCNal4VdF!l!j_3`AV*md6u-(nw&n^<%6wSkp zm1t71Ex)2;ni#cZIc(bQIyFM>l`;fLe;@+xE1xU!JI zjKZO4IM#3|sh@?d1pcUzD6Q!>-44!?^%-V&4OFJ|(|zS5g0KGeC}PQ4pMwoaui;2t z|9;Hiea<>V(S*i4m5|u6x~VYU&lRW23ln4naS$N%SPI{h#Kb%)SE;RX2Qgx&PHC!S z2cC75^YI1&p8nR^{qluGZp00Jv>oVzv_YOMSj3(CDawz>tAcVAcD$>>Lcm**$B5bW z1Wq{x^YtFAl}NNV?s7QNDT<@70E6_Q8r;+mAmahF5}@&`UVUXfLg(v=_6`?;U(?-U z+Daec7yLa%@Nr=I4Xk|i4+BR+R+(<&XHx!-=RJ6>-r+{dfd-r2ae$wXdXJt%L1tA> zcC8f17!&(fqP|AkdeD;F@7Q{MXo0;jyl79PD_y+<+f;;!RP0Wc&*nD+yr0?ikVsrX zA@~D*x%Cjx(d|j*Ik~#Zx8#-Su`kpyb>80Qy^@o10Ksus=X+jOO@WBwnov`g_6Q$Y zb0bPLZ670#nBs3!;pogsOKtfr+W6@`dgU>SydV{T>&SEFNo3w>`s z4CgElAorPDe}^qkXJb~>Eb&hLfI_Oh?Rnc(oLX(DXRM}mLB{QRHRbmks7+w0#9o1 z$rE;e-;;u#A?@tYqe_zF247>6y(E*pi{N8&RZfWd(6ka&3z=|dDmNnAPn2UR3is_G zA}#` zLe46<3M&U1$)R_(;`jD9cb9C1-ET$~((-8}x3fCYv|mbBrGyO(3~P_`5?DF?fPN*L zIemts>jUN#kOiCeV;uUU=(TL(|5adaG{fabu07|{>{QO|bt_#hXF@ywGbw$FG`gVw ztKnL$gYt(ex&EmvLC!S03qRXZz4Yg{oBsh4`&Ga+2oPo6MB$gqwQji2?P5hZo_d7P zed$pi_(#|i=GhAQmaA`L=5@49Mu)XYPUqe2$v^Y&S4(T(o{m(y@0(a@xvKD7lUD30 zvM1-6^dBeX84#Jyn%!NAI!VzMu0)H>>I_ZAF>10!40>j#Lt=4-T|xb%Nc;N|I~#Fk zjy6H}9~)5HJ48O+jsbM4w>z(pS|<+oTn{;Uy!$lg55KTiLcTcqrCaJ-R$HE>xcF5e zWost^TH`JHpIH4r^IBYQIvt)-DPs`4L%0b}O~5cvcP{rwk)zBsBs<*q)T*K9Ho@93 z04BFL5^!;q%QlxudVJFIJDB5koowNC z@jbrbx^eaoH~n7AXnm86d!X=^tL1gUd~N6j|hZ$MrV|GG*lxY9*~y~ z=U{2aL1z=+!6cL;`9MG+&1GQ2HGG55)|{srQHOO^wtE%OQSj#7JA^N~>G5%-gcS&k zboA@tE8!?oRZwxy$$N2WxjB&b;;xwFXT}Zk*gaiHpzWFDz9aQj#L>wW$ZtRXt50fG zURS?GR?3}IQ8fAr(T1JQmPUY(mD14bG6o9D1_~ZSR!zAXnDB%rV8_AxY1QHaaAr2| z^`J%~_P71>>l~7=CO+?2x*wNLFjy>*5tfsZe6u5vy{!{KQOmw@81v+m;o!;eXGh9w zB@_ig2|(XuYMJTi4b%2NCK|N(J$(AEMXDlbQfuf|nXVOqDo^tKiFKRAGEf+a9z12}Z?hxCqcH!$VA z2hC&g-!3aX{NLKYrTO{7HdcQ8?hv)KgP528*WvH(fi6v{gx~AI;%6l61+4~$BlaXD z&!}YBq9*kB-2ScXP@j|Cw95GK04x&6jA?Na7l%ecE$5kIG^gfBXOgcr^rr4MI#mWM zpD}w^Ha_-XF#B9GlFxIvI|R6y-LqvYMk~=*D$wa}Wa98M#t^m2jEbM0jc}BYRkxgN zTrfx0%sv1C892yc{@Zr>o|2VrivrghRRtcnEIcc;0(cb*#mR1`yqQX$>6(`E;(XzH zFBqSZ^ok~ouLbli@odUkKse3Nu6E9>si-a-{KMFB>_=zU5fz);M_xKXI0}iLo+-K! zAW9cg@?B~Lhu}gn`>5-^_v1M}LyAceZSDNs0R`37=-5eh)etd7nq9%qBRrMzVYrsz zl>1&4Cr|IylP_BlG?fQExwsU#6X5Tc{}?9ZY~*3&L;9ltx!jAP8fA%sDUA&Kjic80 zeuurbm+(K^4QY*!h>49|#UC83O3t@_@Bxaq z@Sxw+yk;B6wV4if!vR16k*)f0;FX+&gC!-Ns3SgmC);nc3Kf5RI8XsH>gmiIO$Ji} zWl(7fqtol+KigW{-t)JK1Pk3Ti);=1%4_E=q&krtToeD>+x9!UP1l<U>Fu;t)|BmgYKEnsg? z`)(PYlv%s!>}p|{6t{TH%+5mYK9Dq!T%X684A^LOGX#~N&t9;i0Lp^q{yd-r$an+_ z(-19~#`uD6BmkkTxXHVY{UFrB*!rXD8)+Gu6WuD1^O5mI)$mUzL=zdTz*pwFy}$A6 zA6>4}gVLAt=l_1c1~fPe{Q-;knYAp}L%2SpOm}PPBg(N}7B=Q>?xQwZQAX)W? z`B;VDZp-S@bZK5;(x5I>7ejCjlO59*2nJ4hBvf4YaC3hzC?-O*x|>Xv3yam8_OBA< z=byuVLQOBpczS2dhM3zAYTV@So)ngmOm3~(>Ca~_>4Syta{VLoO6vh=lt%a;T<-4CFov#85C-8RCC@DTYCm3n#}dWkEGfi z)~;R8{L{vyoavkoI{5@A-y{um4gka@qWy#-=O14{mrwgT&1T1xl)1WM7i*SId&r?O z#>#VLM|%UU$2?og^P;vwXg+*D6_R?m+%9}CkeEA6;Oa_HFK-ipxK^r+#Hsrt=zs=DaK zWHjm97v*0;gN{=VQno`VCjR$pE87`AXE2YGl8}y524G2^T5Q#6gMk)g8dH+w;NQ&L zCA_Eui@ZL=`804!N#GmA-m~ZiBi472_1h!2x~DOHb;sY;KsdXFC<{~6AZ;1-W!2EP8!m62(6A% ze;&ONAyn9_Sd;T(;oIv%e#^`-c&=*gy%(m>!|S4fm#;b0tTCM=r);Yi){-l2=oiIi zYj01x9_;VNwiz=>yABvqj*snTInGxYy^!b{bcB2RqrAnBpGO%6lNhH_EwN~0>x3N= zsh$3s43JsSf7uAD^-~2>)Xfa?)B*WuDimp*NKfE#x4d3$%e{Lj+Bm!ya zROr?zE<72g;hEiEA*)#izI zGH<*1v`yb6;jZp}X&ds58`A9}g!9c=gEZ1wHh9I4v%ZDYhWtRSQob5lAgS(vu~Wm6-=19$Xru5V3yk3PTR>OWIv{m% zC?;GhK0fYXZ=Tg%qm6HzxADoV4&TYjCs%n#?#-s#cJ=U2(9x|jE;l1h3x$?Xt>fIi{(rH6#T&f z@XMBiERZF+y`7Ech`!f9iu7q4%>l`qQI0q9V>Lr%HWYKS8zH#*u>jFznfYHQW91?X zuZ}4+Dtpy^%kw;MLSd0;>Y)7@reI=QM~C{es~z#7nLahnZVmnptI~XD&ur;Gb$^uCLI zlf4j(Sgx=rFq5@whtsW|%=x;Vyy|lJ{aC~lWsa;P`3>r7^Gou8m zWuGnyDiFFpm(ve~iV}A4alFaIpbgL?X<19lq`E2iNy^GvsK9jtX`0R%{mai&txyw2 zpjm~%{`r-atpJT>BTj{ay;^U3Yc4J{ye=_m_!SU=DdHofzlbFW0F14Zk}%q|UVN0o z$K5cx!NcmFchwno7P9D`MWm^Z?W_4HRUw99U*vS2d{4JZZ1jOv=231tAJ&71Er9bX z`bTI{FEnny76VYV=&j`!71G^6_jN!(Z)ri(lC;siJXLOgFWqGL52c1! z5@4D6=Y{eeJkyD-x3!0ke)?cqus0qW>!Pg9T^UMXHYF_rDZ`QlPxy7e`4z1qh=^BH zMz8swB$&&Lns3H6$KTD@%s+y9B2s>(KP|(MIs{;y5>DTWz_mxwEbi^#oZA6dc--N? z&A34(_5s6S4B^GkkB=-OY&*!izlZ$qD4t663)g684G$x}wNuZre*N`3w0lh<%?Cud1fPc* zEb~sokHa+aRWVXo1Q%=Z#8Ulervr4S!wi>kV1#PJn+47%9NEbt2%m%n(H-1m5x{-c zUe?qQcyAB13|TDi_P1|yPP`2+rOi`#GgFS%`WEI-Yf$R*9r`tR-8n%rC+}+v2KQ;w zrb0`yQ;G7%JQ8n0`^J=1z2VOi_S|^;o_@XBvA?l@5V#l}B;T+bgI(Qr22W!LG-)DQ ziq83>i5I`-?y*ANS1B;P)>21yY6a~j6u23ppgAY^zZP;*!XtIZdrsS2L}}hcerz;H z$efewIlZt8jN^O;(YeIRw4}-1-;_tRrN?L=x<3&@aW8pSb z(yz*&WHP-~o=0IwS=+0^jkE#;(w)Ax;OQ+D-j{MZ=C^$4eON^^c^U#zyQDtugtWHo z%cg-4%vhpB;)zHUy2;a7eIYGPazllx*7;pY*ysm(LRPZZ`<^j++}zUA>Ge3VvtZK7 z_MU~eRl(C>yVnIxf$9|haF09RT!u)-XFpp^1IYiY`bp78){wE78)Eofka2}xVt?wGvzE3CA=p*lcC)?#ZA>ACWaJ>n3 z`s@CNL1{||;BGj#HOY@mp8uQzBteNMZl43`XFMqg?yJN6^d3(tnSE}T08X$JMfP_p`WH^%+Q z$0B;`2c)$*8TMeg;2$|t4D6m^^p0nF+Uvz>0imDmhlPa&c2-6lZ{{}i*y3t`s%oE{ z@#B7|YQ(E+>Ogkxn?Pm$_%Sq$rp{1#@GF7R)B3)NPybFchw+XM{l`?P8vI*mMHm0TQ z)&ZlrYubR`8hC1f5MOBPPQsV#r9Qr|Nfg#_Wm>0n=YJd*gbjlTs8Fe(fAI&@!-f>+ z8d%5Sfs}MJHEUVn%XrNyK#8qe{BjPC^7pTq9Xr^aei?qnL98Vd9wZ|ZT3Ju6>Ns3s zG%u-zv@w5$F3qeR(8>9;vwn-=`TfU!gubrcL64|PU*_r0%k@)M>R=J(1c zFU8Sq+&7p?uGP%eTpIr3OW(`x^df)yJeyS`pTUfkKF;GuP<|2{bFj)NJIo{fX@x@h zB3lIA1DyXRlg0quVraAWoRn(~nYF{HGV}Y~G`fYG?QWd&bp9_0z=#aAX z;+o}|-D8F*P6VsFn^6`ggNlhbRN3~%%6W_C+3?<1=aW`Vy5wHNRBdjB@@MLl@191u z=%oA}=?QvOH)Uz{mmTDfirqOl1G+^!@k06YT=z;?+~G_tb!&&bw6eMIl;Qr)*Em50 zP_`y8v7C2bO;!0h)7lx=locgU37os6*ueZ7tN+5SxxdP({^52STI|50=% zj!gf59G@dp&ZrbMM-)ZozCujNk(#57IVOsQ-1iZ3C30kLk|Q)C=jP0n#^jvbeI3Iv z8iqA~pWlD5>SnFBLGUHf83{BoUoDD90 zRJoKpXg=swUL#b{Mn64bdt-lbE;bWIg}ik4`ivc*o{&#=POjByAFuVj7gj)}Y4z|j zL(sJi6Yz4Kl^^V+o@^!`grZ*GOVG&WbK^>;H0p&Y7QlJQ=g>IK-x->{MVp?a=yJrU z&t5OT44WimTT6ByMsVmn8*z1sp5}}&S7f~{Hcfjd`#X(YF~!h$V%JZjOg8BLd{J18 z$89^^xTKk7FUk9`Y1Kq%sq3f@t+Qlo+{U%M@epX4{N0+31v)FHWNfCP?OAp8jeTc9 z^>*W%*;Q(kWR3H!vsKeyZtR`r>-bzZ;wrjZMnsSKcTzXKlJ$st4+Q)|DOV7tM4Ji0 z(QG2G8IeYag`XW!sa~iZkkY59lJ@@mIY5Uqh|`RH$}uAx^d8vAZkq?=9Dd-}HD##C zk=`&17gy*kz0+K+iDCG%xvo^ z>`z%-XsIlc{4erm2}CsQrUVK8>xUXCZ~F3GY318#G~4Xj#Y|n)NOoeEz3UlEue{QJ zQ0A}Lbn1F;GUD&tH}CokSqUWs`W6yW$}29W}0Kq0KQ{zBAs5~Z;mK$C7! z)xlSRd=T*N=B2sSzaFAhCRq1jA@ruj*rYdS4LAnBCUX!_K&hB2nXIs>ZWKsMu8B|e zhH^_8tAX~iE>(?YltgS9#J=5wrMZ{7d^Q{JEm2a_*=KoE zm_Yi9y-#z_DTu0)#-HVTyS))^w25_i?#i(csY?0K4?q;l?7K>{ML{p^5n&>IL-cAqq(@8S)H9) z$7YN=o%5k&M+`w#vcLI;j`d#9@+@CXR%osXGiwQZilr4-LeFT8aRaG44Y2pA#WsK zDpk_nbwf2K6JYZBCbjZxzoUNQXkvOMV0>AS86pPhPst9v<&yIECQ9VPI z&CiTG9S^-M=-wg5GP$b*t?Pf@fSxRN3wbr`e%6hA!NU@vXeN12=ltxAsu8FO-=$#{ z1qFGKjwC5BSxbVO@5ZaR?$?e05hQM|PZDm;at%6jhHV;kAFA_=R2)wYy9r>Rsam8K z7qB70#Fg{)>6|)CluXIeNY7+R_dtDyQZ+1xrz%|^F2R4#G1^igGUa1A$X?3HEP0U3 z9Kn;!V{C&`Qt-1e;``7h^ujEa@BFwWsv~08q&J87vb03NuZ?_;Uxm-YVMjN*#nrfp^*w-Ae}tg(k8j zGC1t0hCE7|*J^EPDb0j!tOOn{6Wi4(fN@7J(o>K^qZ1p4Aa4Z!8)`qERXYOYLZ~W} zXI4~z@2D3*JJ<>dGaHw9{@2Fnp=j}Bz2%t~uf9l^Of-%@7}IJwQSCma4YfaT>tooC zGj?~=T!I-#02HoRcCP9$sMS~R!0U(;>C_)61L>9BLl}D4nT9jSb`jsHqp=Q+?IR5N zS+A=3&;xgP6)Hd3bO?=~X84IqP<9R(i|BcP>U2WVKBl6p+c#qg`BG?&-5z0A%Q^kG2H#=cg7UsIJ4$JWllt5$@o3}ym$hEE1RQ$oP~ zXi0FD+6!vm@DKLRFv^fDstqkgU<@*01yn-=6Z1}k2g^coh4q$RTQO}7a3jg@5w^u# z4138@w{`3@*2&4okA_Fr#yQH!ib>*-P5&;>#Hv&KB+Tn?egE zlCt#@8_7z*T1J5<6Kk_J-{1s{e3w zlU~q9CCE^HTIZ0Qq4Oz=Fnp6x6)mTN+hssP{O)$!(toP;Qx82nO2!uWMa8hI;u^VI z7tNr%rL|!HrI}DH;^9SBy;9!qwbyeB;ndm6BXIryjeH8 z?VXj-OXtLH;b;8ySkg){hQdc{e$|+00njiM3gR{Q&3>-AV*y(W-uXX*U!NGc32ps}Io_TM-=O7GtVBr{1V87jCR?k|k0+yw#2vXgK@J_W_*c zTOf9b>L$4Vg#hwK(8HPri50ceV04H_J;H+oG#|JIRt3L&IDVCG)4SzhRf?TZ+&563 zb&vI=ZS5R}04~KvIawuKQ}~fw%+j4mYM%FtR3q6X#H)wre;n#x_6wHG`d17j^^@7M_L*{79)q*uPALZ5HsQDw z-$HRE{CJcQ?GB(}QCs___HKC;L4Q;w5M;1*RT`hT=4(xu4y2%N8YFcHd#`KO1P5yb zQeYQfKQ<28rEhim_y>kI&uuwgZq*x=6rsxdO9k!9kfs6hF|N!v(6#C;i7hKm zZeoQKa5kTffElhD@_*^x1dp;&&v4<`JB~jjhDL#g+TVC#BHYz2S};ThG(17Mr_}lG zA@kCwWXQ`83||C*DK#0(R`z4!j0=QII^T!VXKkSgHBy<`pU2=Td2a+o+({n?2!nxV zw0hFt>!!)yMpaHx_KTS-H@Z(vwWq%M2`mdVg)^W_IQ{%4ipCy3f_P1im|NZF*cZr&N}_Tq~8^1JIx%c}@EX1zHJT~U#W2#hShU$W*bblD+#DYTOOtQ1x z3kAkK9tM#fz|)-La$)nd#eGOW8M{)eCfoR3NFh!1bDgD$aedF^6FFutg3!wwZnE_a zlH&=V0VBn#b^I`tD@%~?4&;3RiF2Ut0~|E`%}6RfJF9+*Gd=#-BYh(&2%8ydgm?Hl z*inH0+D#{J*-~EUxG&;ljW$H2YjUmy%rKViPS)=L$yXD8g({~*z!q9}$T#8ZdDR0= z-}Ab=lnmCMDW%VcAL1<4uF@7&wKTPXG*(7!?<$RlUIm7(gWFEFmUoZVXm)+>8%GZ9 z!H-0I3|<#p&7Ww=twL8(lqJ#S%f9=sc_TnY$vl0Fd&(*QF@mxvjf-0gcQRZ_jcG5W zN-`{cn}_g6j6RwUu%%P!e++CDG#L}y8Jo9HW=}S>Q@bQg>7FWyJSITnx6=F1+MiC& z<0ERg5h5TY;F&J!S6TEuGQ4!Up7P(C*O`1XuwG&{KCud#7Yk#Y1lP`Hl~rkHN0#n|6H?(7ad+LhEVxSC9^O;2rJoK_UI0@DEj zAmxf!PG{3}y(O?K;M-;X;G3*3$4~ptHzSE4`XnEpytbBBeF7SPliy&p#d%Y)$EVE( zdaLAf#9VCK^d;4VJY*1#;7J2Q$42BMxp@y7H7~Q2o=PkkKLldbpC!a2vuuXScwrnh zW9N{dI<_3^GjJ5V(tD;tCeVr)sC|{vXq3>YR#w}uQMd$~_>dLp4S_14>e)z9JZjaU zDiKy`k}xxssz9f{MY)VAUQfgdaHft8;&WlTAc|w98)8E8zgGXH2}_RtV9O8EB^gQw zzxEIWs=O(_jx}M_4LL+ktv{1#Sm^O#B6u8aLd&E@|Jnul2@a!8l{G7$9*+8c{B=kzM@cbUyry4qG~n4Q!5mjsUpf;sNa87ede`ga(t59EImTVEluXF;&{Djp+RgA*Z*2=4 z(up^q#U6*@Ho*lGY$towCwoKTM^e`Y$ebIKzYgnU&o*67V}G=ti~bOI>w`lhdql&^ zpTJ~eEuZ9a>o+!ne3^_S8x%XF6C{S%8=hUFEU0P^DKD0LB!C*q0Ju0MZfcNoh-R@C*lu8ec;G2B~JHx*7) zij7xdKl?m%i>AR}UI(z4;#&DF;A;tK^I_;~DiKXTf8=0N&`}+h>UnxQ%o9k4W`6#`f>ZY-? zbF9@NW{~rfs>%`b+lEO48W&PPwyx6)ilv3MuU~+ML$;-rqQ92hjeDQRB5<+k!_TE3 zA_2-&%!5j$J<*cmAY7>+uaWgP@dz0=F8!PUzlsZHa;*2$rz`69DZ_Nh?@0tAT6E7c z+Um&RiwAl^21fS}2SVR=>d%F7q*x~Nve7+inH_To>#r`y09w<@^62$PhIwG@eYc}o zoZjx_I(D9BWLx=6P9F(wsY@mKu3`oC2!~-gA={$SPFbksF@5KI-?aQCN5E% zmVRnsR9+aklQ8<(h(TH#E^c>FKL! zZqZDTegCCLt;tDOiUNnEYcaMKA3qqroC|igJcoSdgCcBE%c@6PIB~=D8>i~BD4LET z8ts07Nc8FhoIn*CZ~>_eRo}L2j0iABTz%Qf7XD_SEAF-AbiK?7riz`ek1+Igmpy{Oxt182^`*Zd&az_a^)QjG%Shy+5aJAKq;Xp%*7gEXcsIOsXjJHM?ZY?cf75Cl?V*y7h*V1yKSGrnl=C$IoSAre#)OB5XZBci1N`n_GvxpJ z6^$sklEND9PaA=;fY7<$V%(hZ-GDY7I#?aMC>qc>P1$nd7ADb4s(JthK1(IWbv&DB zX?Yj=4S$#)qNpHumlb+L>3V#Tt4O)gXsEg{t=hwbJQ5Seu_$7~so48E^W#Lz7f6f% zeY-2w*(ICYHUde0yQ1!vS{JjyOI+me)~Ypcj|ei7=4t&aI+ zK%v(o2;w?D_waUSpQpeDJuRUH4Blz*aKpH6sk}7$)8!(r2il1N_~POU9l_*Ul%7T%3q`bcehm6N4pgZ zON-16v`hYqgJAAivbq8ebCs0oPY*@^2mi?k6EA&t zduh^5i=fpVwt?JmZ@hVKd_Q`*6cxL%eb(wObxEJ%TeRu2j7{A~$^&&UK zmtjmdN(%wh*kn(xH^(Px)dQA}4#mVFwcFitSIsbP{d+rWqPtxbz#4meNJkG9G(rAu zPm=%-Wf|po`*tjeR$muhvVbRc6Bq%{~dfy(o*S_Z02u8lhIJ_xxjfM40gFhX;F1Q z*n0u%c=NaScuOhdgVTCUg!8D)B)d-j#JJkMqA`CH^V204_ILJn)j6X+8;K&SJV-<` z-G6@hu&u!b;Y||fJzsu<4at~11%|uXcdx?7eF->+WC80>Yg!L}Ep~aTY~}pcoV7Le za8%LM0$hJ?wiB~EuYxoV1&%i#)&k*d| z`STeF7^TApbuzx-bc;w^q!hY3iTQjvqs4YD*WIlDvTk3JQNPJ$ zkr-a1Drgel-DSV#MS>_U)hN-0&|Kb~nJt)Nhi=iQ=%i!<4aKiuuJ-}t%mz>6GQ0^C znE<-Cgbv(Hy_mkL8DUU4(3bJvov7E2?9r0F1J-dR6YH%kqfQ8&!TYA^6(sqKM;Qv{ zq(>INKYmm-t+)Kp%RNx2RIX%gcK?e>NxY-pO6@Z@3`M-AQond*Rrs4;YL)+|w}j@| zV@IMd>}S|^GW~e|Uk=!7y#3$aMOF>~g1qZDMPXAga{MQy8G6tp43#N3hw}_;P zsPK^@%LPLeY-dYHd&BJSBhVeW2@%=w50V7jyT$Hk<1aB=)1L1J6Mwt?_4;kkZf`IE zkhJ(M(oI5+xl&$=G>ixSXcmk#l&Sy1%wS;V=(Xh~`s49ZGUCeyj!~OYT@a3DgD)R6u@uj1pX|R15Q#1b zI}qJl8ztHOO3B?C0=EU9XxRd1+h~rs^sIXC?(v$zaG9rnkj4;oEO~ZxkwrunuQ>R` z=jiz0)y(D=`fakXp1N?7)b1!SKHlP4Om)eBUbDY7JL(V)>W0-;6c*9K5ctiosVlu@ zeZVqZAXZej(TK|6)~Ww8J@;x*V(4vTNtdk`#>sYq><tUGpdiqyJkT{S`~m z4!iapf2d2asFdmfOue&V{%W(fcD7r1xAy7q6Gk$LJiQmP*{oOW(5PYeP(OQ2x|CWr zpG%9PL8B$5^rh6RN3((Q(Hi447&thp$#!vT9qrJA zE_CD28JOW7p|O-mchl75yKcYGq@fv#KB|eExqTgTsG2llndsA+@7~xrR1ki+wHOTJ zO&L9`8!(zL5lq#k|NRRfCTGqBTfUt8Xp}Q0MTG0_O2xn~NXk0pp(~I*oFJ1PHF-eU z-Fg$03NMYR`?LWKQ6{5H?C#l|Gv@` zllDG1^w19fcfi;Gkc5Y!!vw2{C@Cl5KHi3f*Jf|pgxp6G5wA0ULc%PfV*RySX3N0g2YNH@{NE(^4!ZPY z&Jw!hILX)nJnOxm$7_s5U+k>cLzkBpIsL7?5Km*RT~Ad)$$&(k;4nnP6(jZek}5c6 z=TQlBYCNAkei?jAJK*F`m%M3V$42XO@Hu+nRH>$d{;Q>2m3^5Zt_MukQi8DcbK=Xs zkj|+KEYB#J;)PO3mHi@kWkbky5uL+-hbs4|o#2BA5ag7iQ><&`hATh#6N_0#HvZ7d ztpeAzh@(;L?AmOqRj&P%*aBX*8D?G`$uKob5!Fi3s_Mog|rXTAvv59a` zql^92b>S#ZLNJV9~2u)Zfz-D8|basV!LTpiqtN#`DDcr)ZDH)lsj?kAXN$qm-#Km7+dId*ir0a4`b5$4_)_=!c>=K$A$TJ^}xh8FMy(aR9q3sbS^tVanHIsG`RkU$c>7y?GbYm=2I*$WEx9B3!%Iyduy_Ap14>= zefbUXdRR__+uq&iL{5;!7+(rRUH9ULP>w6~h>iFwS#IZ?%>+Q#E{4v?vdw>P8!_%= zr-Xu7MFeU*9`Pd2)Xn}J`!c}^vR<$le9v(jg((GynLGYSKHYl9i|^2U9$NAdu9o)DaDPXK^7W{@bZ-_STn zjLk%a;U zs_uK-u2v!fso&#Qf}w@JRs;_Z%-F2EN+P3{AR05h#`a9>kRG_6`W!j#7osXV>Xg0G z^+zr#N@PZsmXtJ7GT490o zu^ztu9??M>0N06KK>KD$>Z!0=& z&L5X@-|Us}LzNStd`Fw!o-3jHJkYy*w6z=LB@CE>T{h5C?IB@qgQd$y44(Px>nBHz z;ZBtNdF{4fTEw%_P=}(i9?fbJew=P^K*svbZtow}o31VP`JX9SNd$F$4Gs>KC?b`` z1_B0)2ciDjvzZ5NbB@402GlQs`F3`5KROpj7ne6N;G@RTLtsN}=w#QU8gTrPZ;Cw} z1@Pm?U$xuw^%6h6)jruHwN&*j9y9u$KYFIi-~6zV_xqn$!o6)P6%!ngYPFJQ9GH5+ z(EdvT<4e2yNB@q2)TCwJC-@XF%cJ|yL^t}n4uhmiP7!_eY%(_VZuLYrUAlzr!hvHJ zza@WCv;q==X)oL*G?=A=vljp1*`08yd~UPVz7`Z%034E3r)Ro1~yn z8-SyaMm`Use`IHYnxDT0Ery*DQSHMRZr?RWp23W_Tre|1uzqNHrfW6|?5%n!kGI^4 z@l(q)tQ-Q2T&BRf7%w0{$tn#_bWuqhRaB$+;~rauJTMp^MS!&?eJq`=mr4V5P8+O^zZ0s3NF+~NS|=BR08Qm*MOn*hN1MoL8@Cg zEx*wAX&%>uy}eWM+;qlr$eB(`?3G9i{?-TpS(l|w0j&-yzrryPN4V7Ixe9=>oMtxX zT7yrhb|;poK-%oLrCM!a)`eyK69qC(Yfn4^xY9|&3Q0J|vX_G&zNNUz5qauL3?H9Y zRmn99Ee0qYru2* zjh1Bb(&(BvA8$55P9ErP;qv*Rlk;LwCT6Te3oU;e@7^xtzql9HY)ZXn5LfP4?2^rA zg*lh1^e*(e$r$JR%s`FF2dKb26hSn4VqGpt?B{^iJ&V0C-VcG;vdlOgYyBwLgu{kW zxvRZ%xsj1=5hX@+whHZpt-NbRTu`Y4Yien}C&A7Z8Dg9e9bEf zo5bv>R-vkCFB(xN?KC{0dD$>Hn0`h&`i11@h(fB{Pv12xr19VJVc)cmw`jvuT)a7p zw-wr{S^H{IS>x@+U!o08`6h2VVjwAwAPj~Nsj@5iD8~{5DVf^s@&^wuP)YNoz%^W$ zFn0C7fNCV5s@MuE}ykl>7!8rE4?DIzu9$y5a}W`QU`eiS$K0 z%7A;z)OQOwCIBkGghXR=SN%euY*KPFF^3k{!(0rU7e7|{C*YMb$(DpupMiHk1^ z9SfLqcvkN8F2Q!K$tia{*2HzThf!{{>1Wc2gv6LQjPf;W^thUbYj;@wR$Ob z^iZ)CctXXNKOGa(SZ{TSuiwoX#LuF$&mGqdis1aJ%*Wz47s2@pl$}6&k+XBUx!(E3 zT+dVm?Jo5F0}K5zg|~_p3Q=%{%q;24nLp)C$eYYGfoXa-^Dwgf!xzoj7Y7_;uGV`{w=9?k6B@1lRVX- zM$A@pVSQvh9Twp7H@T^{#@n@lVFA^5(%Vq@R&lxTQZz4b$KlgDk)x?-$NREh-_{6m z_4A@ovH_&Xgc@2*ooJ#(qt~gISVbh#nGx&ll}2-OdG`CuOJ{50q)g@4fSmnvg4p}4 zTK~9f7}1E&FPCE{idW8X6r~dNErg2zO}Vl4V3tnSt?IKN)%(%c;HMuHp8^9IK(x}q zS4PZf-YY9g=v3So3zGk1_#=iCoByKn2hxAJAaz0 zDkU{G`mig=?PWYg&v=e5G4GvMO^fd=$!@<@LZLed#(3U0AkmwJM$6{I5NIH`{rnMb z?zUEt-k=Q2-ETp8ydChHvK_$PME_uLXVJ-uYmZ98|M_|mVm7NS+`r?wN}=7X3H!Iz z)#d2{pOyggB**T|5*vvx4-eN4mK#m&>}+#7z~KSK5f)QEt$xGFxTv#L1Nab}(M{GC zpIee2e zu=a{?^Q`;m<~~R6Na4iQSQ6awen4~UMEyI*zWm{rjq~CiI8yS*Wa6GW=}>vLcx?9g zJ!~rJFG3|<8aKOR2PC?B$9?1BC>q^u=A9@jI3x{QbyiLINK3N)R;Bk`n*W9+q6$sk z`G54RwE|Q~J@&f8KLY$f^P9AhC15p;^T#kvtjXD>1NO^byy*UJY#GVjpLfA2>o(Dw zLzSB$KW^hye(5#rj8t#qZM*P8IxvIZqK6+ZYWug|Zy@jG5|v^C1bb;iVDt4&pH#_u z4CEf0SJGJViY|1n+0q(gRh;E;b38VV)gHS7%x_b?R5$qSv&zPN>sLG{nUI`!O@@F? zw&`Sc=+nPqQ%7M>P!3)Qu(8a+XC~6}RkQ6mNM!ucf_1r&hHw6J@ITE#AanU1nC*6l z2p$0+)>GITg~nsZBiI#7eVy0#{YKh;gg2SG<;@xAbfHCK5+q$hPoF(U(qh<<%jyLE zTrO@#w)^-*a@ zRMabNs(3{yIh2RPqq-UqM;fh9_iG=Fe&}jVx`olYbEmB}PnJlIO`b={lEMrYw~v+W zQj_Om@zW>{2SGOHAp+xgx;lAiXpWu>nAs5-r&4&Eg#M0SV-t%=B?_8-h~yAu<#&4K^~<+4t6tLB#^|@fFupcgu+_iY z+z^4ktossHY_%QM2=-{CANqxG?~(zkD|_w)zWKk-$`roAD;gx-+yKu~>#e(W>NFtly~jC6A5kPSubRK> z&-G?gt*(5s#9Ofhb3N-uy(wBd)?oZi*|XDMINMUH=!{9-I9m+9_S|nn(6B}y$BzM_ ze@kE!Gu_dtGZ7!9qEa0D$0cGxg;UD70v1Q}^0;UN*D1J}nT3m(&U>o*)%`j4r++g} zsxX3L5zRU!7*THA|GQCW?0e+;f1_R0)Ux-HpVcW5U%oWUD8=wbJul)CT+LOefqhfl z#3f}CZL*}$YHYC_RCTve8&I57r2Cxt8P6_vS2Rqvhc!An3p&UyTcV|<&F|RXJh38N zMq=XSd@;G{GAKQjt<_53-v0Lx>grZ%pCBev8Yb8$;HdBbfhKAWQB^siPf@FeO)_uLS zXL^^FFcLU|{;LRcZ&ZoYX*rL=b<63y2dU&d}m>7p;+TCs=}y;Eah15IR$bf`EzMVUa0 z!`_Oho#S(+TwzvMcu`b;UgweSDW`_X>q`+E-Nui9t3m83uLZNRBJ#e;^hb3h3Jw3z zxL%Y~_Y@j;B~z`>;1_(F?ad~5N?wOm&RJgk*|ptBE262+E6{QQe6g6H{> z>qoY5$71+5P*!SOx^KCff)M|QkzaE6*kds1+1JhXHoj}G*h*ZqazZ>I*%+3W^O_|* zFmavzT&~Wo!78GwbF*2UwBq#BY4*s#K-0v#YLG6+eTQG&&!F3wEbY9)Omgzn!t%PT z;#yYl(@NEW{xjc(4@0(Ki4b9K9&*dST0X z37{=y6Hmom8I|K6$ta5rH4B{eI)&?U!~Xl%Sa7o0PH%)PP#2SSk^ye)v`=VoFyOjl zxV0SvL8HTCp72Dyrnc7Hxrd>`etof%f&Erc9-KIk#zj&&BY>fF9F5#c>9wJbc6GU{ z7*l7}49gwG?qeuOTfC+TKxDKu8CL^UM3|Hey!CPH_^fh#YEr72qS&$Fe`eEV$gCl}vF$niRK^Ov}kV_K4@YQxfe<)1=1R(%-)TFb$re zc@>w^UbfC=Pi^xoLi=2AC@B|bO9&+@C>RdIIMhfz?A*}ijM9aHtaa=yj$T6ZLIeBT z_#WPrALfs_jH77NT(4iCzO;%IG`Xnx8?8UqcIPBVPkFqRGOzS-2KI* zKhWbo@c{H*c6RN+Cbo8Ox~t1@EN`o6Y-1x@5}q+LUnU^_ep(2c2AO6Ex>Q7;+rP?Fh#a5noueeu#2?*la5JAusu zKu+As^K3Qp$>TnAl-@P|caR>z_Ry}IiF(!Je_@2OQN^M8lLO%KpTxDWxe4%tO)jaN z5s)8N<*Z=d9%Roly5JI5cWZRN=X%k`4-HpXMu}mX`zkT;-uJ#TfBU0lDn)F2)E`J( z4B*d&B8Q?U_w+FJ6C+S8@L+MR&&X~~4zz_%|4Npq?rIvsBO~(fh}E7oC48iF3@w#7py~< zB#5hdg?jTfjuK_}Tqebt3iSZVuo%c|`=h%$jW;De&#qL-7s20p+sqIl`zFO=Ft9A= zf?m3y$`{tJD6w)OC|se&c~0ZmwUuS9H}BzYqI)pwew6!vxVEKRYJI=n%();?Y0hlW zd_DK?go3hZ&7sk~nT(mhFxtT@&5g{3kRbjvawyx^*u|PC^ND3Lv063^2t~Y?LcP!J24SNf_x2taeaMK4Erv8jrj3AENI&E{|srO)& zoZ5MXdbsuOgItjmD&RJZlPao?P~HGyBym3*N~?81U8)I_*edk&a)7ENnn3#vT<*|^ z&=se06F!?)FI`{M<@H=KlPyVa5K2nQ1?oLsRR!62J-L)|CJvu&~x9!wT*YbL07ny@! z*^wVBm_g^{=)RbeF(sl5OY5z(r~DHkTzpTf8~d5SNy+78Ln+{uSU7*aeO?6fV>R-k zbPdl&Zj=Y}J?DgvrlRG*cT3B_64Zbrl5-L3EH02X#tuz~xd=@ph!*-+xi>wCa?2&> z>RluCUv7{N)qbg)3ae0cq9_acXmbY+T1ST{M1vgS?ts)uNJEU}<8n9t>hHBn^iYh2 z-YczB<>x}*O9u9f2L0c#81n5;1`M{E>kqXzFexlIbo;>>QG|RE@hrv}6uof?H2#vx znkJ5vR{+V6@}^ZVr828i49jCc-5cWf|F#;+RwfhJMVKS-*bJLgJ@<35afS9jlH&P{ zxjs(x0)wQ@DlxI?OZSTF4W%yw7iGKtAB%eTaJYE!S0{1Q%@Xw?=ZV@7YkQG|8y$Xc z%%AB(nW4oC5A;us8=sHheRTIRZwD*K6`VOq$_hOrN7e$u|=~gIhX0Lg|(k*FnwQ9DyvT_Tk9Pl zc*^)9GBC-1a{d2rhSgy?FI69Bxcxe_#hYB4DjF(Ye;HI^SUCtm=W@9T9|``G6jgq2 zIgNdc1h^a82iBpar=!6Q22(=Og$060=cm$^DR zy1V0U++HrDgdd#%Xl!F{|E^Vzlvjh_sKTMf<>Jryn;t%K-WmZtTM9r>zs^5Mqi<%T z12<@PZU2sTx?*D&)3WADaghP0;aq4Spl5>@Xiam4TCOMjFioax(blNw`Kw@X5>MYyfgNw&*uUdhTq+Sb z_$US1?{Ks24F8jdm~j|KKvpU6dj9PN1Fe1H&EcNqF}l|_zDGb;W*@I5pZp_*A5sAu z2QY6|^r?9Za1Y3^9Qf!NX9*PMzsPXfart;i`+vwM0Ar>ErKKP0~}M=psn?ee!t+TE?m|0El(#z zt$}`sSqV*FN_gA1F zAwE6*+_{INKJByEs{3EZ?vK|JEcCP1cK3r+uSTAY$naxMVI_B(4k zU#Cy@*G8w;>gFlOYXv98z`wLlK9=(%FVtSa+cxo2=dKAaD2(Hb=fH%!dIhvQ$Ob8Z6^uRR-*<#&+Ll`1J;-rfEgMH zalyR4tbz0!4Y@D!$ak)B;{U%ou7XcZ?us9dw}^VYxrs^GW#|a?-tl2xkK*a9G(f?P zOhQREkKCZQMhsmzL|{i@&#n8;en~F{L;#64hMt{eZTIaJTwO(b2PLdfdxV?@n59@8 zT!^5Of2D~6zP-gko!)M;)};wI&-!1Q(7gjh7N@dFKuLvTo;Q@z)Y z1duA#mE|k20fBxujUboHx?eD&MKn=#i%b*KBXgqDCOs+B&@GO$qB))S2=?uDuK@hF zKzBep0#I5%0h$A6vmqVwvs7$^Rb@|_f)rI{UjLQojdLO_soqUKkAi#vb*{CNtTLV*{(WJ(&e_C|dweXsaykecc(C!DaxkRFegE9Z4V z7A`@}xtjE_RlW!M?;pT@^K*Xu@~6=gqoL-8zf*T0C97t#KsBhz=g3N_yg+t{dM%TVngc@QR;QAjAc{2mDGw5#RC0u&*JUFH5jVl^`(Iu;z z7}x;rJuDNwkt($RiRNJIbrDxPCV)K*vn$)1rTXU_R~rucD%HW4e41}zn6}-3ITup# z{^WIkRUaN6M-{IGCZ>v2xqZCixQ!ks!CUq${|6i7cHw;FmB4=21yblwe_lvTo!we>Opy94&BqJjV}TR*OyKT;IImbiC~G|Q{D9_R zI@T%A!n%As1@PZMkz!uX*h@)KG{T|e_W|gPt3m|qEJU>M(r5VgWAp5ZrC=qgdbJ_U z#8h_j>v(;ENAWgR3g?XYpZO3-HZ45Uwul~N;*a9e988OgQkSpH+@XW8FkpwMzSIFr z?2Z5dI53{BCHI6Ta}47m?X&CsGFoL+Kqu$VweO$ijS4VzYk6w%e+BZOJy|Pd{^$q zEf1P}x|hU&WlXGJps`19#uvJ31t6;d{AofM0- zFb^H?{JCV11|gw~Rch`|x%9{`nb~$yO?GV4tTbx#SsNdcL{(J(8?k3W17hN@s$&hl z{8<^Nu<&p!eI%In7_c*G!hZJf0dwN#dOZS~vhHM(_2~O!_USC>#*QWrW=; zxlj~uItS$+_H-{2y6F~rHd0C320_CzSHk7e4{>-AFrPdw{53K|V5Er2WU^!00g&D{ z5N(Tk+=l>#k)xlyP<{ui=D$D^9Iy90?AuNxkY7u4c$ZdO=(E1R9r(&E@OcQRDsx{ncwF|$1`9=J zE5FK>uauMN@VPe;-ff@IV*_`aI`(~NKC&&FU7mTfzVCS6DdG7<&%9j zuV8hhvhpkz07qY(h>+D$1W903*;L^tRSumy@p?Au{!i+?xKq9nz2c zP6;N`7k^eS7nf5-eA8gC&@Y^B7H6G&seopek9Eu!akJ$2ui#f2x5d95S2k=g?1>mCqoO#7@ zdm+}qv@5RNeRPyjnGu6pkL<#W9{wG-_vlHCkntdQ7sBL(MbNDiQR4|qLNyCWeoMVn zO^y1JXVNmvo!Ff<+0CFHQxBji2E@bD{kzkpmwdX*M&>jREz10J>~l_hxI}OnRc-S_ z@YUIYhSQ}JG@Z@DM*;65=+TEH%}eiacEt< zYr#EYgln)yS~0P~7e!x+@%3dq1^>z z16Ixn7t7ZrZ^1gj=M4S7^n$P<&l>4JS+WY+o67a>#7?nLv3t(Rq9yx{qis_z7Jyn1 z9O~OFd#_klp%B^`WztVt9~d%84gO^-bq+?0krA$TVaSS4y7141R)zu9oIfLK^3XdD_Ifo5%mGB#`iK_qx>85J~(ob8Ko+`bIajzwbo6Dqi zKTtt$$nPc)Gw!<+!beBg5np=_)=$xXEG*y3V0>cL`dQHGfNG8E;GrONm_HXD~vh+pSlpU`GdEa zt_7`la8EiuyxJwQk#R%OJ#?2WQI#SNwViXx^`t@2n1A9Q;HyfR0lq5N8SxhZK1v(L zdZStKUu~Knqeb8YR{uS{%*#2cCl=E`H@zs)amD$t*gaY ze|2tDfIHpj;WcXx(nQqo!NY#?v0r}!|Faztz5ItzddFGQQ4t`kF~kHwpxcw-^<6JX z;ld{-Aa<|jUq?J^xCW;YNIQ$;+q-NIy=ZX$+Ad&1DNFzSvGrPppVGCJv@EW`>QZT) z62JVKH*0XB&I= z;#`mHcs>6E-eOV$Wnt?2Ucpuna>t;_={GMQp2{=+z~d#HH~p-*6>v~x)RhJmod=E+ zfwQ0IDqvEITVRDTtg6n-*(;}>fI(jM!-D))PN2SC+iOmuPjZC>1zR)!3=jZ)biMA3 zmw;WJE*PYvB?cO%L$SlHr;FcP!JkJ>M9hVE==rY=YJCy4Z2-m;c3M`>zKS(vCjr9H z`&Df8lAyZ`+{!saR(Zmk3y$*SL3OctFy3+^5 z`Io)bgvf!QNHI`mc^RoeR*HOsT z1OWPk=a`&}YAZ|0F?cAU@6jk@z6wRtKb%eLj{C{z>5MP5#-~SxbR;Dh9v#p}VI&~s z!q%O8c>Dehz|4UNg4MwSV`M~_&5?7B@{x1;mzZ1oTl*pL;eC~(ch2o*i%>VQO-UqE z8A=yBqz8VBNS|IG)32_W?&<6X{RbH2+aIKN4xc)rdmQfIE4z~p|7?aQaQ4P)Yb}Nm zu0`z4zn|rEZ0-T|!l}a(Bb&fN6?k?3m<@d$_7yl{ zJal=8ohT$s3~VZ#nEVZ>ryc9}*5f196@Kh3zT$Tc3}{N9Q;z^1DYoS-+z0l-ZV3{f zc&E>LoUph3=fLlth{!0o22+q|Yu4Ggx48$WFqR_j5v?H#)hoJ@Z$-K(IWuAI!L?15 z3@nqn`7vis6X<7qyvVgJqPNjI<~>oN32`WAW_W_WNd&yXNp#X6s*>^gvwAGwJL&kZ z1kH`&w$}DB=?>|R?zjZbZ1uHk86RC#+T5G&_-A^aMEh2G^7DSUpCth5^k(2Vu{j^@ z-&*wQrxJF4b%Czs$G>hg^Q_T3G&V=f}^YWDyk*g(jH9QmT`+VsOsM)M2WY}iJ z=iZd6!A^#(Qq|>>q8Y4pM}D4>|9^(FA^fQ|Jvx26$8ChRlIV!}?X8YL=?!cob|@H}V^#!Q%)Tk~l;LO{0EKB`q@S`9 zD;Ph9zRQHFpfkWf0La?92+O}4&VW-gtkB|6!7;J>=oiNIvn@jjSEhbKz8Lh(EOA-2 zXnB7vDd%|=n6m69s%5clW-92ImT>vM4ORT{)5c59hesN6 z>Y90VKnL6Ce~v$1ema>wV`;zk^t4sI;_TGDYB58zb2aMKFgjlY@bW^a+h&j11X>Sd zTb?6IT?|Uq*FrH<`N7c&4b|Kr39+P;_Y}@3g5R|84I0oUcF6b$c#Y3WdwD_KT0A2~ zgn!EVdj8{{%G$3Irp8Hkzj0?5&Vock!GN6Fg{i+Wcf*fPhS&y3yaM8fwdu#TN{H1{ zQx5%+q&?J8(dM zyn{_TWSUx@+G^g$b^orOdTD;A2$NI(Af}^C7!S8K?np)|R$m5Jo#_M))2t9<_-Iks zHaKF;fC0;|vGM~U?g{aHVpfz#*>;j-3nNgN=u-oLV0p2#u+TLSys!R(6{F1)c-bC7 zJkR(%#XaElh_CMKP0FHM##~U32QbG=PL!?v8Px?~*GF`!P8(i2 zuLS+`wdLn1aoH&SZUB5$Rj;SJ%+#Z?kLIeTt`O7Nc`$`9kLp`R*To<1|9Cg>8{$b! zSAM0hXzs2P)wvnp`)10Q*ZEAcKYGY^Xq@9>Z5aZXU@hN#k4W|v+UHc{mb67R(%YrI zN1hpfI(b$={{yq{G`Fa>qjg3}QG|b$yU>_mtNy?Gv@+@}O#S@z^s}MQl4a_|4G(Mj z&0l~okLhW^S3$iKrTjN-;Fr*fZ&2b@Tq)O3FJ?Hn3nG4`4Aa_)3~QC@R)6Uz1DXp0 zJ$&g?ZR6Quxce#HAw4Y2+crjxPqgE+LpbarAjwRApmC;5}J&iw$jo!0D_Kh2+O1u5>V1I_;9;HZ-qj7?C`)Dqiy zz@G*28??IanhkRWMj8LR-Y0VWiakf29?J8Hy(4E>%dG$tZSPK3GvoI9d=q?z>%{w3 zWji5TOWOzj;+;3R^`66D!b*(86nMSJbsbfzvreg^cJFQqjOZvT={*xQ0!>Uo>fS$- zo;J|+Q}je;)yzVWGvG%md}5A>HJPe90Y9z~NOo(nSRT0K{oAF89rfbUVrgly6hD+u zMnL`A<>SmNm5aOcq(Uxgw98HB3eRYO1YO+ zFq%(USyqJhw0Z9$pj5A*@Ue8sh1bsa5BU?#Z7TRm$kPaT^*;`FDB_e`qn#avd<*C@ z3FLH?TcOYQbSC;#E#r#pdk&AH_w#)S&FAby#(g6?42A<69*g7*4i47wTB#%}Xo0!J zc`g4c%7hL4J8$?$?5~`RXqjlr^;GkJ>QpZ(Y5#{a{6s4AjjDHW7H)lL_sH|G? z6V_yh%*5x&_nKkqR!dK5{vp%QT=OR4L741x3-msX(UX+WQz4A=mvN+s)&I`!y7NpC zU`~U65u2KZM7=Kj2%>WjR<%CzfitN%M|I0wBrprRX;=NN`O)vshTCX|`pI-aD2sm4 zB`=HzYTB8DROa|N)iHl+CjmJ}rx!I5n~t^^hg4E%kNtS-RGF*%&gLe-qCVO^;!s|s z1176OQ?Ffmv$!Z&;>URUzj*(W%c6%JJwX1vlx8r>?EE}qf&7|2ate()t5HP4=Tz>$ z9YymA_w4N&G7{7gqAvGQbw>OQsbJ zK!I-{Y(#<^oJwCGHapGr9UOwVs(~O{%2TmDoH+vVj%o>u0{`x9?qg#k$9cfhXKA72^NHo@F0j{i3PjSMG~-NJFx60uVB zxTgQqZxiusj!Wy@s?_V>P@pJbx(#0w{j=~lq@$}RAz>vg6~fEw%M1i+^U3_U?~^X+ z_dj=b8;-s`DKju{KBEeVE(YY24ji1)Hm!JpODH3l!DwoVsb^rLbokZ(PK?rF+ve$LHiX zVH$n9!gMxsX7aY2O-)hjaJhUD0^>z z*FT8_LTdy22}YhBaj(XJ$6-cWOzNjCmjeLAQaNw42GtD*Hz2y_G%$4{Cd3AAmGcY~ zy~>KIwbW^FDknX?6HRZeDJWwR;u#%pr+)pf$$*jVY_1sT`^!Sy|F>J<-n)tS z{#jB`CC8n6kJ+AENbK0biH@sxrjD;X>DBISadZ6BtFNJhvSh5b+7YzhJ*j?H{Qcw0 zWb^s}XLS#E!d0!wmk;Nb#vd=UyC)y{p}(?sW&?~X<&dJMCw{N*aH5TVs$b!;qyAZ- zhP6i}aaKm*NeZDp^@xgJn$f#I8txjD+Y_`=%b^~?gl1?yw9T|EJEoqn(zTWdaaX)S9gD95V;i#Tb z2}5B~*toJHAYz@h1_=Hcsd8ofu6;Lv{dLZr*b9x2dWit6u>%>RNFnyBPyYNTSte>()V9Q zAXsY91in651L~KtxczF5$LB=ybPMXV_(f0*pCZ?J3*Q?^yEE%I&uZ#%@qZKeY(XUQ zkvb!UTcj{%T~|K*s1wj}T;tiGyXDus0zm@)h~+s;>xnKtP;!-2ZCP@3V5_mkpPu0&_ZF3+S~)Hyz9 zE+x==%eOa|uitp^mwut&OfV~VLXh9)Re9d6YzyRC2{P(G5u{@N6U;0~?55eUXsLAO zV58sl+oX>+Q>CEkMqS{vjX;ZwgDx?D72C4D2#V%kjv#ZHBLbu3&K5uU7*=;8#cx&G zRVDR7jUu-q%HCJpTQsi>E6vR>-mH6ifa`SLZC0GD!b=`+XCE(fcVb|>y@hH2^09tR zuH=$ts`UT3i7xy%Thwgi_WRQtaP02Fn_p_vDFB5;lA_@3pjBn zIyU4#4Qx^1y#O_p^X4aIWONLZOpHxUj2lVK4X!#bFMX8nXq4}`w);BLg~w7;zB7uf zm8{~g)Ea^$XfFx{kZtRuRIbxxQT(3}Xi;ypvvWm-JXgcgEQ?FviO_I67_MF5sR0%2xH2`^!j2`KWkXz6mR8lHN;qzJb#*pJ2l0(GJE`!^QfE$3y8HUW@E?wKhSVH1s{D0#;QA&NrdwoV&{f(sAP;%(CWSYq6NFNbPFtS&^ znl-1bmUVErzh9fQj|WotKp0|tFTBIv(B=f_7A@cJIXtSzKKs8blz8@Hkp_RX48Hg( zzOevW&3%^`p%S41xQwj$W?$f1)tS0q)b+y^(KprF+?q^d%g zA^K`D3w<%qk;j+TsZpI=d?uHPalZWmugxn=!{CON1KU)LYX|MH@RTN_xJdg)4 zq9p|J6!xz0N6ynTk4Tvi=enGa6Dg@Ed%p*7tc;qTBf6)qTP62ah+cC#Pn$P>d|QI| z?o>q6y{H6Y1De!)qd51j>-k^lObPH$1`uAbbtRX6qf%!c4_52y)r;@DAA1=#`>p}# z)|>1?so^+PKovh#8v=M;u^lBFUn7q>`;T-Kw@<;Wv!{zX;Z4Dm#KVnn{hl5V$RZ94 z+~5+E;tT|99~%|4s}a`p^>b3ZE&#u+{lp~TnbT(&;Nw_RBMtpGj`l{%Ob-D(23|7v z!i`LgO(M=?U^G(Kmk;XW=^~?e>SOns2v;aE3hseePgkGNAB}RJ`316uGI@ z-0!|cziK}|1xPsvm=XHymw5>f4aZd||H9=_Xd|%lcK>#uarmX@!ucM!N3bi;hRaxN zSN|ZHxxf44-LWS06pIz7bL<0kA;cuy;#5UtHqAT9M8vbbG3-j#nzTX7?4ui(LgxLv zdck?vf|pMwf5?2w`FdOfeOlarW+d@=WU@3Sa;?+1-Hfar?(fXw1 z1pz7K$%2Ek1Rp#|O9iJwGn2s=#P*h5)dId5ZWCkhj{;PGbvizSy87$YEI$o)?ro!o zAAk4KCjWY>3B-jvbt5+bL28ZU+zsfmyJEiEr^9rYjmS=JyP08zzuwf^gDInO3={lDZXJIGOtgQwT9;NWx2RH;F* zmEarDZ~hF996*_-m6hr`=bf<#=W-It6Ku#(RV^0h27w&%yUJel3wmmumr`p>)wQbs z61Dcf3zz6i(nRP4Y@xH>6fvoM9+D$SPM-(pACEA4+}a>LQVy$4QqlLuk5VNOU(a^6 zlCH23Ekxk687(b+UA+O$@wFh;229DyhUm0P88|j_Bsaic>s&v)CKH^h3`Fs4ORbPM zxon@E(Z6959qLcn{X736gkt{wbgq`T^oNrZoY^XuW3*{efedM9yV<5`dwh1T)3ceC zTk71O=7K>r0m$X2`N~LSzcM#S)$2WIUMBrRPFx}<&cRv2oX;F>_B&qF>3A}E4;of4 z<>Uqu<(3lDo|`-KtDNVU&PB}rG77B3Z64@c%FD>e*fr33sk=G$+I-mH3vV*1kn2^3 zIkG><-IoF9r(SI`<4ZovpPaG){Opr~H6QZ`HF2RJ5wHcC)uYKLs$Az%m?J>y zDO3!BX>B~auj_QtHax*2Z}=1Hvb^`Uz=RrU_T3AMq>iRFey1E-UPkxP!b5pZ6ounk zvea`h&a#)Ul<6R>TmR0cqUh;ZRus)U>tjZmNjUsSfF1l5|MifodMe}&(&8$7sQO@Qv9`9B_FGZ$QVicyCXNUEcOGat8L3e? zyM~HCw_L0ZfCSjxn`~Xt5cSibAfoGt4MoH^&8kf@bbA3uoBFm8asi`@q9=I$o`)m8 zWaYhSt@G5{^}GwaEXM7dUi}Z?&~Ep5{7DaTCtw5Gq)gTPS&JAsCMiPw5J=h(l2r5M zQ%KIWdHfsnhVC`|#h|G!m~ltZ$kYhUY*4J85{=LD*ARSF>Ud>n{67Gs`V%L`?7ZM- z%_+qe|7v~2t4pEzz%xqti!G310x&f4!PNA9e&JlQhevy(J)8p5;{iR_!qh>+ugZ4i z1UGpIoCYn$Si^0#T3{ByS)ijg;f%HVnwVHu6u~GboeH2ft7Zep+MkZ=I(zod)-eyM zwe~W9P2dh%Y3`7OKiPW#eQ^-dcWm^5GW1z)?(z9LTGZn)TdVh_9t0A~Sl3JPbPf^1RN z&Pw-zce0R1GHjrCkZKYguo>Cb4-orvk^aPQL-Ye6Cm$R0Fp|`c)(88NiYOn3sA>@1%1`>jvR?GdtKBfpVk?8{*D=) ztyN^4XB$Tp60j8mA@!@fTc8Mz^G}|;u1qo(7MTS{yd=U}F*oxIql~R)rV4E?&S=Hr zku8m`L=k|lRH`f6kqshuAD)XT?98_KlS+^G{x7B-)l#tEeR4X=q|T*b%P`N=41G$5-}hl=Bu4;k=0#tQ;)Zu z(nK(l&#_qhr5)VEw>|&}#am$+w=I|aL!TJ48yRG>J5c64W4=9tRUI*87z3nuXaMlf z=lSE^qT95AKP+^U%yri^d&<^nA2%FB%WZ{_~fA6g5SB&Dm?>)BaJ0@TDWD@3OWhiT!wp`}g zTJ0><#|RInm6wtkSoH|oyRP|D#VY-GLzVG$bpgIp)f3g~#I*!TXXlI6b%W%c*(p0N zFYBRBo|t#2%(P_|c2pSjXQT)!W9Tg5%#aZ;1nfh2YF}J!0Q$5BF~$~#Xr_IpJybx4mQ)gN_v3gn7f37s5vP@5 zWK-Yw3>tHO1%N$VIVOJUK@3oU+tr_O&O-*O!$~War(5 zOk5(q1g;`z7e+a=+xlX){*v*f2mA&PA@f~^z#$q9>1?VAQ{jY!$i!F1J7=JjQt0cE zCi3xrj6reOf_M(0QdUPPQYGQ<_Gx5??#~VE^b4j-t-sv`A2$K{U} z5|37bkN#jyw$VYzf82VT6Jj`f)bW3P>!1PkgaX;9mB$Br8cC`cnVcMWsLA(rPOF_w zM(9-|PcS1r>4?@NGg9lQ-|`ZX&o*M1lw(=0G%67Jktb{1ITjncUx57uL`S}v1Q`J3 zX3cEvJHD*#1#jzwVbZ8+PkGM6D$$n~yXxl4j))U-EmhotxR4Gwp*2Efha=j4_ss7p z?v#=k2G<+Z3%pXC2KO5mX6t1O^9OFN>mkyWa`Fhoc|)RCJVfXa*x@s#e6lld~?tb%1@CTG8R+zZtTudFbEZ`|QA zuDpdPceYjmDkAw#>-!LX-wpv%5!7L2@8I#*@xXT0+}$a=(s<^2RR1nv)btK&ZSYyp z+|m=-7@e?`7C8_9$o{Js$>29!o}4Sq=8+Dy;|wal?;TRnBmg<=m)$z zeP|nS&4Q-r77!rYwV-$amanfJz5PXmS29Fs#u!`N;`kp+$oYyz2e2ze>>oogJ=}g8RWW!|%9|;ZhJQN&vl)&EgQp`ad-htb;564&=v--uy9Q%z zPJjDCTFu9(R84yuKrWF>Gq+Xwa(_8tD>#@@JHDX6{a^B5LQ!SA{<%|?awB$I(fXHh zqy+Wm*cD;w?XWL?EII^&A76;pk3CfepfLks`;8iw8%27=Au~efHmYd$vLjHD@4Erq ztKdImx=btZJ;0yWd7AnT9w#6tJZLo6$k9wIsB1AjTB%GtN;hp7PeC$6jpc47{XGR7 zO$uYWD8UN`6PL&x_w~V!Bfw@LDoJl#8ZL;}=!u^z*XBo_kguqm&MWQe>P|@56aSxx z7O#A-lXDOY!T(jvgG>o^H{1RbQilPD{AuuQ3o^92;e^~w>|y-R$|5!*V7V>vh+?J+mnjPwt9ehB}p~S-%e0l4>x+vCCa4$RDJrhxExoV z7VmtP&@w!3h4H0vgZr{)?rVQi9v+&0TrYJ@2qgKGRrkC2N?_Hm@?cDhwuGVm-2k6n zz8BNa1y01CGV(%OLilArA}|4gir=!Oes?!y%jP36>k|);X(mf|>Y~xR2~FIXS3o35 zo68*a8ocIs-An<(m{YHST!-*}ddukiAM{(Z)p(j2`ZA--pP)yBb~K(;39OENC!2f` zb9lhC$6U(zyUuBx{db@YOT*u|am#fC;~vE7rgz@(`i4dO+=H6Q+Gw7tWqt!W8$m4De7W+Zkn&Xvhpre?K zK6~|8mzxGK5A2{EE-frR0H z+h1v<17ygoRFv%!mGkJ0@dnrv#3FS~#7pP!JB9h2k~yoD_vW-3DS%Ha8dSD1F8ubP zuNi&+mQ>^pn`07_v-V#F3Mnl*%^Po3m68gM^}YQR`ssa02zQmDNvxk@6(KyhsxC7( zK&85%AXalNZF;qdpRJ0qcX(IIrB!kt{YpA-uI14q&mW!MzQHxht8U>YYh9ByRnDMj z29!~J(3SScw~CKFct))$hA7YN<$o+0G`4Fh1`9;5pCR*se#41;^_!EPyj0H!Uags#Te2*-v;~VapZaKRO$bnPLp(%2>g#6 zxAi6!&HBlE%iicO3?!6SYR+1!x7;20&Wn+|Rl=W`^TU_;MuokR^!AO~O;Mhg>qMBc z=K$=a!}Qh^>|{k}*a%o5xo_iTZUhOsI`~+9$7I+Kl0ATXoUpfQp-TTJhZ=pAb3oPT zIoPh71xC1L3z2PsF`PL@_d0td=FF`G)a$5CDlO5d!a9%k#VP(fXz%<6XX+ z{J=EnbIiStY$=qJfR+K{X%2ahh%E|u@ia=kxKDRfWgz0Bub$9c=PiOZ@a#5u2gMTD z&hLOxrF4AAP+gT`b0C&&{$MOFy(32Qb<3>wBiBr=>I;|{?$M3d4zBO*k1PPZWIg)o zDDLr~tNT=hzF@ZEvWZCjjkS2|J~7hIMizF$63=f-;A{ZF0IvVqFa4Hz+0}W} z?7{2&jfw+kPlOmfgvu9}v&m|tpS5?tsV1+T9!iO#>&5}9OPZqV$n!9hXP*OIRW8;9 zNr8W>4@i5VLSr~T@EajUKN+Wde7VL)zPo*xC5pf*9b_ zPmShwid28C;2H?9NsckaDszBV7|!1G;4jW2(P^@WT?8yu89lP?^U|RsDEt2NmUkM2 zYRA~1AA)~yG)MAQ{Oj_QfQ&M+MAS{~OI(tPX$wrCM2xnwA#AkWr8N3`X|GW=XVl)H zX*?kl9+GXNW(66d>IcQBpqp_kk#zP(Vp{_2OIVhtRy2Q;D7w zjod#5tcIP0gcw8=UA%ZM2n*=zq1erk!}Z`KiDK_9t!EL0IJr$KKG;%Hz*-%>B+Zjj zEh@U8p9q5n`AjqLu8Teedv*Bz)vQt5W0|HRtg_H|14dcxL^V;P zX$Ux5kQ+})Ebq7Eq{RPW8@}rLR5S8Sd4}Cfo>bKuNQkViOP#L!5T?b4;6n~WAo25} zDXIl9v5dJUeglWOJCWo#Jw){xGwVK3fVyxO%ylW_bQg2>9gwA!iTn7Eh2&%*0*!7p zAJjTWi^lqM&v$=5N&duCP5WSP5jeU>5@vj#i&7%AZo@rhzX`MN55}A{u}1Lq4!VAg zGY3V=<|t?d#^eDp#bWW|T(5AaEk4x*A|pB8f7N1J>th%pu+E3QM9N|6<|7M2dpg~% zV`7cS8;&Y;Dp1_n;Ox7=(8Gf}dQC$?AtpOx1{9dxAieoRSUR<@KlH2psC=k8u(e7b z1bzQPUr&P?r25PZgqU&jiR%fI|92`C8tM<78vg$M4-RiX9$r~{Ps*D7F4)2}n$|zN z4%oi!O=3gO*;xl_+W}R{g7|Gi*=G8FAiU5=D?f1!hOTxwRbD3+y6Qa&pJ{&5L3Xc3 zvD62g>cvZ0CSPu4&TWi8Hlj3+w>MrNS`%B=ENdBKb5xy5%qcJEz#@XeE87&O3m{3Z;j3Oum|zh zltg`D@8iC9srL~>Y?J7-NsdY| zr99NT`If0Q0qu*9vN2#}xRIOdN@0|UWF$z@1@Rb3ZcDhiR+c}N`@X>ZH?kz7*FaO@ zjckv&5v{D8Rm&6rO*H78-=B0iW*94dGvK1(@m`RCd3c?nN)PyPezQAGR6?Q94Ukyy z-YX687RD4pvt$fvW=o&MoJ#t#Bbrt`4feb~;6v^_87CLn-lS5U%bkA;HLfe+btU}C zLHi}e06wrXZ91k$y&kDA{NOtz)L-#@Km2BP7O$e!?-_xO=1XD1B^gg8X4*FR6o$wl zrdjU`Y=OC)u0!M_;3RkIKj9SCnxI3UlThSmlUxxlr*~<(f^X^OnLmDAZ5m_xLO@x3 zHEW)XB^dwRQW4?lVA7O~jT&J2v?jMo8wL)`1fZc8^Sr}t?^Y{b_;~SrFwmi0o8|&t zKwA>m^?psAG30v+_wI~(EHL!3B%mI%^sL;%!ufM2x%^~I1gIaJk|Hf07IUX>328^Zy zM5S{MmJc_gUYn&(gx(11eGOUu45YkPumI3(iO@rI)_g52vGN~>@65E8%RkYQnZzO3lZDc8 zc`v*(V_csAS~}`ATFl;hR1bTPAaay`w0iW1<8d?rF-NW)lA0Z=4`F z+J4aV;18$WR^Bwe4t;s-L56;0Dufw-M~?x|EVq8Wp=0<+4?v`mBBxTU z>LYBv#@T0;KTNtuc_qXZI+DH-Qm#JKxoG=aR;P*!bpxwzGouO?EmeAiAqQWK%dxKD zv=3RVX4mq7!sl`kuB{QS#DX8u((_k?U9ar|{ro~7gd*r?sUm>yh}0GmG_Y9e+lQ%y{VDl<5$e;12*Qo;fh@`)r(6(dEcfmHn(8Wqh67U{2F{<$Bj^|*Hb}5I8 z={<+1juwyF8ST(B+HK46+p@a>@A0+JzJT58WXnW7KKIK96R65i9#3q5@5zPF_sw{i%8>=rvad)=>K-}^J zS`l+D*!xCpqTZR{>IkxzRi#N{_ZRvVN+SC((x7s5Tszq2dWugu)+Z)0`pm!rhr=Nh zdGLQ^@aZ|$qinDwyUD{{kAwRajnx8%ktPMeOo#-)y-bZ|MgEs!M=$mIGX$D?)>t5( za%vJt&8Ib5yjd|ZkZ;B4FO623l>BP=CqAm{m2?7skZY|PktPE42mbERPhIEL4ZJsP z&$1bhy=vSr-ZH{s?(u*Q>)z|WufEC{OYZ!4NxO);(%E3cFCdWDw*F1+RPP5&%>&G& zdZbBO&8)Q#qch6N&HCCnTptHXUh2A&-|U08?Z^BxJO165{7gDO6J$2|A9>yAkpse2 z%h8$Hrx$35Z`YIZXAe;DpGkQc_gcW7t6mqyEu1FGbimc>rPHN4N)nGz=V@#4pkB3y z{y(a_TVthf_`~|+LwF|we7D)R2Q6}b77E-mo=m4;?*Kz8Ps&!Vwkiteub`031W@>36B-j z3L>qR9)GGA3>g>#r}?;>^Vi}~t%YGhm_S!$MR^&6e*qU|J}R2QpJ2N{G;5U>Rb;ll zDz4&JIH6NJp+Qg0!sFU*c2R1je@y2v6Am|PD+f0H%!&xIx%-X8=9gipvpt=iY0~o9 zH$gy$LF~>O}P(Q=}Y4(HAb$un{{s+4u`VE93 zVT?a|T~m<51Xt^;5xTd0@MElD-P@jG*Z=vznUOL#DlGnd zEcj(n_k9Zn^B*z52f02ETa#ftvNmgMQz-Sm{=ZIN%#f`iMYI(wmeAvZM9O%r-l*|Vg8IHu zO@35|7Es7=VkliGE=^hL{21n$_JGV(mt08THM8RD@19h~fIpUg47)EqfAEJKVH5Po z3W@&ev;@;u3Qq{VK9V2S-O49qI~=E*Z_JA5IGX*9^6@tJU+t`~E|rSBWm(P|d8`s) zzmJ^)rDDVs70}}V!{dgk9qgD=mV1DNKj2G$bRtdF*J~;u$hjE)2O{_*lY3)LhE^4J zu7=c{E5SQE(=ps@E3>DHZf(ct?F);Yo&g=kz(nFA6HPJo(pn{@6Pc;LDdutfGN8z3 zVfh{$*kJG4?oRf5x|dtR5DvLQ6eov zB;Xe8EAxXyCv^^ykLMI%4Gpa`PJgupxcgUn_99(virPzUJWaCF&r`mR6&4j-6&psp zrumd^jM~eOh0UTd6@r@oapNb7ldh6TOc^KUCJDQ+6R2R}1(Moc>tS=N5I zpLmbBzOdOjL}<3fGq1tu`)_RoxLnboLa!^sSlC(6v)^{;sdfD_SiP$n8EhDYMl%O& z`Et!6=AmXoO&W~!`{8R83dLNoFAm=lIMdt7s-F1BnBEL*4^JqTl3Xjm*(WK`ZO)0MpBpRKh&|N8wy z<^E17TC{XK^oouXjVlbiv@qppQRDjesQV>LKnQRAJHE8O#p|vYKmKFo5{a2kdufAyR^lXinlTR4 z(xh<}%Cm_F3q>CpVrUx_y+iqOb~>OdUD(4NHxtmBYs5;vY}v<&BsssTt^JnVhk=_01RVq z8KWX1fNm5QJ%}j`Jefyh3;I=>B)At^4)fo3rWwfJN}mdE*U*Z9l9JeiSYTjDY8>~_cW5Mc7axWA@kJOE5IAD^H$ti-YAv~3>46so z=In$Q7VEtYDPy6f97!tE)ejt*BMg@@x66=efPlKp)Tg#b3wRh(9=>x(i*~Fk^2p)by1C#@ZiH`wRb;` zvv#+1R+oxF)xTF|_M~^`CllJUIm=2>@YyBI)a5K$u3AZ2Lj=J%azQr7eh@< zAH}FuZ{sdFr<}x+^dj@7g{ThBJ%?L96*U0MFXAFNP4>?|@KzM>ThO$U2$UK&F9N+Q zXb8xb0fEhI9j;*`yd$&WR>G&5E;i}}ZM<|;PIDOBF8IzzuZvOw`1`q2OEH&m(&mnN zs$f2zpRF&Rl>B6w9xS^#eSz*omdD%js_)*PX(a`g)}o$k@%=LlX}@HHc)35~psBD6 zOa@Jj%jM+l(}72lVxDz9qccGQ?J^= z^8YA0_eZAwKaLM2O3bY!w;@Fzw~$LhOvxp=R%5PFE_1)%Nj8#8$=r2u3603eom@s^ zWNcWba#@;IOn|Iuh;YWcrbzf^r?;xIt-zpB*6+kkV})Mg^O27jbxL- zA5b5!e{*r#AsSmQTfR%K0h0|AdplBa>5^egt$T4_?CYKk#b z$4^!;-sU7%TKThz1>hjmtrRib^!UVg;8?B<)mIgSvL<4(V8*GUtSg#f694waSPX6Q z#-~Co

    RxU8-HpWNLA>k@2jBbroJV5K-Z?!y=?@#;;N(zfYAP^8 z%?#xpxbTqQnKsDvBZ0|&-{yGoy%S1$C?_PftTH+8J#00Gac69bQi1(;&R;d3T|rVROy$Z+g2%M(a#ti<_pyW}*h1y8w+%QW z=coxfWCP=Oc41ScFt4FF^uFRELvCn*PKX1tiX@K6haBK84pUHEvLWns!#*%LFa0F< zQpkk*DP>F!bDPmwpS6@*I2*nD-)P9F2halg%rM$we^Gft{8ur66#0q)^zzXi^QlHl zLZEC93oDtQhtQWO&Tgvce8o#+012~F$ znpPRdzs<{Me}}YW&UPFgo44s{xt8%M{|^nS?aFVbUwntzWBEE zhssq&l}~2hyN?1MB{grMA}}s`YM;cV%nbj z(9arvRk^hLcTjljOSq?S4da1|Rc8AOO7n`?BDwK_-py2`+=8}oh3xaG>w@%sloMvG zu9`M`0Yqv=VRbex`C9&)2qy5d77Smuxpe}2o$Dr_(bZ`6YP{u1?rxw4ct6kJ#ab>G^@oIq;E!Da+x zfydnZ$3Ero`fwZ<>^zkKe5ekAT(4iTr#v9ollLWPGNN^XG*bbgoY~!E(_~L=+n!ZQoBgc= z=M)@>PuOM|t43Fur^R~>C~J*>PcsY7eV|gbK+yo2N4)~>_JT_>9gYkJak=3Q_98oI zXLap$UR3n%E>FjC%dB76helv}iQaFOP&1=kg$hO?-3Op z?;))?oF8;+$A;bR6map`-DezM+gS9|2p6|X-KLM9I;Jq%uVOa3KO>l7f6?JLb&I<6 zdg_fYH95>AKOMp=iHxQ87ZNV>+tIKlaN}CzN7)x@%`0ZI=R?{e#7Q0jMLgwF5=Io# zes+|lM}JnMcVhI6<{!%bIG2})UHjf#N$xgFM&SobFES7C6$SK3i!m*s2IXZf5yVI` z>|~-Xg*#O08_8)~TwU-!pNcu^L{Svx`7U{^qQUe1zP2Kb^>pG`Wt_+4b-h3W`c-n_ zM<~MvnkSJ7)P%iWxzHxY?ZHpsEeiA!!cO|)&P`CY?Cc@rBI z&aT%N2fg#i?pr%MO9nf8d+QWNO!?@7+x2db_Lg7S!RKlL0P1mX>{hXOI_-EZ_JFa% zJdt2__7T!QEFz+aMo~2@s-SDx0Qxsv2Z%APa)x`gxxNjE#vnjZ=`J0GC(S)m8Ntdm+^&1m@4NGUM6 zK>v)m75O5uz*_631(a_;}Nvc{55q>^M+U`9bmh>uSgt+7O(i@Ih5Jojhi;!hb!Pf?CB z$*L@$h1ewJ@&DSMxS-x%!B$q1Y}=U1GK}a7uQv;tM^lQDtoOMs#y!2_KD%w`c+(f` z|L#pxE@i~XsLO{k3L&VE+f~@?;0*kPIs)bF-8?365 zl*D7pnwBW6_@3=m6gw_aZ#$nmh*Y2942qbZN4AW z=ZC>r-Z}W&a~T;U%bPYKG3ngnmR$Yd9+=vuP9hLh&Kx&|az31Vb}KY&r#tqb=7_5g zv5ew6SdOW{cry#~7~Ii52>1hN&|qXdMVSn&7paEC>0rd5^^2TpLT#YJ(yT7FP99o6 z6W`(tnlM}IAvD-GgmZS`bk@0}6rXc)j_cV3d7T|Bc%tNIB$>xdGNMMZTiWok1`)%Z}+iRzKl_ibp{W&H5(>Q6ttSvd6!foG13Jn#PS&C5Q5`Q>Jb;vGuTVYnYnX z+ZOj*u&Yz2Kb8rUsOkTw#vjj;DD)#$aidYDqxH$n0&TgU=>H0JQ`sc%ZEGgo_XJ3yr_|H#=z&I5x4NFU z&bv*=`HS1NNAD)Mn=cAQ)3iC>@*wH+^t95I{ha2pJW}bw22)X7H~KHKD7R)i)B?B! z&75EWrP1CKuX~8I{9h;m)xrB~GzKto;6JG?c1I;xr@gIX7!Ob(ER7FyvV*Idrfc|B zLg8cxITx2 zS*F6|Q0ZrNplrAZ(g)?#Iv5PZLRy(rCj%Ta1j&V5`*c69w4v?|7VGlLH8runKOAn> z1NIbT4b{>GTTw|XBPt`Cdtz3JH?{L#F}+jJcbAE12@UROEW7(rPSJ4WI1wnZ7m>Dx9}d&O*%FwZ_bXy z9v$ogome0A+Nz%rQXvRv-RW)_0BfyBH*{Y@cDC`Q^XtnvMvha_LLDOZpj@}w4d{wxY3j5G^h-9aeH#_&2OmAS-+nr89@$Z)>m>LbG9>cj4HBi?au0e5|~Q`#7`|}tzhz+ zUtFcF<6);I6IemkS8wQafww;zc-@9p)+xMTa(7x&(kvvo>8&ff?vhgtFLN8zR5Sww z^sSYl|C)8sF6?Ov-IY>xNcI_z%-W;Efwc&NqHm02&NeLgX#lT< z+7tylSFg8ou1iUu=LZS%1>)j5Ipg_10Z>Jf!W&rLxI5n!jrH)V6NHvAXp7H(n1g&^ z@)5nisIId!qABwg?t7aZ<+(R=9D2MARQ+Rs5k*_{?&(Au*hWm=3YG-c=hC5JvKpBE zCX>x$%Ne`v;yD!!b=CP1FO|W3LJRU2<6PA@qP<2_B$o2^5H=IMnqu71ghxXwjhba( zD*+FBLv^_ST-wT08Gr)WTqI_0I3Ln+V@8v<_wK4Zkoh}W^aK7l^Ua6Td2q8%mBfG> zrE7lLB|7qy)2F)(VReiH+)+M#(?tgJJsba9)iTrmyM|bas@k-%K}oca8MK1uk(uBv zXXIm`qts;oz1ogfknIYFmxcF-&T;jd5s^rjfV8%BucFe5K>L8Zz#y!EetpAWRm^ss z02G{BgbWatc9(j94R0UVF#kfO5&z`mD&eF781k=8w#|4JRI z(be=c96xKq1x@!LOhs-=XHm&XLz~xAE=FaxFUsalK1SAqGm=ZItFS4mHp+VZ9#|GV z%=oVd)h1O4$C7&6f&QipAbgR9iGzH30e=8_7Y*RS}}>zt3ODk_$| z2rnr~hSiWEXGpq`rf;cN*3HUO)1UF zA95g*&ED9#U(aAK3NHuje-PqjHn1}cvjA0WPDsX&<%mG)vkR<=;BDN}NIF7L3ECsX zrc7$?9h3S~ZRi>Rdeyi@auef50$T?!e5+PS#hJklmv=0g+GW)gEJ`_1zDBt(&bf%} zRs>}^TaHk9y&Rn~(9r=$E&T?9Rd&@jT)eJ-#fpopgjr9qdG=-_T4rd>wYdpR#w~9< zciOjM-NPUeq)=M;w1BYwV)Hv}nH({Q?Xz2a4Rd2-RePAezj?U(o9L`@-OJ0NP>Obd zBLY3ETdABPT7lNq&(6ffJVMh$49kGxQMQRgqiV>Y2KOvAgG0ww-bq8zioD4lp;ZJ! z==f0shTaIctOBohzrSdNnnzlgh?Yej+M{VkdmlO59VRS#!+i|qCF?F{M)MHN9NPvp zV7!I|Z_0vhAX6bH4GOb4JGJ*;0c@~+C9$pa2VMaHb)ws^X*zai5qNdS{xdHRwkumq zV+Y)VrqTAK(!20%kHT{1;*UrMxO{PIb#(`#-PYa##Dls4MxQmW^uPVzn>gK24^Xcg zyr+be*Zgbpi|b=><~s)9LLiUd(9tQ#t49m~u+;VCkXVgkGhP-^q}wA-mRvHzK>viC znZwM?v(Q>PSuOvmv8Yh}Xm+qvpTH~tmj`Oj7neO0-LyIjD?%xl`LjfYIZ zRMfc|;N4yjy!b0#xu~*PvzEdHQUj<3XT-&epmhJJkE1?~^I_T>)3OGS8cOLb*>Bo> zHHYy-hB8P+M`{66nKW~_(o&=7qrHhGc}zvE(wI$i>pMFd8#jgegi6&^zkAu-Vh~#u z(x&Qva>$Y+ifP2fH3~yx2cSusHEo;5IMAwJZ0}n)h`0imWHx1=OR)!AOmcDTCu91j z#^yXHVWN^q8fJ_%WgGAacsc)$h&K2J49_*s0;+bvZm|$F^>WNv@_de!IC3^Dx-DvZ zI+W7z1`!#f1FZFKigC+bChiY zvLd6~Lt7;M27H?c7@nh}lPg{fjFYUF1Dc|k`Mz*`fC9J7>+f`!`^u4f{pV6k6@B5pA;9(m7sfgGXzEe2s25j=r<>)0~=%y#m zC;4T&{sTkA0I=)el29l@3ykg)0J%emdM$I0_tsfc`L7Zf3l7kqg3I4-S|R&o`r^CB zOp~2r9aHk2s(@$)4%fHtnc0}K=%tFJ)Q^RxeQmHbKIcJAkH+aun8FZ74K?pfj>e_R zrcj!7{F!b=hiRuyE?esN*;j7666|Cr!_$j$p3Xo+W%&|hv+SW4-;cw-d<=}z5+!?F z=T~_tn{D5KP*Kwhq*i594)+e$n5xr_b%n(QdUX3Vqemyi;;gei0fs6^L82!@AMH zG3^s`E*IzQJta^gzE}4qIJb}qV6qvF``2p1hhA9A0P*vmVv@zQUQO{Rfy^mk-WP_nv2dSkBJK#WSL1^W-q6tkwehYz5mV6-`?n4cA%FTHflQn7F-CclYX8v&b zFihk2`LE^s^O~8Kb&pYwtk6Cq;htJe`;SDE2mK>u3 zAWLJ8&L1>{RETl}Lqyg&-6aKzxqfY3=OE>obYt}w*~*4@y4D!%<8>&v7XY7Gp0WHa zIqvbr((|8|c$?fD!a!O)VTxN5!ETMc>%j348YqwXu9V6iQVeX`39r%#nzYJ?UN42( zc#NyX#r?o)-$h~iYT)1V!>&z1m%yS>#F|B;`2CD}71OC7hGeh|N z0U|At%`R!159)6ptSL>%`~1P#G%cSqz>hRBCzX>s);ia8O8)6y)kf$eF7Nz>TJhee zRqru-zdXGX`*l92)0^+%mqcFz#}|9Mm&$qX^H{Rj!B$gu4l4h&lG!zBte2ku6=jU7 z0$wUwf2Z+4WV6`d*a_a&CpH?h`|>m{pz44Bs@u3s zKJd3B4Xzz{%6flB~;9|Ai2mM14(qN=Y(&QLAaZ_osC2l5M=r-{j`;z2#f} zB|dSnFY(Iz7Q)kFqIv}fDAkF>32SZRMgwJWm|);381K0&r5sLOm!>Q>IyJe7GoR(2Ap2Kx-7%if2mxj_ZM#VR=!$=ny)focg@I(wx{(*9<8(tBZ;4X z(kO^O@XKyFmNT$@pu5s>xFMRy6yZj)@h95|PB*;Cts%eQu{LgUtfukYVcZcE+V8GY zpfp4?cPXuIh!7F+1>nBwZe^J$JG@O9I(a8mtc_Wn?vYFK0BQ;l3$9z%3*q{6hrbex z2)+AU$r#8 z3yEOZd6DVSb~3nX6H$qtEFJBeJkg^?(WI(@=8h_g8oND-2Ad$boAlBgp1}# z`-Z8fb}GDQXFoTNF#&GC=IpFG;CEc#JgEn$fW3*A!dCWleDf<$($=#bW5OY2D~rm$ z;dshnNc7R_J4OphnkHZJD_S3F_fo{hgQ&U-=BNhbkIX0s3Asiii_%|~=334EITtyCsJ+szJ)(I{qdDq4Z=0GGho zk#wli;m!wTF&?BXjCx(IN>caW;E+lDl6Nh^^}GS}+5SyM{QBwvV4VgV94sp8mgs!@ zFLsZpYy5^z*;vdsuVBKpGN*ud^N-s!VyL;fIhCk7PuZw6(}~_Gd1$k?w`Bv0IaUX_ zZo97|FBdDPz`(=tjqwqs8C9yLY4h0Io`|g2v4-VU}@C zY6He*dZG7*Bk8AU@=iqbgW8VW!0s>)+;rMua?3+ZVsLySBEB?r67HaP`}F zsK%C6hT}~Nt;lShXh#QN$4}%eFl`*Dy-`HKH?+fNbYc(AO8QW4|MfvrfI`u}U&P)n z8enuDA$>az-8*>8+wn~gU&A{ed0D)GB*|eGOqa$4#yk->Y?{Pz!IQ=q*qqmo>q2#$qvYNNMj)tUCx0^zhcUOzTX1)S>o1($(&OQ}F^Ea&} zMTK{8M=OHl*i(UBkC`LYK~Zx3;9w(2XRMV~NO{xM6yBe|18z;%`zWW}W1D|LhpU{j zZVQ|CLQ#7cb=dT|Zt|)oI|mxG_CztAFcxR%A`{ z@QUy%B*5--^s-e8Fx87&Ft~4{bSC=2`fccx02dg*!N~%9L_w}(>(XQ((2hUy5+=9!ybzJ@!XU)-dM|{j1-!%TO6_A*jF4+ic+FG3GZ(2Yw6-p*5{$0aN zPlStuGcWMUpKz=ao~Tp6+KMM!NU6_CvV9SlmTUJqIDcFp z4DBTStNX9zrW5>up44Mp5vn<7g;{^B%DBmj`df#I6LMYG$+Bl`w|q?E09N@w@-2Ps zNCXJvhx zvQwb58d;X4skb&DA!LjYJ8y2Fzw#!0D&SmZ@FdCp8FSVjq*pQec|5O{HxlXjS;~Yr zfC_wx)of(0-*?&~gfsH5>$Bdv{$)DL{R$}uPN))Qm3US)ezD2+T8q*6i)7)tK;=Hm z#=J9pU%^1Dm&K^+d7(-iNiLyxjOBCAviW6PQRu8MBJMM}Z7cHp4gH_R0JQr`nG^K! z>3u>N?tL>Qe?mI~5+?6Q4? z#KhBvKApQni=Q^{Xf06yTDsPT|LU~TY8bvC@O$y3qlgw#ft7KbU&pi|Y{pRVMYN~Q zciVnmQ}-labYoVtPHD!U<~RG=<<+hh@26N9JAF8SJ> z{N)T#ps!+f!r}PufKR6g=pY%42<+kKS^FM~7>K*-)x|6&@Bqd_7D73{#3#C(3vTuu ziS_A;4DyiX@lg3Nu#b#%U)cjl0xkB=Z~z?@77>#+;7hk{Y&*UTkgO9?9DtkEyb_qZ zdtIeBa~oX7RO7#!ew@Mq|Ll58aRM#aj_S*BlIx+XnlqREv_Gsh_y7)69mrIJf>VS} z5pQQWtE$Tul?Eft$7ty!U!N|6*Wl7%Z-U{2k8>Ah*PypuL+e)_M5l6IF;4Zz_kyzB ztnO3!BYS|WUr0;VtB(DZ(AkLxh(Q#`Y#8{B?OZ?`s|%N?)%|WsK~SJP#l36)o$iAD zN@L3fw7si~OvuuVqa!!&S*SMzWKb2*aZffVTz2%|=JNRNLz970>I$7k^JO9?f3s7X zbz;MGip5-DnH;F*U+<4WPtn^i>g#@1$pY0LFGLkjdAVB=tg6%+SKzX@NKeN(YZMPt zA@Mbqz&ozYLq{yFaeKSn8~`f0KEfkuv~=_0r0V@y@#9-3nhl~h%ypx7=WBEi0))*C z{yqE9j2Y$`j*U4!0z!YQ3Pobv!{Xv-3V>pn4~RUYU|i8D<32q6L?CK+o!KmpW9|ax zsFs=N2lOpqx*r<57ut2QB6tZoo&TCyHi%ry zGAUXiYRfDA>YgP6h}k3h@t=+ZMV|6gnazmFT7x+chXE%cWQIOaoBrDht1OEC;^~{9w#(gCl9n2r?EE?eEQboQ!qsDn2T}QLV>|&IP3R8_OHXj z(Oej;rDw_y=-pAyM;v)c~S`-SwMqC*sgXt&s)}|`&VJ-L~@`@ zT8FE@iOj_f4aIFToq{X({rXkSm&Tl}BM_c%H#T-0ZPoxYOCX(oe82?o;c0~Y9}R-; zcT%0M4WGH8I|HN`UT*^rQ?4p&Bf@JyE!aZ@X^HnsN&NiNm*bp1^6MEn)Py;n&#-m- zQ-FN4z*A@NxP|B)z#nz64_Jk z%dm4lKBg_|X1h%`sNVEORs6i!!)I3*INWqi@_&BA*gBG3q5GfsSia-<-DzzUaXL>uQec~g84DpoR* z(#nR?l7IBd{MsA@8*}WNyOy_nkHL&KSF2q*_s%~i{3bW>4C5i z81!yI(uELiUIJ$}Ul4*cI|#*}^{hygs7!+k^XNz$2-uW7^u$$rh;T(P{uD^L>z}v{ z0zDhOsA>KlTHiqg1iwUwjb*deK!2f(rWDC%vX#jV_n>LH94)Zd1d)hoAY%BY-G|o? zt}Y3j8I1Jq@gC%ij2Dn|4xF_%ZEwEbR;Zz@{bu2eSk}>i*4vivN78P-+N4{Oa+6pD zs+#}uodTr?E-O4(3!&{43i_6|Rh+~73B^c$)w-pagB5m=QQ2+v_h9 z?KdUZSxr8}r=v|}t9uVLVu>_b7Q5OtX_ve**NW#knY`mJe&Ke>TxSad-IZpphZId> z=&x?iM0#PBntn+}QWa0RU!L;vKmvhOtEU*D@8BeVVRG;^$=;YfN%vrd$J|k;Vn8)b zvMoy_p`#;K={11x5BBd0KTmc$`{SjD>Ii~joy94#pVg;m?wI5}u`vI8Na}4ut`WML zG1JyDh=!f8cI8k+)BJ_Fka5}vzZYmJVpPg4l|X?JhVR1PbcYp#^se>%^^*} ze3fNzK6f6$70HL;zn)n=v-jkHe9geylCIr5C8?T0iNaui&gh!XN>uh|xdae~@uQI! z|MV!$V1C!6Dq1_WTf;$-jK(u}SE7V)Fi(-!!EJDd z?Mv%xep(_p`uzIJ^eI}HEdH9WjGs<%qnIm0cr+SV+tvVjO~>Ok$AfZ$^J=S4plO%$&xTzh?(+NJq1 z+?wMJ@na2cW#8q+d1CC5;{k0uokwuGJ2J$DvvkqmmF;YcddniUegrK#oG@T z<~Q9v%Uf!zpTj?1dM%~O{{u}XcmIZ_HsUvrR*r%~_ttO#s=I~J?$tKjXou4218VQO zEK}Ww&t^wFla&E|NAgW`B8mh1c=2$0e>>c6o7++0((|2z4Zc}bk))2p-OC|Cp`q=O zQB20saz|`<^zO3w(b49vn+R@pTAB8TsHYRPvlUtL(cLHwHEwDh#R1PkMRUY`WYq%$ z9nZF^N)TU8obVU^#W)$sw8DRBHa%^c1B6Wlc#sh%XK6AD@-Fbd%0z2BFAU_*z2KhM zb2eHojHXR0jL~*6YWfHb<0jq`L_O?+?x~73)^T}&;-)H>`63r1d53CM4zz0AXr5IB zO;v-rS1%r-shsc!(}KlQ)}{O4hTx$~p^Gh;zz3bw1>?lG1WM5gH_eh!hMyWqTjLGE=fj2Xs6NDB6D@0L0buXE zf3+Oba4kaL(Bfvg5ibb%4Va&LPqG|LIYzt zV<;A=ad0U2=q-85Qef-Zfe4@++7lGsRu>3={OE~{aMHWRt%d2`p?xGNKPXw*r)kS4 z?ytx`7z7p6PX&<-IS(5?CHmkE2S+5zASWF6ubWBnqesCnMJjSaFhCU!zS`uj;=&zw z-wT6PW`g@H>rfli%wIv;;1A6D;nu=^*f0W%cIbnIGYk`RC)eu!qlW9N#uc3DI)e1R z8@{CS#nn~?6d2D^k@by`t0#XJL=Fb}+&~(7oK250ZQ8coh85MS$LEmTHh)z8)3`Sp zInW#V#It;M8qszZV5m1e1_ceq{eXUZo&G+AzA zOqi*GGuwLUZI}PFx=HMuCP5AK7}0dNHSdHqqGm@ZkLAXRxWk@vM4?t+3=o<@LeylP zvRza=?Z=S*115{W%O~L7Rmrss8Jm*{(M!)y0D9#iJ)6mJHjthH)ZHDK1WJO&Nl3Ey z5cq3_Bx)tEtGSf#*#w?>T2z>;++=&>sjpiV(9KAXrPECwt6c>JXo-M-Sl>j#Ok^jO zdw*Fs2igbTs`nQ%9w01ln7^i4rJR_sd+>Mg9?V0t(H;hDXuTV&449^}5t3=jJpwR< zGP}h~S;_dyN;&1{8mbjKiB#bm*(4TIo1wT|)2@t80dTI!wW|_H4YA?Qu8C*Nzie;$ zK~uRJdK{f;W-Pe{fu~7^A(>Zi@EBcsKzp`*3)-@xZfb2K>EdDmxf3_$bq8cK5%08r zUbm>XwB)z6sBw9@no5Y zv`k0=+S^^0UHngTb=N8BpNqWiNq-~g-KyDjSOs_CDCmjIwj3mc3tlp(ZEEH5$PnrL z)GYb_U;{!^lt9o-@lg7L7+QjR6~0cL?!TrcZhj<`6TXjGZw!1FC#;E+sVGo!ykbt_t7ARVZtcNQ{fAv8U zssP3>G?d2-lA4)&IYl64z>f8zR;wVd02ahJu@#VyxP<- zTIf0q9PQ=$cf+XL7)W@)XCFL$(=vh$yQnlhT}VS5&ZCPX1zlPeF>m;+)^*w)9J*AK zb{2*xXmWXR%o5pFBS`?mbwMB?iwzGXy@r8d^Ts-g{MWbNafH6*pv zrm5w|{$BxUqrI3tuvl^225f((B|MieHoU6Fko_?u=PO9_3h@O2JH z+RqDAxF7&b+r!%cdO+9nev4+RkNTbVCDov(MAgFKa?#RJJ46$&3jecW@~7 zqO*s5y-{(u8q-+aR?Jw5^aTQ7fV%5o7L6_kYQ`E{GQi_#|NqC^_%(!Uc7Nk{KF{&& z@&56%M4&@8Bb@bfqL&f7Pd{Fa-6O~D;dx?@6^|5T(o29|HJZ_}KQtS*y|IYbFl61? zodvcqt4C{ytJg#g5N>wlyt7%jww8Cgaa4!6{gJ-Zv{2JO(XBdwVxg$*_(erWJZR((v>51pT zk8mC-26+I2NBfJpM_5~{L`KN{=Tj6Sy1a!6lzEqIdFR4_Kn0)(&89@@Ov^V?GF(!+ zJ~ee>8)p3^wlHUE4HjTGH2KCss5oV)9v?5m)fcm1YwBuzVcZIZB+Q+g~Dc2d$>%4MUd5&#?z#iC>K)*yE~P1HqO!%t_3DJBm+Ko zmOSRqfY1Rlsq--pj#P%Nhn$t|E5mGxxmeqXf2cG*4;3K>*~wj#44nSgfH4wjy+ecM zZF45u)RJsrFAe;hJXxnXegvt%v6}YupeTtNq+qulT3jzny#EvZIXa|G{5=?JV8l5& zCcpycD1Bk{Kota5zSEu@$10)8`0uNwd&2dX1K?7V(|5-k@`lEEUk57p+BbOiC6!+N z>=?j14v|)&0vhEdtJP1E)ZVq7D*!v~+h?&fTHW*lCAbX(RSn@R4X5S2L6O6Oz?j~x z34eJ((eQN{83}CTVI@0}H0QNVax#)dyF8UOD+>q@H|`$j?XnilOxMw%P9BctpCN#H zdM0(6E#3g|&b#B_M~0P;Sp`m7a6(w);vp*t1yvhKzci5ZHVh3LO9gjK=8#3U#k=uk# zYTy7-weHMm3Ao0N8ja++TATeX8FjzpOJGYv8TvGU2jk0Bts3JBQ|GKF83xsK`YW6q zu{;ed&+iM`m>Pr?Q=9*K`36_DppL?{mhu2@qcldUOM@%!^FlA^j|NGGnBihc`y0`; zlLI5WO%jN^yLjS!z|EQBfyl+mD|7m)F>UrayGn&F!i4S1ps?_mh^BS|MYtP0`}X~f zhvn-*eP{Xvtc=n4pHNQ^Q+t>XCiiot1p_z?|(Zx$3T8V6hu?RX6-s;G~Pw zX)XB8SACE~e|V!%x#j-m^;5dp|CI+mq|qB3o|&WZ-_k_F^)BKp1i5h)ZyG*bguGmQ z3wY66+Qo+XA+IJixWcc>g@^#ewy*ZpPM4nZ%Fy6Yz2y|WM#}Sy3Few=Hq6s^X?@X0 zdYB0uxd}w)k8J?myJS|Cj~JBGfM|iE-RzEz7)M7zI@k8(eKF)57hi-iGWgM`!0a!B zjh>%x0b`gIN4%qB5kp+u5XhFLTYa+pCT6|doF#a9eaWZhfeoHwX>z#LtuC&6GA$F%*d^9IO)_fT4=0I&bY{&*CFZ9?Qo(>W5mD$kr za^MDjP!lj&R9vi3LuT$C%`f6Rc8iaPbdS1cRQ$hO-`Uxl7Zl(j>ODqN@N0hSgxXrE zVqg5>;=#hoGL1Y8P(q3O+YF>CO8+!5AGx|Qui2YH2KbNKX`CF?sIbuRcGX2-;oY>* z9i+s@9Tgkf4piuZ-CjG39Ir5rSAe~!!$LsYMoUXKaG}4nGh(;D`a2(oJY@hJQLBs| zq7U3CE{@x zcrPtk?-LE994nf%{8~(0KlPbk&83S>pA3I08gLHrbLe=Y;YxQ?DRo+mZ{R#@E5kq0 zsW30L`|h_5_`3_o(wro_B2OqOEw*JFLowvw$%0VM_o}%xHzUqmW&>AuoW>l2UTG>a z`n1gDt&1JpIxEA>#-eQY@rmIY#o}V!>0+^1=Ao}+aR+!6Z7wd#isDwLiY6(wQ1P~T z>2q!BUxFP(B57@pbc@pH0aj`!sCS;KHInu+`W96W*Vh8i8|pC*w1=rcc)wnOLP$`x znLGSBYUc~-5h~>Tj~?=6QkjSNH_{v9L2kv!_UCXj1Mm-{IFf*>sRJG~*3)Skbo`)C z$dU`IYB3$4yx0}KXc<^0Q0i6lM`I2))r_U)GzELyj66>cKjVz*{)!0~E=QwZ^ot

    7! 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≠R-HBQ7|)QjKH% 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 z z=F6){1`7HRTTj30SF*b%6n7~nkreTx%!<{J2AZU0j=PHZ1YnbNV`KR<)tghkudko& zeUoDlNhzUcd}42+jI=PgLDM!uc{%u~2vyjlB0Cl}L+zOv*@eaZedRln7Tatj+J-M# z=Q>oX@_31$hmY9}upK`=BA%+6^9x5F>|ct@U-B(ca%?-P=r|2l)EroNmLpESe!Qpd zG}?1=bT?+JW1n$azP!D;vyy#E1mY?M=Xe1rbf>E#l;@OiI^x8`KmmO%$ArA;u5mz^ zTlGXnL?0BLW}mLo@9H7xE9*${HnHBnKM6&`Taj&es!0mA*+q=tX<-=Mo@G<+AGEdy z*RMeqeeMCrJryorpY^C7UQ!+)wvN6=`AdJ>*BG-H2lj6?OwrvT*)kgyIhA2mPBlNX znbOwzOL~%C>~oLHtxKE#*gqp{@?Hc!jq>N2rm}clgwJkUnY1VZ#i`vA3WwnGMb2`} zzo4QL^(Uny6WhZn1Pvcf0e>EA;>UMhVWqsV9BWgP2_Zw7|B;H-s;j2fGlBSh4zGZZdTQxXgYd>OIi1hk@sr zW|09L2)mR-02svi+!|vj;2nQo8eGh`#+t@er>!UmgAiH2)GNut~JJ{2hH@p`gC zHj1L&r93k;Q`1M^m+c-!Dz&7%K%RCnMy_A6leyLXovbo%GsXSK8(ZP>WY`|

    p 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�JKX3tAHE@`QV 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 z +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

    4}_uYiPxOq4Js{OS;IpD(YJE)Ab_CjEr*o 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

    IEKvCaxuZVc<%TS_4}$1 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�Lbfy{s@y<5W!2uc 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%0D{lmPT&d!|%9}=C)!IphpnMS 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%0DLBPRohb<}4;L26n`6z9n+lai>+HF+ zMx%0K^Ti;NKM)W!YA3Y1yof9rq9t!tlU2TqYUuLM#d*#420KZ`Z;w#Ody++KS=euvQp-RaME*uM#l6v3UsBd-?P0ir}1_VQH_=Izg?b@*0 zIW}v}wzaqb&PRq0f|dGwy>2)jJ66q#hsOgiu4?v!rSCl{3}{ocuH@NYHa3@KuKZ7hc%UFAC#J;WvTvV*h-P-Kh(tae zqHu|w7&EFRjlrF2Ruih68&E67>HQs?P9z;!zkW%z9>WGJ)X1)V#i^%k>gutsyd`x$#Y&~CQq>nmu44Vv^)Y?WeSy;=*t$ccMUQgJnY za3miIgCh(MtqewMA|9ipj|r!q?r~4rN|;tuhN|93+K0{x<7oj8_J+sGC@Pxj@^&NF%*=)B2^N}tDDr12mr!ysEw6ecz!%B=rv8%jb4U;d~(ah_V`M6x*hyXF$bBTU8w2 z-}89)GplQbbz!kQLva~#!E)AgzKnX#$DA#|Bct;&QlqrvFLN-SrSW9pNBPgvM*e+v z;u0+mkTf?yahZ@?op=DB%&Rbgfxk89jGcC3b8HP=X!I`b5;&W)~*xZa(#SjAu zjZtHRCIG#p6V;b*=^r0B9QSPBY#DYBeEi{`F*@MuKtNJLH_dAT3S|y__x%t2!{7am z|M2hsEjKSVym|XAop&PL?eJk`sWY(9H|`*|mz z%@pLbO?FgC%#yE7ECSZ5iOZC00kIM+!YSrh`g5teJOOk16>)9~QjYvi;(wY|<);N;Z z41MGu?;iPe{ET0InfSApnwR@~^xY%MhCKKip(Dw{6av0#vsunxpsbj4JYm=~6V=9$-mg$p8lnZ_)#lU-^I!n_X zrL;m7HW5|I#I>FpO*IV_r|HB36|qpRjRWgg4yyCQETswPeco7(u-}D=*`!IHA9H%E z1bWM`G8K0QStV|?&LJR+yBw{S&X+{N@f(W_iqz-)A5F>&g2EhL4idy#UWS0^3-;+$ zq?#6LHoZLCKV@N>kA|{2xWsB)HXf5+Ggn;_gnl_FMius@l&#vceZouyjLvHIR6HxO zumvkAEM#Mzx7oIqwCxzhdu%I{B^-H>6T-9LuyOGtOF%RIbzyf}DY}El)-B1_41>c0 z$qiI0`wn7c9D{n-bEORrKJl+Ve&EHXK?Ud({QVE_`S8^8t6j&dS6kL^uCUf{{O~jA z$m*p+t+KMAYJ~OEG#I5gJv?EJK~+{lKAb1IKyqFb?s=K&Pd&r&z|aj0{V**piG>a6AyY9mApH?)M+)_b0wu*W9GQdsFdrjQD&$ zxG4OkzB@{@qZQXTlAh@L6PtERJPptf*nkXoQG}@QgG0s0;c&-xwPJO>$@jM)aNs+J4 zLV(q*X5rGiI1d!c`!n$z12Ac!ix4+?b(S{&bj|}*OL_I;-A-Io1f4TP%fB9S&9vYj zjmf#$9y=~do;_=7R%(jVZz~=!3|iNs`gb0~APdN(F;StlMWYF&dU+a>#Ns|l({f;b zzUQF|N}mFQthAElS#aMp%o+5C|j%&8n4iCq?1Pg_sBG zP}McV(BosEtsAPQmVf~5**)y3lAYA{)>ecBP16#B&r6ZnzUjii?aK}A%5pql2X=QK z`QcxFA_mL-eb1Yh8wTg-heXo|OYwO0a(1S~s%~{m57keS5#)l3c^3qsDUL>V3T2a-8xn*?+001BWNklG@E%K zubw5}rp3bTQy8Af5qBZI`&>nI9=tUV@zmm3yd3<;vrvzDCQu~ursZ(3tP@m`IBFJb zdQ8hccDYA279PpD zlz38u~+y#g^+Xd!8Kg8FPoJv!2I&vIr5ra}3g^jgY&lplMO0#0sK8sX{LrWyh^HF?eu5NGWyIx?JgC``1 zu`SK|ikGin@$*mrz|Y^`@vFc12CSh%5xr-<*)a42FJHaj;08XvdrxHzw=Z6>zx#=* z(pbB~ZYpT?#1R`GQc0J!MSQ$$uSdnj670^j@k^yyFE^+;2RR=aaWSYxLkwveG>(mZ z8dW8Et80KZL1kc_6jxE>@f`Y&_a1&aD)v!SVg${1zxj?gHyb`zpaxIcE1r_!$vfU# zLz@z*R=lx_Z&l*U!+WlObKs^5m{r0kQR&6}F36?8WJ4{R{usp;T(S;F1d5uJvT0)M zhy^?fJU%9|jr%ZjF!S&_mg$(K1t6xZx|}8Y6Y4rEkxbEy8j z{W)iJ&dP(*Q+Tf4O^_4qSXKA{Ex&9Jm8eu}9v$8M9_S_GmG%A#a zQ^McbwzB}2$YE{_l>%Lf%Fig-gczgq@H#Vgw3-$mi=c9tN(5!_QG!Q2&_@`2ip@g$ zUIaF&q6j=|ETytOBQ&Evtj?)#cw8SZ<<(wqNZVL-Y65m z#`k%&T4u%!%#Hl03}j+*k=e7k0ZA-|b5Pp-iqX`WQjIhx@|MND=VtkeS1#m74GzoMvsisB=-MC%l&gW^thC$w8p5!5BDeDKOOjH_kyo>C$_gORok%t>1Se0 z)GuFQR~t-K6SJb&cRkvRwI=yMbOYXbQb<@WPJsR6p3|XY=!6aQ__#;4hRyYj1RGRM zRw0kMa!su&36K&KFo(FtXss%m+blg<{kG3X=|SO%5ty0P$qmEY-n z-YvtLvNAga@QxHa5}JPSG|`|7o20cipU0Y{450CS%<4Y5n{;LZ#~6iGcj{yqs9?3( z(9{jiJ6sIZbw!GT=cpU87gtroet%@M-7+|j_ff3!Nf81h9P4chNJya4i8r6UX8rO7 zx^Cs%Qk5W)l*t_LaKRoP@;w?e79$T~b0hA(TSQflYOcvnm_p`A3%(|}4&U{ho*cve ziA~$)u!ewXt2{UlG>xWrj_qcJGKuX?D+^!)(MMdT385n-i}#+R>j_cwa2O=SAsGUS z(`m@5-C0qbZOztbtQ7=OT~}zEs2T$%p&DJxzDF)&DWsWD zTxSS^(Lpqe0o4D`(4I8e+b{k71-_u*`A@ySR*RuUx?qc(1x>mL`I{YeDJK`}X$h&9 zDXA%B>`yTqWL&PMnX9J2))v*))K!hj-2Rlgm1$hKknrA#BGBZpMtz=I9EJODjF=Zq zHLD=C5{@_;vsw|3lIU$hqTe5|G0^5p!DqGiI)@Va71ltDz5LHV8;fB z(G|Phfo^xi8lBmP8jaYqZMGXLrAgZ6<*c4K!lS}0j$)??|7D8jCCKb3lKTjaPafxc zh#v0;qVsf5Pqak7S{a`t2%w*@{27I*uIu~-cL7D0BiQ12X$^ejXq_fT={*%jEa@f>BDTIKC_-g1f6 zV~%W}{)M?fm?&OFqHB5Rq(VhAah0@2=dc1b7T{r2Lxn}s=csH@YPL^2Yq-v~CnaY{ z;@0)ltdtGRv(1N^$0z7)o0yVoGmkE+x#66KiL=Pb*t``n1@q9HOFm^=X{;n%p#)l) zLSXat7pSChuA^OVIo#jT?{=7&=$`htu48+>r7@KxYubjYUi0Gh74IHDa(}nu`t}uJ z7({9wJYE@;hV^E{o3CE;+yDAKH&+dhF`|;E+N_AH72)az_4DJzNHpa^T$H$Dg4L|@ zSSCD<5>~AX`Rf>FQ8bqYAUg3hFCfaLO53&+O=(g#Xd z`|r>$e}gUIq)7r+|hMUxOX2|qvV_e!b((`?Um-H(O9F=+GCCUyT;;C zO%`5#A^ElpgGrMbb1ZttV4S7h_AEps3v^2y#`5paN_I5`G|jf+cww-V3ZYt=^fhgl zO@?wW{<}Q07MD%-$iK?S!#ogDQ?j-y6g8rBlZ3jEM($`i;GHeFDo?A&uU91{WpK8O zC(^TI-UUpXbdjA{Dgh`O8eulYnYp?A#}sFSE|diq4T5LOyt$FlMR1U!WcbFjxx`D29lREwJ)T6cG_1m|Y z)h63~?NsaOX#rME%h<&xJU2$8JD_}~$R=22YP3xx=kUXU!}||}Q_s4#yxcUTryYHi z!nihIqNq(03U2~Bc}x`bj_y1Bv8PXoRcrBsCv=Xh7uPhKnmBx*^N#!bJ=@mul!%D;NLz66>o7`&%G13Jkzp-7g0g5;isu6;ph!epn9Xi{)LKrXW*e^lUI%B>9f) z)v?DAR(bi{%xBejUX7s%Q$sWkKa^M@4^IPWaG2ImujL%{-jULgsxe|ikJ7Y;JO#tG(3$_ntS<)1K?#aptTIXQX~@c`4CA#>S*6NcK7djynmwag?p{Xur8DP1ZB}i zW3!E&tOQJevS#inyvNoS?e)ZnRa&7c;ZrNEuvI1RuoqRYttz~iq;PE%1hM2h?+E$# zvb|d6`nBS8>{)FpuHSx6ys;X&VJo4mIjD+YX zr^xtt#^?yXBl-^SPk48P1RwAAoOVa7RwNs-wx)>|Yg8VRMpkQ!>6C<_R2owou5Z`E zJNAkDcMmA9`1o)Fw5SWcV%IxXt)=e+DaqX%gJ)Y;q!?H=G8|}(%8PzSB(lD4g>_mP zOeLa^2&nOyRprgi#RS}QTkv_1)g{$@dDqU!tRf6E<6THIf=YiP;BQ<&jtfs!Fv0aF z0)9$I-gs7weDkudntH)W9FyicJI#yWDIh7ba95NG#i?ZcYkhXMWt^BEYr;Gj)k>89 z8IL2%Qv=3K%y91`rZ!kxEtXWJN_tX#<+@ZtzU1>NAccT36{cAUJJ1cp9L!^5q|KPJ zq%>ZSh@LcIMnVYmS%`D$J>GeCC&$}s&+8ayhmK~mp$aumKfL2c8FbZBw^t-nF@%%2 z41K_LJ;!*$pk&#Xkbsf|-Qh&HJ4i@Jgn;4xX-`a%rfFrFV{&*`w)&4Xd?1N{sSte3 z$<4EfB2K8ZIp{`QeF~K%n9KD&;j|Y8-uw3)?jCUaBd;4x+e8Mf>3w!+<%=*RMNo=c z?Zpk~JXf2FYTYm#I=oh>;88xw_nz?w{oojSPjZg7YSG>?e7vI{0*`l(Je_)KYtU9P zkZ`*_>sr&ANOZHPCS;}EJXfFJ7L9t&3NP=jOKIOztz3lBo~LbRJk2x@JFDN*=%7Uv zj@3B1YDXc0MvtUc;cMwUPK7;TWDljnPF(Uh)uNzLlUipy24e`z z;l&~}BtIyMDJ5+BduNQI=OEJg%Peh6vlX`}$kf~rF4<;rVTVx0%*~LRs;k_fGOHrS zlpMVrj7o!J%AA3z$kMH-?E0?D+@ZjJ_r%akA-?-?$J?*of)DHuN4j8m`K7|xmbah1 zqDuomK8c6kn8Yyj!s|i`3*R01`WLVHFMs=y5AWXdCdgKRswPHFb#+as46zrspDqGF z#v#ZhMaaZIl(9@bl7^J( ziBmu^hz1!9qf&0ZyO421$=0|Cbs%lK#5}m-dqYosX z*+99-ACvB)7Y0uK;x*=agLynkL2NX}NGhAQQmBnqM%3A7%=X|^ggi{j-DD)ivS_5{ zRKjtIU~?g>pTS(G;v}lc2A+~3j0wXbpuYYBb$gSQeonYEDw4Ep&NKqojUlUOkc7Rc za&j38Ow6`lH8)PdW9lt8fBDz=@4koM{WDfsR4q@YF&d+xuJeE^nMpY(_KYxDz>;Yt zmPKc4@-#eF&uq06+_y^w@B;RpmD7`z@>5EYvY}OC%hyURly0%yw2>zDyQnX-f@PMC zEYhaWhvg-&W1(uFHprLZ9VsQ0HTgRLlk#RR%!O49sYprHQ=X40f~)2xDK~)3L;6WE znDPmxW+mOZqImv1XZE~LDEK}F`9J4=F8|DPcGxA$((~Yer8RLpmqr!OXe(PVf;EdY z=8`d-7GVYXTrB?k6K8Eysrmu!di1G7xk$AY<)5gx^|VbL2cSvi;*>JEdHxJjfYxFd zl$DCcqHTlLhE=85=R37VhW9Z=Y#vC56sfeLN9QGze73EE)_=Y`3N~gqSD&ezOelduDMqShg(l#yuekgD_uT#X0Xqb4 zji$3QcQ6EMD>m(ukEEf;z5hs$LTwtZzkG=&(%nDNyx0geIo-S@JsLQgM72^5?r~H9Sv2KC3I{ zpLb3n9vjNBrdOky`rLY4G?>%ygfS{yPj%Ob8CqYhQE^RDHJA#tSm;FUA0Q36ClE@o z)=hRC1hKc7Dl-Vvbho4wvZ^~xHA_mK;5+%S}{`9`6H2OJGRXb!@M$==&k}D>7u)h?=nL9qUz% zX)4IfPCpFPZB0K&+HH4q_(6V$x34zDobVf+=lXiX>eVZ5Uw@8LRklIqC3XzsHJyV} z&hH5smM9ce)kts(BWHc7B2#)4l1R`>bVF9aW|xuH>~}|!Ppnp!R8<_09ZEsdTI$9M ze@~0LX>;9T3{;h&Ubk#-w-ZzM+6B7PJx#4R9z8KCdgnZ3(U z$m>;0b+yel={74Kqu``6W=QKQBdk=;R)`B$Y{nZzQ{hs5` z_o$%t>$}Ol|i-h@Nb$6OiVx~M^K5X z(fK<`i@qbyPMxIA98>fCxVS%au%t?#s@q4wUgqCVp#`JWGA=>BA~@nlr1A4C=L z68EuP9|x}GL7$Ji9Ks^<<2=-z{+^m&Dm8;i6Qim$|4zNI4)u5_#&L)dNtqiLa`BkP z4Rhvr$veQpaySl?qKeC+UX3=f;$>E&N=au(vzl!JWl~=Z(>5%H`TuGqibS0^cVQ~- zBSz^(lMutq6FR?uXC`A&ls|iymWh1N)VQQiS^-s7cbRYgl63n8DGYS3$J!OzT9Ps( zKhW)JsG24(BC<8f2k=8?=D2)-YC?*Hp#!C;+BLRWNh3KpXm6>$`U<`KXS5Nu zuCa0=D7 zIannD-I}P`{MENu)8J1XxPYpwX$Ubk2+=wD_ZoQ?hcU!xR59BcTQwNl;G@sb-2)nf zzJ1H>|Mc&9_}@N~c28IpN6?x)p?b7I>pXxO0rmO+Pjc}j13EC-;K!8Rq-NXd_zsUl z94@Fy%co^3_Tn<#_dFRc#8}vQ=R0j2s*d+lIS7hHgR9Q}S;Y`NO}m=sD!=pL-h?z@ z+1SsvmNPbJeh;l43wC+e$GbrU=`u*2lE$Y3xUm1F*zr0m60bo@?N#VRyDd=VOMK(wE|nqHhjc0q*+O!l**PD3XIAeP%}dDV^+n-d>>h3 z@jarbX=`rRD|Tv+HyVp($b3=Z<3Ut$Ql0wDS!#@?kD>@xN!UcrBz}B4a8*^D{_zL8 z82E3#c*AfyVQa(Vk3aLTfB!G+KHlRb$duY>R<-5SJA6#J$WEM`!{sDCE!z^Bn_Hr( z1b-5l(oyFW%lY>-Hv)O7mV9Q%%?f{|wYVroH%LAwQi$lvaJ6dbwrhUAdn83qodcar zMyd2(R9Yz}_8&h=jj*p-{qhZJ)pB>g!y8RF4fy>LN8q^L<9dg69_<}BS1os^BfDKk zQ&&_4GGf(MXcE5nm{TI?fVT-%8Ehqk3kmv)1@BBPhU^nhiX0BKSVY9hLOd#GN}K(& zrn#^Jjfxw=v&>BGMJU)A9Q?e2kQy&87Ua+E`xnv&p%{2C6}sn&!zBb=6-wsvd`Jrn zktzm6GA}QO|DU!Fr<4|ItJ(gmCIz$iorFVFE!s5F2uFwW11dz&gCKIMO-3|iMYSFk z{c0LyORBZY3Bm~fE=6J(NGYHqB-i1?f#gpd9zXDS*YW+o?yyR8eDrKyGKfzIghKE> z;k^v+*Q+&EZQ1Yp+<*pBU9o-pC8}Cu?24*h5mT;vRGeH}Blm9}Ecqlu9|O*H;{3?UPI92Y@$vNk5%wO9=0-^9k(Kl2QYkc_&$43iuWiEReVO}RHxKx)asIsbR|3^m1Ba~mT?%{F_iD)< zz4#qUt_%+gJdZCv#|w8s(eo7j%QOtoDaONz%`j0(P~Q2tEO*wTDwY3!DJ&{qzqK|X z)3nL2yX=vt*pp4s!J(@LOocLruIt$tO>e=8%4fJ%!bte1#UctOACm4v2zlr$j{AY9 z?gOv3E$>g3&pv<7_it`veKcXmk@JTSJpTIkjK?F^3#X?eec#j64Rxh*=-k+kc!Kb& zOCd#*WpbXwFos&xRH&+nvm#SG`w8mCXtz(QHNOaWlf{h2c|R|ul>!?A)>PG?rV)2O zae92j+lfXCj8ho6;c1M<;4l(Wysd|mx7f!$)5k}~$--U3_VyOlPYmva+15;_o?(B) zwHumU!^^h8AI@}5jj1%}_Sf=gD5?`{=g-dtNWzu(n3vf%UN=A~nS$rWeyw%LVusnxn5~osS<2u9T z)e7Fc0A)tzMNq%R0g8ksOf7gztB06(5hMv?=kRVq8_6O?AA2XYvN7c*arI%3;~05{ z^9PZcZ1H{Ou!g*GoG;oO=Ufys%WWl#f3D8ep1resYPyFXEn zHmDLt$J6U~Y_?m5!@%Jb0xaw>a(lDIImd^u-*ETvz?%7@nO6~*SPIGZgkxoC_dXeA~>(PwLD$#zgG@t17Dzqw?}rN5itw@MTe)iE}5 z*z)Ens_{x?NwAs+Yq{X@Zklgg=BC?4_E7j4;gq$wB2m{B!E!PuI~PnE7nKqYyHGtL zAsIU_{W8?sXSltO>Dyjh-ZC3wg7qK`B;x%4!G$>Q!s+zGsV!IPYheWTjKj244^>B|f5{7?Tg56sy#LTwN zFu)*#r?UbgzgNX#1sm5oSAob2tiU{MN&yny3qD>0+S#Dy@e&$C7gTn7ThadTC#bf; zrA<+HW*APin+?`F`tup34O-Pqexg60IPD+V-Q82)++a)_?uDttPCafK!A^PbEWV}r z?2owlFa8z%zyG%gRgoY}ZX{F;3h^ve8lsA%RaJyYrQiwga}nk!=in}fRk(uPCW`}1 z&ELs)sLPwhV{r3#5rcQ!BjK#N;#n8PRD20E{sv8?O{bBf^3QItt6p+Y*lsR~0w)OcrEJdjc3FEx6 zoC6-hR(&;`@Dhunc?svf%W9Nn}@dESe73%pbq;BHH60P-WF6fjp*&>uIfkf5KG)*Wa@t-O`Ka~Q%>6Av%xOlGkiAq(R z4g=qQ{T_M!#LG|aczS=}fB(f>-k&;*Hf*$DaMViJ)OAd%o^S#t@(oW)7zWFSe&WyX z?oq~M&w2RA9Dp*8XLQxzovfOtDV~Quw+tU2nT`j1-&2V& ziDQ$QV__?Q8Xc#}F?{Th>P(~Hi@*H^##9(>7>5x%Obm~E-akD>YdxH%iSuCDRq)BH zd+NGjnmkpd(MCe*@MJNQ#dIS~W57ljqQ%nXbkA}jeWBiy`8-xiRw~t*KXo0{e}!#b z+*>)2f>lsbF*L{xZ9L;VF1s*nfWBQS3yMVGMLMUfpLssB&dM6Ogz1)u21z0VS*y4y zl05xF;V@hJ{o=WQ%`Q2s5$6TJD+8BYs0Wp9=(>T84k1qt35r~TmDH;+7{i5>!e&Ld+!S?8>?Z! zZ2eRb4Ut@DiRWBb;r+U)Lwu-H9AHd^s0nX1ZW2z%6T@I>Hzou;NQGz!1SEyhj;3wc z+*IstH*9Wqn6{GXci3UVR0G>>%j44?Yc1Qh;y8|+x*@R5oMqDl7fNF^y>;wr zMH`2Kx`y3D4ccR;aI@)Jp-g(7h3llML(-}gi@NKW40V|%T5vU&I004&1(}t1D}2(7 z?zpZhRu?wfYkYlQa75+ezvm^lo4-%44npMWYq-Yt^(7k;b!7muWUiG%O&5hmKBX4N z>x`InFmyf54JcIw?q_neEv8&t4Mi)NfaSlZo;bS@ML?>+02hZb3fqs=A|ZGqstH8| zm6msSCLaT42!p{?bBtDaniRvnr*}f_;ZTuyUL%y$y@SvvXMQ=_W^AX{y^hs_kvGCr7+T3<-7K2Gz88Q4t-T zTX9fW1}`Ni=d2PAVMoqssAFv~Iq-_9JG0U7Vrw{@9omJ8+(f>j_m0VVoM>D(a`@^E z@~me&ZrQwg#ZcFrA0KJNWBU;`ScZ>}9EUS*oDe%QP92+Oi>hnhJslAYO;ZPlpb97w zEk58xCW{&!-pm3cuhQ9BEcqfr$~wEaCYH0Vbng^QNx6d5T1#iV|IX07-0o;qEa~&u zkTjN3#qTl88pF@EeNytpEx3zYL0i~lR5_7n{>4f{>Y^?9A$T{-QcFu!S=3~BDClb5 zntKQdk6PkZb5IaK5c};e~UALIZ(kUj8SxN-e7mPj1wqpdG`F4zx&%?1-b0x zFiLUkjxcDU~Ei=m^0W&F;t%93;VU|ho?UX|0 z^X6U6AhJUJ=`*m~7uUDdHe4f#K^eVUnUWckOc7!77Nv#Bg<)_yLo)oIa(K-JF#IxA z)faa5nG@g_RRtFo%d3+8IEcTnDaj1r_t)J^IjBQd2H$KDT```X@YYiAG+rB=o#^(D zoDK(GeD*`O_s_ATMVUI>SEmEwM%>tOe0pSa`;7KxgJ_M40U6FY`f<;A+M{)iwU+Vl zF*N)Jj8RAxtiPhtaE^tY(#4{%xtJOn(Df1$;Onq^ki5Jkp7UG?X z2PV|cGdYj84qm;Y{`C8(%^lhe*vT@D9Us2^idUb00FOX$zsBS}O;d$os)LG(Z-4(K z-~9F~{_J1eU`!oYCK4>~I1hGWJRhiccW4uU=ORM$!#~3R`tMNh-e&dc68szrP8}7` zM0->TKxdUUS#GHp>8?o&v`Frq!)MCEb{!V=T>$KbbtNUqAu~%Z!$~9$spn#@{C$fl zWswAvr~2PPvGW-W>{o#AMFCt&Xo{j>wnL;dPAwE~%czX7So6e2Hl48RX1u&6i^e}F zHS7Y7u@2&r^=7~L-6a&fEK=6b9aR*^>rbpy8c8_?DC`yLiZb95BJfrta2%~ij2oBBY zJX3$9J2m|LKmHuHH+=e4gOiG1ym?Qj49nem65ES!a5Nop?i`W>H3D8A#rqnjErhN^9-cQ+`b+1)?T-riDI?{V%e^@o8EkB?6W6NV|Zq(x~sSBy?5i243kMPN`_KbC`}QpLIyrFbrn z!kL-ut?;yTrUYvtlXK(s&{NZ}q! zY1^y@Dh2Pb3`;CRLVOVvDn=od!EA4!t}&Y}roDk`3%Ws!$t-tP;YjO&UpVX4;`vmj zHhWyeTxpc{)a{n-&6Z6qOecjFFc1b=I@~8?{22ztkm?0R7y>$n6NQq{8!9E7hmrS( zj@NIVc>D3^+}<_(@Uv(9?&~Lh`RyqT%|xh;(8xfo9i~!Lrb4V^WC}IB500MBIZmTv zKf-Zz+-_^k!vno{R9eR%Of~nUUW%ok%jwu@xtTt{G~f&vsK{Z~Rm@NeZUE;6v|;!B zIqu_2POm=(9wX4G&|_&Yap4)}Go!OS^#k?CBW-0Ehk>X2w+!bFyr(;LFirIP1O9mA z5S7Y@!P0es6a4(yj_R`)oV$r>n%K1!ct_ud1GUxJo1^ zxTm?#vVL7!dGr2nF%Ofg>flQ@_fjN~yMdMdMpni>v%HZn;l#Q5Pw9+txTm8Bd=|z; zIJcUMc5e#{PU+1rI6EZ^D&@o?psHYx3kRr=4!U4zQ{wZ4Jq<^24!uZ|l>Azj2|#mU zm0qW*bSV6FuvaP_f*(W`m5ZztX)-ryDl>pohATSnP+q_VrIj1v7p&(2nYE#qu{Ldk zz<_BnQUN98?F@AmA!`oN1%@9F!Ap&w~X&ExC0jKhTM&Zug~yDxqRqvhRi zf6v&T`0V8!r?0<4Z7TfzJ!-V<-|smcZBPVumhR~JxBv6M=Px%F-4a0L@iU4&7-FOV zzXm0oTBytBE!FJklaS(Z~W}iPPi~ug(Rf50N_*ER`h3&^TOFqGo6pT)L>Qf+y?Mzn^tj=S5*WH8MupAK`SFSSPd;Pw?0M$#coCEivG&us!wx-< zZ(now@G{t7?1)%}R&`Jejvd2ckCLDU?7sRPJU-HD6Q|yJa|!2vF4x z%2c`W^ICEL$xHs@|M@rkY#i97Fc1l9ys9<~cA|@^fm(0SRn5@tal;6eiNhV2S)Qpe z4^5iNyuu$5`l zl_qvSUkkC}TU_)0lB=_BSh>Qxo4Gy3^_Y{!e6~`jLby~DdFK})B_-cB@kLdka<|s6 za1L@6$h{czOI{lk3ZEh{C*cM1wcxK>mqgTs1E5sRF1W$W{KpFjcN%KM=Z;Mw14&&q zOSbdSt+W;v52nz-7HQPP#C1PebkiX928>23GgJOnR_L6_?R*RXaSMgGb%EEF&`jAj z6s^Ht8Px3q51&5A{N@|RqzXV|v;;Ly96DqUvJyaNWz%FGgY_UGMYwmKlM8IT>Tuv> z9d_T*_BS*e&5yS=UmXV~(cCDZG6sdG)}hEhj1z}pKqE9&MK=zdd&fWj%b)V;Pk$C5 z=TSjss)}(M!*}H*+RDu0JV|UzW5WA(*uWCPXPa<#8QA!lYK0RxKkhld`M@-J6poEj z)C!sk8i5hC7@gwf;mgfGJ>iGk$3iV&D6yxORCd=Dz z4($7hLpP$8V(2$iHs2T}jQTU(|$|>J!mDIUHVMqZ&#?AA3x4dsk zW)hyO!@n8Lvz)b+7P=e)uyEd74z3rS1xBZ&-vuU3C;^GIt&Wqv)}e3B0^;w$VUDb4BAHLsysSN(qGdhA5F}|*8n%FdTWI%h+ zE+m9Ap=zPo)l{{bH+4lhkOpct4sSej51bJR*{|+bffxSDl79{my9n_}s4DNjBiVNy z;I~}pt*x)+kUwFe()qr|ZTXdIDgBJ~@UL8otoytIk)Pk6_p-=dTi5jCM5@NENC*dXmO%@H4;;O&>-gd~U-SCu#Ls^E zIoS`1R4|5g8jUp3hDHrA##NGVuz07hxdGZ^MoCsYv|zR2@jP)nchu7I%m4eY+1=c7^YQ_&6c%{uEbmXJSko#{ z4pVEYTCih}9pK~RDWehUN;5qjsQVLQ2TU!rwXkVIIEdDBXkSo-&(~eX=oB?tj<#E2 zfJzaL%Tj%=V7D%<^q0bi$O!jk5LrTcNmf~>-pZ5@i>35-Zk&?ZCnN~@IZ&WTL(RoS zT;{kqkW10fDk6A^T%a65o+bXSu!zpZx|L}GJP$Y6yixM0aZ{{VbYYm4jZg)TCq1CF ziHev7Jv2^swTXizStJT7QPHwTPRh*>*vJ9`P)_3~ixBLLrZPG5J~T)whJl2`(b`}( zRilN|N5Xukr>$a9CgJ9oQ;cm^1O}d; zM<{|ObSy4&)9MN?gXC){p&rX5&(aoOK|>Dgs+`V<&1ZCR1aY z4#X=-pL448{O``qT*A=6>3oA~DLnqgA(vF0NhLelFzB5V@1iO+%5;dY%m`{oH}(uC7`rpzsW&^sE2gQ(#nd`Q9XJ`@F%BK$ zFe0YHc1OG@hWi!5IZ_UCJY>q^SZg)6U1 zzkuA99od|Itg>t!RS4^34si*TulRa&NcYswUSV&yG|pqEC%oCwv|F4Tu)~0JmhJ5e zyqg&NQ%v1dJb(F;7oR>SXrw2s>lp`2-LzqYs0@N<90!zYvEGJ5ahh;83@OC>XpvOe zK1E!9EYyxEjH0k@rSqdK;<9~q*8VM(FP>%6Yf?(B2H7(#cMf?jAR~E8Ct{wVjljUiRzwN+2pedF9)2=(3z`T8_LIIyt&p^7!?DOBo6J6fJvEQ z*h_o)yeyobVc?dEI!|6&BrL);Zcly&=OW#gAh%-FohF{D*nF0dVz*fI%n62Sss0eZ zN=YtkMikf5YB{;qJfvrhlnT!D8AU!23npN&0^uc6=u{l=mTA?3A4b$PF%Czx7mR6e z=-4E?qWH3l#>rW`8va2zpj+Dg+#Zc?S7(t;6>R-i@rXB)$ht?>1+W9Wo&=s7;Zn{U3M ze)fvP>B!LAJeWF-mVR{X&j&s=@JFrU<;F*=U(crYG`BUzsF_TUmd43D)?;<@=t|)aViZd2N_odQ$sq|k2G|4hadtv8W zQZ>KRHLzAIl)tBB`K53r3eNY!e!OBSl!0d@E?~)vO)3?NU=gAPc8jiVK;H+BrR_t& zhX-x8APrj8psIi=@ish8rU@qlqwhkH9um}RiR zp9h@PbmuWX%ft7n@NAlj+JI3|8%1pt+cpG$G`lKi zS7?L${7}pHGyfblGni9fkp`J0Rc<&N_31?ahcBVI!S3#<>e~?h5yNamUAh*^=Vo$F z1grlP?fqkRvxUW1MF_$UO`2!{r7_KxN*lHe(N3DMGdc$2#350oQNR>Y87UMHFN|K% zqc~a5(Sf#}{o@{Sj`!~#IUkNZGc_9~&ZCLWv*0p#KUh8vmPU`%CM5fw`yQhWTdnvw zojLRql~&+2_YYg@+k3pK=6Q#;K?%EXx-c*Mig3EH;I!C?93nDUa}uh_ND)x?K) zLS5A~RZ!`I;&k@Bes|#5I}XE0Bf!w{>0QgFKXT(I?r$`0s{#*LitE1MTxaF2FXl-r z>$qPdj%4Mw^F`=kF&oJ?{&gQJrQ?NSmNF}MQ~jK*=r#|<1(RODEE$ZvyHuPIrmiWl zKvLqQ&s{~d!^}LN#KCjESg^-moCO2Mga*vZm4}=vm=Y?7MR@1YRoEKmRLJ532*cOq z7DJT6x+z%w3Y?-MY`2sBKxvg5g=G2EDd{iTEi=TmOs&#$LeTtv;zK7oiRxP6Y}npw zQP?4JP;3HugHa|bI6T{0Q&q6(d+Mi-^Eq&V{L`Kh!!0)4=bwgDuxa9E|DMmPiB`dOBh*oWC(5AY(n^vN{L=qt z;&vmbCS4Ki;WMXiPT-BU)~qrkbMs1f-GV7t!lxIOyYld}(R!Pf`4N;r{ZGJruT^<2c2^wx3IOMYg9V z^{Du)zrm)5iBouqoJk*&suwoG$OZGobsEA_6qC$0$!u_s>8+If93i8cmn_U>vS@xV zDpw3eVl$&du(05?!FhDdF>ez95`++Tj?M~=zg;XlYxMOOPFN7BooH46J@?xbh@bk9pgdW8 zKW1R;5(Y1eaF_W#u~4X_>n$Q}V%QEVexh7yLft}XnpG!$F8pLUR-J*__=1@+tCPdP zUM&(x(~vH3OJV?vTXA=ym0>1RD7=kZ$UJHW#<@vJCjX;MQ6jtb5lOA`7&~>Ru0LvZPgBzKqp6prEi4T`E8Q!U|gQ zjzrXIFyJoIfpgKA1|oSdQCgT~$>?O6f|i3=WNywC&2>Ob@AkSVPp62u5jK-BN${1FGlu*mrx3HdNx#2s+wKMT60i#u+VTVXG-Q z`7oZ?*f9*#hW`ZJ*#H0_07*naR8g>#;yhSJ2fcN8XZhY%`0=iy(IMg67!5X@eb)OJ zCKXwZDl7@=Km7?WzyBGNcT{Of990n|P(E=F!#U~}i#67aua?!piNEFRv;Bh)gXZ>tVBmehQgQ~PHMa#aBhgpCQn_}@jXZm ztDDA<^4%m;c-_THe18u7}ZDKAbeQhW8&vHjQ9=$JBf5=(vCO#O`^^ z>(@PZ&-YYq%jt0D=J_3Zs_9Q1yW1T`YmSe5?w>tib~Sz9GgwPoH+T`6ddqqL7!3L_(+z0{t-JrU5sc!@`1a{TfA1rcc*zN*?@K#!Vt$z3Z-gJ=Obb?r!FiP1~-DP zIQKnm9W2jMHJECHG7TnTXH3-wX0m~{J@8=|`1*@)d3^GWqXck*7G#{*R+$~7QLCH#VvI6`kc3u$yP!6JkQc&eV z!}`7ogVgkAeJ&O9ax(R(MS6Y7C(ZRu=9|mHXMCadD{Hu-z7>{8C+Amgf)amH-qT8! zgS?eHAU~(FqPGkkQU$(5^7&kbtBFWtz;)rgt6IFN@haHd6N`S$^kx-Enk?y2=Y~#} z;G$+$N(X0G?6Hk$P;J9zSJUh}M(=Y-oAVy86&f%~7XkxCfpEq`;QAqqPS{7r;^;kR zuj%(6nGV8DTk#hg&Bw`y5Dl$xSe%Et(ulxdC#)NJQG>|9$-*b%_^cH)4nOw%vp@eQ zY+rnW(S9%c>3*E&`%gG zRCp@oXpBKiu+%FA-L^b)j_>b0zdVl|jy5bJ#}R!#^7Q6C?WJAd80XqKUl% zp!F*lx4%#=E$wW%(M)NZURJRDz)6^Sn<^)(iB{-J;dKlgiHW{Wf_+#VGr!HVu+`Vj*CN7@pF;n@x=mxVpwf451*o6 zeS*{t(x2(A!*!3y`Apr^)Ri#yCr%&VL@P;Pwsc*G-t1`GmP2>q!&iS`yWQcHVsth< zn>JfeCLB=HfIXiurp6D$k^xyHn}3&O!*gkSO$Sd_Y$QgjTC+V%E2XA%ny(AQOX&0b zJV{HNlCd|B6CHoW*dQ3vl-(18`9^+d^fVl&DIs zHqb66+y-gA r=665?TRsAwKuw)~qVx;72mDU{ZuVXt(m1Y^9wcLQZ1uA0^(o$~e zDb$-31J7iACIxv)yX2+?+#hJfX>HwPvuPyO(K+`K~S z7NtY#=64NSg2l^a0JF7o@P{#KvFw_Y+roJlc!3t{E$yq%ssG6zF~0vRjCf4s3oGRV zdpADcMJYsw8Y}ZRwOSVD@%I|9ag$>^rDqL=(mgz{oC4(?9-W;U)@p1NsEs(hH?)8D zXQ*0ZbUmL_*5-3QeJbK~Z7N{J2&8YTGg4tsM){o)x^2BU*= zv(_4;JylF~7ZnwNS{C>b(Ix<|D@TRr4pY>-@ha5doSyAEG3;*U;U%uG{Du@Lt$J=MF5U@lm0{?`rqx@rBcf#G-6Emzg=|G@tz zS2(!eRST_^L)X+#dC62*d`pzVf{hfLo^>-chkoQqYEDbW4|q4=Y`|fN1ht<}D_T{> zMD`l5TBfN-X-8GHVR`1da9_sfWZw@MQ)8!*sxpC-Cpz5Q&QWc4h#eUE6N2XJZ$|c~ z5j#TX9ixT!k3Dyr5FqjTgQXV5?#mN@`PmE3-Fv?L-4oyYyyEz#N9l&!yP9bf&V2~{ zu+zx?>4}?%d#bv|*$|v2+TiiDw>LrI=RrH6t~ZS1NWI;#*=_jn=4~E|Pg59Tj^o7b z-5sZoAG!Oa#!p9(5EN#6i>`wuIs_z{xTIGhS=Bk5AEV0ww63BWC?ws_iL*9x%}lUi zGGfPpu|Hyr30y`coSu5V{(8^1-yYFY(+?p{`*0k&yW8>psiUq9&8DGh+K^f*fnV>U z=)z9H@t~BZ-rjQe@)`SY-g4NVVmP74sPN3IBF}pm9IEm(RY;B9ZZ+)=bOTNY3nOuW zp7Bw6aOHE5C0-U`9%WCI&JdS*mCLl=k_{+IuL8J=e^;T$Skx)k_{mBNlfd#`Ua;Q_ zc3H{5Timn7&yu1f%(h%5r8Z+R_*7A7;}^@mS>dD>HB_u8U2-3);`5RsiFGQ%CyF%o zDCnUd@y_DVfp4j+z>Hr#L+3ioXL>`Ka_^nXu82~ZXswTV22EJ>M1+yKf5!HEFFC(C zpa~eTa36#k#wZn1?Ul?ye3`|cdJ+?VzR=r=esWke=gD%kamMT>{-jk5-cuPJ-2w_D zVPR&S$D%063o*b|qeNju;CtWWFaO*B5@+f-xKn9uhf+1(Ptn(*va&clS3^LZiag+~ z0*?H`WuRPf{Yv1E-aLEG`Nad{AHJd&p%Q47&}bbPniB3yqZE}2bEKPA@$yFKCm3A_ z^|W?munzn6p2>Qi8->vtB@cXPNmO`_&GoMi&LZy@i@v1(UxZ>Q z7V`rYv2RM&%=FX`*_B&q&KO97kWd>XXNOxX?DO0t1yQE3)FNB zO8~p~xMIMX0!X}8G5s}s5!&mRMivh!>#1UJNNpXi4nv+Bo7!22_rhp}zr3rl;ut%{ znFHDjsuJ4iJ*xF=e_92pu1+bono=lNnQ&nQTkWi>qzH|eEpf{xKQ%H=eBd%B+i%?7 zFSb*&zbif%iUViCvyKII+JFh!CX#FLk}K##(ibH_zwpbu)xmhJ(M<((3B4`<`#fwe z5}{Vjb(sdeU`#DS1(pGtY4B+$7k@v6C4Sy4fG~;+w#XE_UVBs>@6R|S2$pui$rJ~$ z6oa_g&*P`isNq1idNnA}%arQ{AB2mvuVtaWjsupJyDh)?5B~?xfARx3 zcZ^`Ft>Nqb;CG1#=ge`b-m%}p76tjF$(;|c-T|j-&3iI z^ZpSd!tmh(P~wgOe{eeNsrHX-+7|BuZ)oby7;QN2pHS9uZuV@Ry+k>S z*n#or7$?ikvlp1n9o~7Wrp5ImlPLO+Z`m|0{rhk6-4Gqil7~$WhiI7xcm?Z4mAsZh z@!~VLgViKeR^bh)xJu7SACw%W+EU2{pngt#Wx>*1G?)cMI=@ygS!q-<2G`d2#epnE zgPsPAk{ZmC!^u*CE|+RB@8_l1{Crd~#OR88ENw?A3c-|G~cdxrsFa%)?BeD z_qZ4wX0qxz+)sAqJw?Db-i81=ZD?*@u=~j$bNtnB=nscjXM`nn5Y_l#VV2_F&nn)T zEvRMw&Qc+tQ|r}ocAS`gss2d!ohN|ECI8$QIJUNkIY%RgP}6pTz`vlC^oS%oN(UK zws%a^NVD06@C~!Yjst!e5LKbY1m(RQscXaQ_a|)3<(jN#m>h5S79};-3CF>*tsU}Z z$Nlq1wAt{VeskvQcklVhk6!SL|L;A2@)tLF8$$3#rK#JR?s%l%KVsSq^=8Z1g(0BU z4ku8zTc&YDdC%#1pxtgTZOy4W2bSSDKvc`@ng&}Lth0=#BX088@yO&AO>=|8AhwG4 zbv>u~CLXJVXLZ8+SU+T3N2%^i%E)o(7tHRa*)dE<#_q^@zvu0@?|Ir!JRW-zWs0U_R3dWe#e)G^;DYfDr&zALY zeXrHC^YU#O+NYMmIthO@!M|`Dxgc>|8~U#-2$w-#88@_CnzIsW zTw9>#6l0m0X>-3B5IcVLj4Fi+Hl6{~WJ9=?s5k&L7ifXfaZp+=r3nAD#rKm`?^RW0 z`?xBDpiGV0K5+AsA0z+yS6FAz;?UxveK^i56zCL0D$$X&=((aJ%L`Crt*0L*go@FI zn$P0VQlped>0qzT%VQseR-rbWgULKFc#rc6XL}z0^rM+ZCA|vbt|wS^#9umt=N$DLV2Zm?;*`LSVeu;$cy4{YxpV zDm>mH)4&FWoL5omJzIr{>Z=4j&D&k)@P@oiR}uz$lGF*_p=*;ZK0%2WhciYmtQ5)0 zR|XWMgjZ!^O00wnCk?Vn=9kj47Izh?t5=fPq>wdRDVoB#eaKtOWD}YHHw1Q1I_z?! zxelrjQ|qi4N`oMiZ41fTFoTSP)d1`mE^0}ZtdyXbLs<%TOYs2L5d@q-+l$mR3Q&7i zIjT6oQ&};Qu8s8lYQ2Q$r{-lo_+++j$&z2Rgg?t-h_c{hDU}{4F?=O}h-W3Lo0E0@ z%(zQip>&AksQN1wmrL&39Eg)uK+I`JWih=hkmsX z)Sf7hVdUF?`#-XKzh~G~V1&arPq@)CO_sK;sT%`WTsLBFA3|fUG3jw)bcaKL=C&J}n+dNCS`3G$cifr>-08^q{TrsQ{($OExMse` zM8ZI1eQnQ&*HX&A+&8NQgX+8Z75TG@&$v__mR6X{u#RQgv~bCkxlEZ~Zmr7<&NO_I zSd2@-=9%+2mZ{tG@?d$5B%5G*-l^h1PD4DKmQj%>kU!T&GI!D3r$b!L>O>nCXZ|8l z*ewEqtaD5Ijc2tOxa3{VDZT00>8=*|>-6^W*}!F(Xc*i?`@Fvh%aO%3b)ga}2F@v@ zAU$sqX#N_jXr0KK?d4ad5`qfh6vk2g)BhUtqd!J;9p0CBd9WgJkQpaiGZ@{cjknDV ztgL2_wi)jNA4_SK?cjmU;-bZ-p?&oO_Mg5$zkQ4$K?Za?9zoLrw9%y z)ot@VCDCqrX-iutXD)2^Py`LkfkBJpc5#_|nhNPOj1AWUnR~e&6s!|V%f&}38(gxH zyU@&D)>(34xl3*jDHQ0}-go+Lrub*=G|eFOsF;w+0M3Kw+=Ot8zy9S%p55(u`#AFU@yM6o9QpL2;?=7S&1RxI z4K$m9SKqs5d$Z;6bfgX&bgHJMJ3OMaqHZ>L>u@FrEBqLgR9b6nH{d46WId<-kxkn& zjy<}n=+6UMVCeVM+ZN|w5<%BDp>fq3I}Rul&_Ui3S>jO<7_HFaXQ$ZY9p0-#?P5X_ zaa`nhXVFS9exM&ay2mqL{r&^rzB!`Aus=<_{?OAJ9dT4a%urQ|y4|q5*-}>;tpyX) zhl65_%<}RMFow;;9rrJG4E+-x!>O}8zcn2D2|ox%D=O`%YGHGysTvraXKO;e5{7>; zSxeK@(Z*~FG)V}eP^!cku~PD=MWDy5(q}OgWMvCp4!+f_nsXuSgoxDgsgLw(anD^F z#4ELQYUSbhiWgcUJA5%Sn6*Ow0w;pMGW?GW^R=S!g3ru6+?0i&qYV7=aqzZ!W+z0s z_n2)<^Y9E3`_GQS7Or)!Vaj^OIhU~{CF@Y4T5p;{5K#F00Wp)@2ogPn+B8({4d#bG zgsq|K!ZN`K7!}ffjS_UkKPA4e*32#z@llkNlyp#gXgAT#;v#A(Mn9`c&C6RAYEAD2 z=M@s&CC-I;jk94v>K%-x<>C9^W2!>}w#v-IF)XZ$b1gCKM?1v#oUWT7whF4VatTF7 z%xN8#ff4W@W*U9Z%@2P_|Epgk#|{uGqo|?_qN&J;B2_xvyksm&saV$iH6xlZR0s`>j4b-qnD_984xR$SUEhNvQyI=CFx&a(M;EJHq)Ay;XR;qs<3 z(3z)ENM+SLd`!{PNqU?DJJQf6 zZ8|F*Y|%ys>Agw=4p*pN5~EH>ZiM5aaZ`ouIVBkT!Wvu-($a&Sl8456tE?6WO6h_3 z3ybY#p}bV*`3pAG;(@QqlwQA549$giN@4a3#>FxvuT&6TCTIQQmYH?>XZds0%4)o{ z=oA}w$|&`@P@l$PgT~z8i1_(lX0{@Tjw~u;GO+!^>MV0&rl?iYsw>h%3qGUI^nj&z zR@6EaLm_nBef)w+X}WKJg|8pze)~1!{)oD{Lq7i;KlFU_yI=AA0blzZ#vy(V5DH~! zOt13G-~_*po-G3K>% z{H&te@A0GK#SeanwU*)OBb5%Ag5lgTJ{>T(w>-Xm%g1j&@PnWHl)wMtOE#O9&CMPD z++n+(cVB;tAA6c%pu+LtZ~iZCy7#mjg_7dlT@BZ+v8UFc<}$E@QUUF`+y)nl(;U>~ zFSlB2X!LBo|1O2*C4k=-s?G8`ou&P13Ex($Wkq=nL~i3%o@imo^Th_k7j~JLmo!^z z3cIUa-9Og(f;U$1aq@NKGfcW{!ozv$@*p}eYV_*!=E632&6-yx^L-arY_0lPgOBII z@jP681fU4p=+p*QvA#b{QX1-19Y6)ggV;2DP69>@?=YRlLKF1Jl$6CTg=~ z^V6R)zWaZ};7$9e`jdpw!At3I@U!YWa!XSalm?KbGf_*C6gjikLcgYBbEU)L$R$gv zLpx!5cmNXyqLUA5nrRxS>L!LT)p#FlgX1vbLcR%2+hXm+Fpa^!rIWoxTP3{pXBd{f^<}QSxZBT^>TgL;7eB0==grGouSw8DxUS9 z$p>?U_Xo_5i|Nx|9E>B4QJLGP#KfVt!I&1%SQ`dgLA|Z#B*n3=>O7(*CTFnLF^n*cfwOfUEay?!_Z_O)@yp-8 z=Gns?zxnzj8*TXgn}(<15B%v*@92AvlLN=S=KDX=m`Y)X4y(hnXwx(t4^LEe6Y5B> zVrWn36SPv8x?=45Aleu`)6mhj6~<@|hYp3X>jsAN38`AVop8e$KTXt|JA@jeg>f1% zx?&nTOjU>xy7_qp@ep554%>QO)`8~ zb~g=(#VZw`8DUWF;!s^@)Ip&lNxb?9z+T$*DH1znp+39jLRkju_$6k^>nJ2}IIk8c zT)#*Lm2COU>D|KOxwjbpIlriV*UzPS4#1p-D$8KbizIYpQMyS&p)fn|O3Y3k#uZ{8 zfxKi3?SqJAX+aRV9Vu(EdrcuR)>erE{JeU6~cf* zPep-&@Oc59n2Nj4eoXf#|Ag_c{*GGLBmt<3eXBBY@vbG-;zDXq)Ix~}CM(pdMHm@g z7b^H!T^e+9pq${Hpu7%4a1ka8+JaRU^|K$NKl=fdYC{m+*yAL~mcn0HI!e;gIl0&p z-&SM7@=)e;&e(Byt5Zp^z2$>>8x?iU(~Pjpxn;byrQN}b(a!f zIGlt1EPQ&J&HYWR|vELlmU$ebR4A==tq6nvN9X2}ig!cH0+wQ>`YhM~T+n~9Gq z%cxjnRu?I)vc;<8o2BA+ZQT?VQ=UtS^|gjdQVLS!^`v5ZH3Si6`M-~r-m>6bKbXnD z7ytku07*naRAd*-vEp;Mw6v+k&q%K^fN^D!D4Ez^=}6ILDSh|eUsbZ@7=Tp779;!F z^1Du!rYL-`vv^9`_?jV_TWnJrZE?pUxi7XS$;WX?0-c)f-5;=(r6S#HL+jKc?hd@uCFH*~Mx^6~FC zy#D4j)3>iV+_gMBd&$jbKcMSJj&EM$-hRz(zo*fTb2o8%afAK(OZ*qVrmBQzwdeC6 zSJah&j`u;dp$R^xsZx9=F}^Y0`sxapnEbwc(HPD}6|17IXbcxAwSieSV+rQrZTha2 zcSxn#7{^8*c>nJ2IsRs3|BIi~{`mXYyJt|fJidL0?FVk}cWfT+u@BA$AGsjQL;eua%Kav%x zE;jxyg=2Y-nBtjPgdDAzzU9(H3T|i`r1~QLIh{>j+){ZaY1uR{j?&-F!}HabJfEwQ zH=38j$%6Z{NDeF(MT*7uFA@SZ!t3_4N7je4w)x3VQUB_{`TzNP(_cxFE6eZPYi8~d z@zz|ciY&6YG>hz}I4!h#AV>oA7wGR5APJCW1PumiXr^15=|#P;ipAP1x3@)vyPMh7 z2ivZBywnGx3dzdM7ZDz|oO{miKsMK@u~P_xpW){{A^__)DW2+Q_&GWKyq{rAb^?rB zPy}qoqs+?!V;zRg2JD9P{?Dm#)K~`z>3Iu&LY$63T+*59;u^*<*rpf{8wNYh4Sab7 zN@;yZY)%UOor8)4gNjoU-Q|*0(xNO^B9a7dW{~%Y#zAO-xqg;hcAG6YmNTkH@H3W& zhqV3$k9nHFJirc_Cw80x1l+v(0byBicJ&0C-8oUaMYUj4uS38N=ilNd_ONzBj#h2%15dzmi#(HB!Lc$OOPVue5{g;ueh{Ui-u zyMugn_%h1e2S_CN5HRf@FrM$Q*_@>!56Xg(pFzeV_&M(d3y30u zKjwm}p!SKDit=Frlfia#1|LFlXR(1ufVD0;FrRz~w>if!p5yl6pD~VGynFWmAq%zv zZb-?h?Pi1<3@)$FF>Z#$DQ=5 zm?dEg95Kie#v!$U8tY)30lA1Y@1a@{Km=Hu#2eyV(!`3{N{?`UBkDS(f32dW5Kod9 z#nn?C?vfi-q3*S@X8_at2BXC^keyN8076uO$VwPoI|M66={?0jC%;Gfghn08GAqPE zZzv-4DTRIjf$BCdNsGp?ZiOf@2899*+|Vt` zdRFd`Ra40CpFv6j8Uyp<14Qc(#Un21|D=3|tjzy{Bq3l~e2b+g_f0B1J%Faid%=C zqez)C)c@V5OqC8o8s+4n0#OwRYmvk%ZdIl^<-=~g0`=NiA50@`o?b)+qLezxZtYO% zizoTO)hyse{Lt~f3i{3vO|F*Luo4@a!>}}6wc?8Rw9V!VyfNIhhGq&u$H^(LJ?3Z# z&>*Y*7n+q#w+S01+Kmt&J&+RhE-1Z8YB9HhXp1dZ8r=2d^{K;fTaU_f69CAG%*@v0 zf61%`lV6J|=lRm&)ZDJ?Nu6|VRqDPMCo#&p;kE^D2S1FNsnPdQnz_Kb@4a z)OWpn_dxYBm}1^h;S1Jntx)mbmB3vZ%OkqIsZ0lBo))HqrF1dYbl%`ciZQsxePbTf zv_qQuxlSC#R7L*!TJ&KviYnzQj==mrl&CFniy=Q&7(lI=x{KhEa`tv2rY3;dI(;Ez7RaPbU0ZXrIk3StQe^AXej4x6(rLhx|oCON2>5TeI2 zEqMLyclhF8zJ|4eUtM0o4h~?Gimi-E-zQlxPY3wp5!Mc{ZiF?(m6i7IvzH%Z{@w2& z|LH%&xwM~f7yDIl86^g{M!YoxR)f2xvY2!Pt?h>m8>7<;?UG#ziBcLS#y;v=A?UHQ zeYdj-E6htiziBVF-it}adiA;PF$6~y2UVd_aeJl|N*RhpHN=$K)T@&p?-TCY5GO}F z!TtFb(wrEpy@B=XR)e>)rmrtOrCE}4a)#?gFb{V#_X$}PWKNMhV;mv`UVxDdh-}`M zIY%_}-eDMk$w>-U?k4rKgoqdySay);GxR-0*qMss+F_X&FmWolFe4TK<7Nx*1>P?p zA`C3pI0H&>_T*;WI4}TbEp`Lr>6rx(@qCk>yGs)=oCW;h4>$Pnvk@BuEY{(F`d>c7 zU;OEF{OV73IDb0f;@KI@xWoLghe*W!-79P^p5XlI67w>{IhVQ01Ez-qm>HYx0GM=U zpN|0&1a7dHj}zwm2MoI>X`KxL){J?8nIneCX{y2|jg*+6S*SA3C6OHh=LU!XOKA`{ zCY!Mhm`4aa!g9nk9r5mFkB7U6yW<1Cdv%B4fz4nrI)m#cm#_n27;O^YY6%`AECI|h zISx%a)9?3tY&T;Tm?FIQ*dGplLS9o*zS4HWouvz zU`)OXCq_{&d(+fH5oP0M7tgM z}ag#tC$_ln;nK1 zAA(`v{gTiP91w#CF~jF|&^Zh5J*H(rTyhDBwQ%Ezd7iNZpLL^rv~pv5E)7AL5S9sU z7~z)*6a!)iAf)ubKm*3zQ#}1we~x!w{Uhl6w{R{A>!!N=3uY*v^YR&zy1`KG(yn)v zmS9Zf9Db&D+VVb zIuXk$pIa7nSvU(uyrkoopqB)VT;&lf~jdsjiEd8;)0i(W1m_PE>bjDa@f98Llcb zu!;!s%2xZnMy4iAQ7#XNN{lS8t4w~uTr1hrMMkyFDFC>!CbJOo;fFG~m#NEBwCGeh z_%`uN3%q)gY^1~tOd0?Ki94_opZdd~bj&F_nK-9%0**<9U~umqtlS}4!8U0nq(vo8 zE68b?{xf$GmNmPgx8%nYk>T{PEUOX(bnTF9mKLvKeo)b^TK1v9xZupGOD6E2c$rtGE>NCP;{)IO*BNCD|<+MYXgMp)V|J*nx5- zBk6m8H1njuuxs3(bYVqyfOQLwW@2(D7zz`FY~9?_?~ND@W7q&p3naeA)gbure8i_8 zI|#o*jD(;4VgRu~Xp>@u&L@Ep1d1_zh6ErKH+18fqN~toG4F1@RMB(M&paANVjbgZ zWf;1YqHo%k6TVG6aTSs(Q;BHp<>m7W3KCs}F)Bl)*4GZLyY(!(#!>B}`=u$n7`;vJ z5+-5QkgG3_Z8)Wc_wu|HG)5BoVkf2*XK#rT)tGTH>PViBRSbmGLu)M&hzhMh@~Bg@ z4&g|uv*C35sAAn{cQ`DBQ?}eKd=j4-py_Or6#zmDh zC}M6EV6_;Waxri}`UJaQ{tkzq>#{-;mSe6Oa4q#)!)}+=su0R?oh#+5x))UZOBZC-VJb)PFFwWa~wAiW{0DXsp)rf zRtJd%h&9K6vk!iZ{U1LA4u=#KRf{pLY-eqMmDS-pbiYw|C!Oon7_NI{`R~hF zK@W737_XbOOPnW(BhR1VTeT49_7UA_7s|`*0QTAXtJICPL{#sTg!-zg~$(rJlNl`t5_Y5;^ zV9Xhaw-B*;fLJ*n`I#UXW8u{v7sAqblgwb4{BufUnU}Jc})DlS|-MkCqxpA+YS7Z4Inms z{=;A~FAG8pFvh}-1MFslyN3mV1U_aFn-lGIcgAqmz*+&j^glb;EX<_x2{#y+fnltG zjlf!yoZAG2(xHl*BHd1%;&IjW5^~C_2-*dnqgzKya)r^OS(r`wOzU8c`m&9@Bg8yJ z0I5P2YNbesk2sHn6f3_KX|=ZV#>ErGiC;53PmwNpc%Z{ZYG3c$OsNf^)tXWU*Fuf; zN1}?9I1S_I$%hzT{sh7%F=pAOJ3@3Sec^$a3VVE*iz}cyunAPx0wxCM(itBFmMxeg z5Pib8AVo=I@CbfN?>rj}KmHK%?q49B1wNB;XG9R&9Dk&WS8HlBBb_84Xd7pm)ZGKs zz*Cc16$REr>=<%7AOeVa5VG4M`UQ5gL!_3_j8T9vVF?iwxA4mmeqKOqU^ulCPx~Vv z3vS-N!8}d4yg0|OyTDR7iJ8fVxL`SY`1z1JAd-6vb{O;BmT~dX&*A^#zrp^0{U1}8 zX(>lxjmu(%Kxt0%qCYVMp_)>%SqP@ zep3N``%o7YeOT@xEncDCq1K1qd$nHKvtXgM?fUTKH_2|(&H`PA_WVr{EHlbJ4XG98iAG>5cs26y9 zmooLuo+>&lS%`zQ&AY7yoTu>A9GI*d#0aU}FCgR<$>c>=48W4A>tiYSlqT$fQj{%3 zX_5PgdbqA-6++salsAY1Qbk|p0MiLOrCGd-aJ7?6ZpMubC|gAkg0AMShqc zb3jXrnkC_#K{gc-z-7BW5=^+fW-e~e5+R6E*Vei|$55SZK6u#t-$o@O6cwf!URBD%M)af|&%+fV?wen`tdmXdGI3jkjxh7=9+B{bGpUOC+ zZrWD%jPMBa^CTJ$DAt!`K`}EAb3`l(TI;E(h)7A#>jOM8t0fCMqCk_9Ic3P-%@8tr zBQsn{^W1Vvc=?=+<9hIG**slX)t7$}H|J+t$~Dioxc{WG4r_F{UfdhJy1#_1#RRF& za55?OR=$t4D$XhE1Cg8zj@n+j8t74L|JD+AaX%O^JOSpXzrt)B+-94m_;!GEz&t;s z_np%PbJ(6?S(el?hXrg0Y%g~B{+sU*;qdauAHxnCxa}5xSunbD%*O-5asUXJjz=7p zJ6t~BCg*(&;9;A-M|KN#Bg~Tzv1~TLE`EqYUja+x<+N*jXI(x}2JdMuj1KEseM zC{n*K@p=UQ`~`*=KZPAGz=batLl*J|V7tTN;Ra+3&d)9oBf&2z7A;6Ef5Y|+##)4! zSc(x5zBuXu7-zGyF+F1;E?Jng7~GiNzxjZ8xWV!M0rqSQ__=e;KF*6PL%nucs4dc@ z9Dt8plA%h7QuI11p{~X2^^jKd7e={PB>+qC*(uat1UN_MX#MloA>2yXtm~PqDG6N* zc#A%((z7%Z>(9O{E74RQ73Q?~w!Xx7?re2XSUZCr`#o!9%BjMLTFCNP>o8f)t=~C< z9Rq^*)o~d@i&jPnu;k#}88*8Yu$yOylw4`dLj}~=^&vY9tH@p7le{)cMDPM@OpON? zgiOq5o3#EIShs^6t}q`W?%sTdm)9n*qlr%}C};k3$4ZD7*Vagz2Sgx52IhIf`N){O z!`quZMr+{1f+!ZRUmr0Y2Hfqx$Dbab*k8i%e!?UN?2+I-U##KJpG|jM< z;r)Wm*1?%nAW%{~vPAe85a!hW42c*9hY)8x`|v5os}b+s9bwpDnbR3)7zLZH#o+RD zZIY147H}iMjf`QmU>9K=U|qhyNE(uqA(Z4KnfSi2k5)rF=ENZ*0iuuTP9D>MO{()) zxw~cUpbKqHEawJ|UO~(;+qQmjhaAwwMY3}GHKa=U`B`HLR-E&CCVUUaS&=Hb?|R|r z6%)ise6lEooZZV^0wpj#}R06_3`Ve8W#kk#KIKN1P zlNdn~vCKyt5BKQ|;vB*%I)nVs)U0pw2TZ z$0l^~X&tI^$AB+GxN2BZ2CaTCqwYN}pHDH5wM1uT^L7KT(}*!E9j7>9``1l1)u7Ep zr(FV%GleZNwRT-<96wQQn5nH$MOjV7Wo03SYa@@gUFz&Um{i1$T3n@Y^E9WYM6Bv% zwOM@$&23Rr7l3BE5Iw^vv&XdxfoF6!2XvJ4(|4q3VtXno~ODRcu9i*YHDmR`*lhQU< zKEJi7slP-OImf5*n>_8nZY!h-RHyxFU zPi>gfwcU^B>DOOAeK9Q;W?4nbgL1DG3w15;Xt+qQa%_rq)Ml#PFe97cZ0#!X$MH4M}!*lxEcmhb;ocsi0XGnsOS}iqTn`tI(RsnGuoVGdg&iMq#XCZ{`0p^}VSucUHnq zb&)g&SOXNN?^IbYt+O@mUL8VArwqQb{!whMUFycT`OYn!J5WbH^&U}AtXxWGG%vF? zVH3AfsUkG$!OFU{q&RZvsuOKrJI`ej`+s>IUVT4T7U#@LUy^(BM*tZ zETE?!V!Zwk!b836C$4Zv;sPkSToA#|0)EOdW(HZtVjO}P%*O~jY~jW;z!yQRu;T`9vx6OWDISE} z2*?BCas;AB1i|K5-;<|L5W)<@HWiwf2*Lut%;~(uJ48tlpBO^oh6Rr~1Oy-9oWb%i z!{6V)EUC=Ms7ru8M7L+Hpgs(=rdFe=suj1n%%Ii^#kk6AWgS4oexO{=w*?vxXV+p{PgXHY?&SLZJU%75g-(yh7WoT|>&McmYkqmpn)+$zd0z?OC)A^3R)(`y{* z3@&bAHa1<>QP`14jJqUFHx_ve^)*F_FnKSHAtvXTpHkUR40(--mA`20fO-EmE#b^~ z_GAacM<6ohMc@VlzXXI}U<_lL78v8;y@x~t+NO*8>_dvs1p>jq#{lBAZ=W3A2Srrbg* zxeZ*M_mWF9eJ{eY;JAOl;r1Ow44(h!1?*;v&;IFq+`gT#-MSPlC&p$RFb)pejZ0{t z5E14DQ#isphv0` z$Ld^2q{WwX=8QfzM;w6J09Swc--CYn=kPp${ESEh3y=A5gh+&Qn>_OO$`$|sAOJ~3 zK~!Xq2)T$TdLT?k2t`cO1hx+2<_utqd75*{m&Gz2usu&)I|Pqqo(Xfq&=1{{4lG$!gmivJVk?@PKrN5vqo1|O9kDd%CGUeYo0H8&ekVR_0q%`nI> zpzEg?E}p@iU1r=#N>we(0s-*u?Q1-H`B6roY%pxkK*l0Q4>EwY2Cskk9{d4p>5sb9b++ifvK# z62yu*NlnG&YNEOT&`=?y%rr&hDw+*M>cEW@_fOwP@mS$aX6AP1;;2zUr+nab>0Z6N zp0o{9jeu9Im}%lA!zkQQ>=@br!Pat#%r!}N?5s|foOnJEih})QK3mYgu^oCnxLQfZrs(+4;Ob(e>ZIdf5&e~C^|5+|YMsts(@1D1CkXBtT5feitWTJfvR{W3QRP%0)d7}uIjci+#+O>m=)=;87c##TF9AK z6WMea4H@7`*($5Sn!WR)*-?9es4sianRV7tHPX`=?hl6|9rvkRoi?| zc|@cNLaN(Qw;~`%5o&abTAZ<7cF?NdN-VB6=_W^=kX_4x zsJ54v&rGsokI{0lnq^gY!?<#7)caVQ8mpqr(?i=j4p%qaQ}=TxB6o!{=mlWv1$5m< zL!J0TC+;(%R34xmc}0xXv36DfX^L5RR}G)~0#2&Nt2l0Y{ZqA-XncgaiHnZ0#aP{y z>CdAI#;%-A74@uN-&k7AxLyXpNeE&U5v}6oN<6CrK)k=V&KE_F#DJdZk3JoBL^*a_J7_OfnZa0_?cL?vk$M(4c+=#FQNC>Ir z_V|G5aKP^RWtv2Xn93`V3Mhtgi($9JoA18IW_Ol~r@V(X27Z|l%m&-D3oHj991oa| zM=bLZo837=^q@H)vP)|=nG~z}=wpOm{3*;=pM&$oQm|f6P8l&oob`LDBh9WQZWU>7 z-L`0{jxoe8m1?kE`G@Kinet1;H-> z8@OT2{fmNM|L#*fynP1|4}UxY5G>OSYaJv;jGJ8ovWMiL2%80dJ_2?F@-|(TAMP-} zc@4Mtdfq6{^hP|r>P zWoH3ZPEy30NhqC2rt_h!n6#%+y1x07O2R<2Qz5GIF6mjIWjMj_kL<0#(~r3ZA*Uw3 z_^80yBF*a-#kJG0a=h{eW=tzlXhR)Q0Qkg3rij!=3FIV!AjSm(pPRdjsOyv9$0nD5 za2rl3kvYa0l)=gplR)IdlFLO>Y_*oCQ^nh}dcY|l5agU9vNSRHZR2Lx|24^}Xa zW8U8l!ji<7Wflz4VD=HP>1^qQ;Y0e)&+`o981qc2aW@hk4j#+{{_bD)`0nkO`13z| zh7AXN@Y8Dyj_~@M?{E;*yC3_+^jaCxrPP ze%@oACM@$2ufM;=-~7#I2$R5>jJA*nyG?`eVMzSF7z3tb+N&Z2gBw65LPEsD?Jc&O z0fW6j5P@+3k;4Zczrb(*;uGAyxyMgFc@DuIZ-01)xOjwMaCYf1ZUy7cq~>8K7`F`T z05?!>Yj0;0&YaGghS0bw<*tW%w!1=&RiS|97mK6d4zsre=DY9uM4zflw zsjA~_#Q>>ixZ*fNzDtzzR)5B7OO15LxgM+Xn0cTRHXl_E^+0ls%Ic8^BnFHCdHEdi zlRt&qJ%tmW_BZAwhBBk zcuWs>FlImqb9VHN0CVE4kp(%2(IoCAUp<4IZNLxr5JuwslH|0`O6XB36Wu>pjW{K; zs&Nm3d{)FsfnR4#AVQD`3x>Zq2R`@!yt_yP=A5($lK7^>c9VwO*1+yAV2y>3KHaHB z;Jt@)4rXxJ3_Hw+L;B1OV=j(tPL616(p^6IRI?pI+9Pc?J@{#laCm@Bd-!F68Am|& zb!e$wpP}%-wGpj*js5;C-JN`G21)n9k~GyW+j4J<8T&xpAS~7tN=+WbkiW}Zd81{( zA4MK1ZRrPNeT;X_!WG3k&M80Y`Oa%JoJd3GSSn3shO3wua$$qv!m}v3{5%UIMko<3 z9b8kT4CP{q+{D}7kXV@mlQh9CR(MT0 z1X7LD2Jh>|R@=B+rd+><iGsR3G*>0X#A{4^vshnC91~i#bpc`HrH-(5Rytiw~M4NubS|OF38rC%aLy7>S=8KD{Qd~Ff5|X1P z>ND;kOK1^J7U(A6UhGpMDQBy9c5<*1{hR14jRlDY42F)ImqU`YO-({}c$c3!$H9tl zT3B}l=b+hf&Hbt3Z1HsCDmT{3-jpU}r%9aY?D#?2^WK?oNQupaZs;OP_OHTLEn7L| z$99ll#e;1vYr5G}!E>W@unup#(q z0^1P?3--6)<9PEHb{N5Kz~YaP81eI8{Sx1P_8klp{4qsk<|QKd01JT~#&ob|#>4&y z@gCwANQ}vGBs1n1)4|(V9FGe&AAStH`U)hztq50x`@mrn7}lzxMF%HO_bE5VwI5f%pZ>VGs7n zBW`p_Rw;2qtl$64ZGU7qBHcO=V#qPC<}61M=gK)#xuBcVSn2?vU?mKZa+Bv~Q^b1d zj%rUzzb7Q5iYkhVIkv1zM*5V9SrwWnK>=-eb$ag0C283?(Ctq-`=SRX-Cm@V$-pDf z{PJ@>CJ@F)OGI>Jxf2`KBiNJTk<)WpbxIOyy-$>gn9~qteV3S6Y1R%9b|5$~Zvje; zplMiQDlfJE%pn5sKo+(ZpB$||_`J^AgfTIR*&CK6m##@FQ(yx!1Kfu3{KW?Q2T7u1 zOah^Z!935^jk$ONqJ#HojrBu>Uj*C8n5P+R2h5X)=k)yAfx(;%97_;bMA(6Ge>@@( z9{R!uti5 z8Fz2qVctK0-I&kX2HUd%zx?gT2n#T5H!yC*^N(NS)wiExKI~yff!i>~4Y1iH;n0l& zHzem@a@tVdX9Bzr9n-d+6_PZ@` zd}vWlDFPE!tV+9M$f}TyS`kRz=z1+em8j)JQyFx0XRw&u=u4Qvzx*xa=}TC{h<-wl z#7kw9k_pxrjKhH77i@+B)@>3~bXmY`Fb)PxjAt)D#O>WZK6v&b8NRadA}llf95IX= zL@?M{#83t^3%mzF9uh1J0q`^Ccdyg?7M6S-u7wL7_p-Z}aH!rYE}uh#ynKt2W|c- z!rnUAQ&CrHKh5PPHeGV+~A&>+IA3w5h|4^LA;LvXNWtd{)jGx-OfU0L$R(?D`9B(-GxBtwv~pYFuiL z%Ra&=U0Fjb+Q8~HQx~-^{Jhg?yXneQSaCH<{H`qAQN@lbcfNcNs5L-=+D3Y^xS9Ot zm^2Zt2YYVu`i?6^nd@NM22&YFQ96gLjsrF)9JRRn30%EDkc&$BNa$l!R0ZkOE?8l< zNnKhE2l@*ZsCO9|Y7k&vF~-WXoZgaLm|${~S#cmL;4w1Wsklc=#3=Ys8>}lYs;-Io zHHXG$f+`NG2Na1XpmjZ^)03UD)6WNY`~T9uqKiJN0amD9qM5aO9({pTealPRTP_(Ofho?S z+zxEmL3_%DwRttxJ-9fVq}wY?Aym77^rag4(w`sSklCx0_p~(zqQ(l*hHesDDFx{@ z^nFM#IxXHRgQKp2Q2u`ezWm>?`g_*c3jK$y8wk6o^TQGInS=2jcDF-Zj)*>S6Oa3Q zY%ecUln@K%yEh7b+U6e8nah9`UPd>2OT}~4Jz7i`v*Y3vP>sTrQ6=<< zNLEhL8l>O8&+^E3sSP`+4x^|O$J%)^|I-I`ZjuvjPzT}9$b#nH*TrlN$*);P5UyvM zhK7*$vWZUJgZjP3NwJ!CbM$kzmOv=Unez9tH3?ZjskkA8fF%Hq$JAJj0f24i{{q?< zRj4G%>0^w|47rdQYKf)1ESdXNqTt44mZ+@`Qez#g+hW|7QbY%9Qd_n$gb;=#{>1cL zElW%;o(RlS`uF*`AnZnX4{V%+kHEYHgavhs!d@Vmjkt3gEO9}Iz##!` z&aR)}=G{F)5ZH~u_UZ#XrGR%o{0nxw5o}@-0Cx;KB==t$=IWw~BDl2}troUW1ik!R zCy_u@QBkbF4y5!`DTL5}mi{x#aI~69B=%`N0N#hTF0ejcQ(ZgCzm* zv=Z+-hmy1eiB5m?eBQQh)S?tYS44PHbfg@Ab#0B3lNQvS@g$3Xbp8z6pZyBKIBd2% zOw$ZEZs3?P9q+-$G{ce2Iz~<&8Dh*naV&3M-{JbnQ`o_Q$4wFd{Q_q#*jpU;x0sg+ zBoW_#@kjjp*H>BK$+#|Ks=z~ThvB#X1;W4oKVb9wN=&&BrvNJOxQq*4a5m;3q%F@S8se&YvJ04%pw{!x@L!5Zw7W+~xwaEV#eF#pRPH zK$x-kfas4{jx&aF3o-_Q2;1Eb zur^qyTGZQcQdCSY#g1Uw(8XZ&;0j3r|A`S3K87Xu*pBn6y{kYx7ViU-zMfHv#`Mg z9%q+f6&|V5L3>Bnno$?}OPoHi$@)zNdaeq!Sk~IHkU+BB+%x6dRVJq@-dNOl{obYp z8LOyXJUO&Wr^Zl)3KvJ2uAe62)IwM%Oqt)O#GM*rTZuNTTuJK`b^Y^I47L58I+~e( zJ_(&?BZ?BeRFg1OD04`xqYTL{aBY#(Ogv>42pLm_oZ2R>BATQH;Vg>wOvRX3KjBt?43!tS4pM4#uc%EUIpSRLPqbb#3)0ZcfH3b?Pa65oI_{R7ycj zWqnnJg0z^I*9zsrDhMHc@jnD)KN3sqavPwcNEjogxOf6Ofn`J4xD9@}#_` z#FrJ@v0h&CMctGOU1qkF`qst?3@s9@qHED1i4#qr{E<86`HIb_cREtk& zUKZBvyb2Mp0?_vYYu+_ffpgvPskLZp7h1p8q_{5SQXIWcLB)o(E0MaxU0&lYy2(2; zsuNR0GdkOFgoxm!eJ9d$kcKtG=#((3xuod=kgyXif^$+_q?^gn4&+$5pVt@$p;LXM ztWhCKYd$CHa?KynCnnqN>WUpFl=^+%qitCPk;}Y?)C`5Bkpzxe!*ZJ=EEMF zvvX`WTg=A;ru!QpM?9Px@bZHv365_F^Wl(ONJ~g8FYjU9kP0yj!-olB-h)Dbe|Uf$ zH(5aJqzL_4<^JVrA2W5?{D;v!AZ%$nnKk56JO&gdE<$%roX` zf^|dsNP~c^fgnJZ1^b74TtB@|=dXFfA^{|VJ5A z^`h5Lm8GuDyJ{+5hkV?Gt>(H@@%Tc(qrx9*XV4N=rZQZTbzBFv)JNiJdZHiBmN;D~ zyn;F~*J+o>k}+h>Dq^cH;_CfrB^JtQU-qvu%&d%RQ_E;tRJQE1$yrWvI)Kp*KBJnb z+xfY16`M(bRs&@<#HAAdDzJ6V*xQUn$ZRh%2E*nY5&^$N7?)mO6&g#FJ_N-XOQJxE zyoJ05ry@GT28&-Zo3{w>0mHb#vMhiIoZTTzdkn)lw!1AHPw5iK0_V1wG055R^2x){20dU;in(4*%>&;7&esHi)=EBwG~k1L}bM@MO7dJf*RPSb44mpY3Ig* zEC?ai2;rKqtDng{pRB{km@!KY_fpUyp(U{B6zfvc28>4rZfd;{eJ(n^EBDd;b!i}- ze8<~hvNXNwYkqsZ5*yQ$Ss8G+4+~fE&@iGMVSe%n0tOhe@JkZpQ4JZKqN_uM_cM0q z*U6|5mW<*exG}A7hiQfo;q2lHVv@M*tVN6w!+4%z_=h`8%YAoJ znX3!fpZ*-4&aib`+`ak%L=lT$Aag`q4)Es-cGuTweG4Ab{;o`$V96rJIpY&XggE0b z{`GJ1-5|+%b^kF~N<|oP%>;$(guDBvz6!A|V2r64Zi&KhYQ^C#=xiy75 zKyhEBAk`_d=8(i3f2t9=b<1t8U>1^&?SbC^lpnTLb7Q&aNK9Z&b@8kXxri&cYiRLO z#FImNxd7w+I$DxiyQq^rr$ zCi(R2p-oJvyk3fp@23txiH9nyRz@hRbt`hH3vX^zrwkx&E?(7YRxa>L+|p&;l5OCk z9DpUKQJvYEuTwudxJ5O8K=f}@mbKejJET{f ztv>yn;>GKBTSC{erQN-Z)WloGVUd-XRLT!xZGc}Nl3Ov4tVBW0!Hk^?v;JOcZdJrJ zk4CDnLRbl^)W=%8pQ#KfifgCn0F>9gAWB}8)|{a^g0P8UD7STt;W3z_|g#<_v>%^{3tLt`LR|?r(1(%Y@_p01E;9 zf+e?(a`f0g+yTcU%)48dm^;u&)O!c4o7OeMEfErI}Nx7qR;x?FntN7sF%hT&Z1A9+j5$ z*s-tbpujZewdhuE$Z9~w74EM#6XhiA5eFgUwLUkEYd$1EmBHI)?S15ma|%YgjR4bJ zkYIIAGHuwX9ZQcel^<&YE`x~j^EDT58=~ogs4}#UTI4p=?j@4&MiL{)UVzPWklBLG z2qu#o=tI5Fl!6nYzIDHW;=JhL$WM z5r_huGe{j68!WS6_JS$@(|h3P37&wh6VUD)fB51x_Ir;XzCS>i@ad;N!ry)N3UBTS*5vaV zF<1zn{^|)XpFRgDi5|`w_!!}A`fh~~Qmi_ER`WdP5;Y4JRqhr8jIs5*mx@!eAQ1vY z76d=R`y)b_aXc>g=9>w3cN1>!ZZIDwY+sy%5pcdur5~H^h-uHbd;1P&yO%IVFddF? zHtjw4uis#Ic@FCa+`fB*^UV&!NLU^g+iu!;D(IWlzd5G{3E$ z)u2m`+PNU+(#d5BZP>TQMHIXSLmgJp-vvrB9z+cdIzP@M_i8!$d+^Aeg*xF;M_YSm zF&lIoP|?H5j#W-M@@c>q=a`QNjE6f+_cyRtLu#Y<5g{(Qc#AO|_Sjx-0Ew6m51nT{C85g~dk(-HgoJB*{l5(V3{b8N>mAOdlk5u%54BYZewv^(72yv6q70xW=K z!qX>P_~Rb8KfK1plWPPKjJqAg$1F^y7qc7|_)P>YGoqidzj=pocMggfKSPv?OAKJ^ zc~kPewcWX+M6k_i$a20{cSr$XAwBynBZVI%b- znNHFsCqp0k4%11otMn-s{cFZ#OaOI!OsMoiHL?-GQU!zRNNa2Kt#MeJbUhF~F*oj;>O(m{S+EHWDcA3}FAa#K1T3*@|ut1bWMLeTOH+5(+P<~VlD;ES) zCn?ljR?3*RIkmWONUMNQT`JOZry@xgn={P$a82rG>RpTaptz1kw_le>nJQeN?uz3; z{j{aJ4|AJsI$)lbf20eoX@Z1`XX$t_v&m5rsm*$&As0fc190!cEMQO#pYQl-nI#cP zy31dgc9p-cOxpBhtN`=tCT5zL-PLci{vt6Nbh7=gor8;gHw)3M;=egU+A$bd8oRB$ zEYmJsoHux*iZ5chIID?<$pBoGnU&;`5+iDluPz{=sD%hX=Dzr*6BC1Uj#a8mk`nh_ z?LDQa$Z%y9l|f7uQdSoQ=*T=NF!MvKhU2?@@0Xo zMF<`|9kBoMGn}1WArQfhXAl!%FQ38fp5XB27H+%%g$2{$7PQR3bU@hO1GfNKi*P*R zgO@KsWU&AKI~?|RxPI{=j{7}!mzQZ*$ij#<1HxHaPd4L#Wq%JWGbBb#w{J1;Z$YWq~6Hn4dJ_wDDnzxxdF4DFVLZP=41#bt0A|A7;fR+nUgF!o|3`fC%TIuZ2@l_W3p(E6+#hhZWw6WZ zux*Y)wL%|b@#4?uhwiE zd)^b83!!&lquUpnxY^c_ZYWlNPOj*fq(SDikDwc3)HAl?fwuv0D7&_D;tAHaB3%{* zJkoZ!y5Q(v)6w7cds>8ovQIKo$36o~9WE40abhtUjAuUq*$+VW3XtS_Zp>fB(~2<_ zMOcdv=2}9>sg4rH7#x=w#yD8xQVV-c2^1$}`b?}nL-Zcov*)-Q2f!av^qd98q&vUe zFktohso-If7!(56vquO6{2UR(24Ml_hlphoOviv_0;Yq_40L{9LL-t#B@};RVi)u*N}TfsqBzo{jkOn=4#D11>M_v2*u$ zc69~EfP@9Zu*Jtey23x*zDb|CM7Ydx4#8tHINbg5I~*R4u-4)3e#U(iY(~cUiDo1j~LKm3Wg;Lj`4(%L1xNB0n1? zy=B;D=PI@0N|nS*?!efW8dPUtFE;4!k}+KGXq5|0jX`|k8eg&QPYo#T`&}?kYk_ag z?bQR3$D4t3fRd;sgz}K0GVS%iil`-&^x9PJ$PyvP8Dt;eA8tXHFJL4wd1H(S(Zd>> zzTW`iJY&9p3pqUC?(QDf&tAZT5D75b4W54VAvPO_<#>-E5rZ3W*dO5B06*{H#t{O+ zGS4uZ4Q}r4a1|zi7=tnJhXY~=m|{qA^z9bM@4m+H;YWzyeUVBN}c%$C0))m0D z2(sRtS+lrt%2vfH%|;3NgZn+Oy+=6g5fNZ{03pIp2kb_N!^1sT1c$qKNx08zABPBu z3lKeS-o3>zSlqwbWB24aU<~GYfrNm~;1G{T>~C(t&!1v*@g%dOJr?gVA5%Q|`sxY% z-3>7B0cM10K|JhX$$(A9AsKDHl1>|8G~(A<0`dTXZ2_G;xta;?Hz)mU36L> zz&ZxMN{-dLiEisCQk^L!Rn#2xj|l(rSo-D>XMN%znv;7Ah+wswNjvGV0&usuqZIiw zDXkT--xym(gg|TuNit>XsF-4KOYnf!eB~CLOGre>+jB9_&S>2wVGEu|R7P zPf7$dyYHmC9C9tSsD~}}!AA7Wami_v4@P;a+C?JKY~caDIYP$8T3lUcnJ1;=x^22+ zdj66se&{(q-0axYTv91Tm<6ajJ&kqKX{>=34{GW-Geat{Hw!Z>I0s>pkpRMF_1l3kso*zmLZu-z3b+Zi!LOC)?QeAZE3z^`)h2Kc|?>3y8k4m{h zb)Ju9SUSRmFZk{~{^Q9i~j(-*ziwVhR-ALvAwua^p} z<5=;eF2(X778dodDJy-~K(0z&>uX6YswhZ;mMN?(>e?pt-=*K9;v6juNTRjdySM8@ zHQSeGLl-m@CkzwSwZH6bI%*7~_Q?PRzAoIJ9Hk@O_9>0OSN`tR_mg#T2_&)9^l+pT zs%3>mz==DjYxFHH`XGG?M~zDMtwoxbY@WSL)9$!{Vg!P~vIE%R z{@pu-vmN+{Z{W_(lTZ+6?1l{}^P3O^Y*TW=2M=a~C4*(ZhqDHD*kXG7E#|jx5snLv zUww(?i_dZHA241H;K2agoNN66lxi}eIEIna41^w=kes79IWU$g+E_}dii5xrcGnK$ zj_}?0f5h!q1pg1ehrhnU+3!BZ&F7zCnNpF*_4O5w3$VSuh7AFCUw?^ZnQ{5?&+z4! zU%&@JxVy(6{^lR?)wsi}Z|@M|02;RVzz(T^1;E?S{{SaI{Nw-E*PHxIl4aMO=ic|s z-2L-!h=|OH94j+HvOt!|rW)d=7efD`S_{x#fHn>EMi3-Gjb?XAg2XD4RmI97GBSp5 zba!(zd-rPbru)o2#T_dmBffAqd&9Zs{Lc6IkN@gl<7VFBdG~~?OAmil2L~}tQp;u_ zTG_jh!dxO$C(PLA)81{nwqr*5T4g=$G5FC#V z$01-G6|4geAUxc^hfX~l5k9)?u^T2#K|tbgdExNa|NIva_6YNU&aW_s8J+LqyT+0E z7s6BuS8B9VF;4@W@5^2r&k?ElH1q93NcWw%jt7|_(+o7n5PLu9uFU$uw8e!SoL6KjKc&7fmsm_N9cY+*aaXs><@}zKjE_X2vhv7kGli9 zDV`4|D5k>z83prk#&)&FxI2P8!F5`g#knjkM||hGII*38b1X^r{R2 z5M)lmPJK^APzpTA4ESvNiyFtb6!AHyjB*h{yuTrtZnD74X3#DsZp_VlxCnkMkT`lG zG}t>wJvW{+y3h&wBi^>E86hTMsG>oP%Tmpmj7#gg#x;MQ;}XaY&i9yxBdCkt^E^&K z$0aQ@9rjR}pmP92uz$LPUu`kZBf8$h^&Z=+D-e5l2F7VB&d4zCA=3yV!tt=fm*0Ge z!|@2Ggn2k147<4a5TWbWkZy(W@D{^3V9g9!ccAfTG!8KdnzpGu-?^K?Ph#%d1Ycw% zY9s90f;AF?2toEoj6ZybtFIq12F2_HI?s{u&kjK%mwNy7gzfeMeZR(dJOIZza_Q$I zWPiYXWWak+n6X{;*g+A75ySp~uJ17&2F$~Nf%mw%xP~fWd$|P@VR-iz^W_b^Uqjx# zMhJrG?heE6e}`^7z&V9;TAtsWzG<1fHZ`Nh&tWVx{&EaP`L8*(Eh@LItIM;rtN{h&eo`{D*101+18)+`u zyx=jNIcSw;=M(0lw9^Mi3&s*R(acg~ZkkZbOF;3@a6W&&TrS3?SO$yu3u+`eHXoa4 za1vEZ5XL$_(!a&!pTRnWN(9)%&Wx)S#5F>01g3gnpJS;7D;&8}h?7de0M8|o>6puO zB6Bxe(JWbbxJEh2RqJnU-j2ULXh_1hE|_ME2EZH>XCq9r8UeIr5|N1@mq{(<=(nDf zmNYRmvh0MmW!bXq$VL+kr!Ef=)YLFjalnT3wVAjzmI8wj2VmaUer6jW^&<|1BX6hU)&sa(G0RLi!=v2_5l5zalB9ONctCPZ zoZX6C7Qf(%$Vj4fXaQ5UbAq5SF1e*+RMT)Ltx-~3J6aJ7jc`kCyB;i8S6k7YEdC%3 zCG7BsO&pojw4)P(!2>j(F`JIQa#?dtfmOYLb2Gq$Z7fCQJ=Peqv;b$VB~s-gp*0Q> z3ttiom#ffeFCc76sno>nWx*kCem=5p{^T-uQDEia16bZ+eEUc2r!@}0_%rKz z|M@TRGzLWSRRDD!|7e_~s`ZKRjXk$xB?gEG2Wn@*0?q2*(lRJ50MB z=KT(Dzxx4~+YMGd@a)r1aGV1^{N^7q-ag^@{U7j)7d@_C^x&=)hV#K+U|p;k09la+ z1?hsyq=DFINs9X|Tx6~6uMM<@s{G2`}! zf55YChnK?xIt}=ABOo8ZUFO532pU}3yD5pH66!u~2emah(nfR3nP9;lZXM_-|Gl0J zhYVw~5y+|8=i=wC3#L_$LRNSp-)f6Gn(`kidtK(B*>eaL;b%GQId`F14j+~mu*ON4{=mu-L@A^r zio;vK{tBDzzks-x05K$tV7E?dSQ^Sf3ho1-*wn2v=4pV0F}k{ZSALeR>!T3GK3y^c zRKyIbzO845SojzJ+b&L-G0$2+aP#D# z>Vcrxj6j$O^9URskC;cs{z} z#hAnH0n=fRVK2D6dXCRuDr7uh-9^#PyBKfXUT>hohz$cw0+|$;()+m&`0)KZaOdDV z!s8h5@G#=yIpO}{h>u@f;&Q#h`!{!RjvWpU_wXQ$hY@RX zg>A3JBvEP8l3IKVy2aebG{=4mLrYST3pMB;P)$Foq&oV<_#`Cy29@Fk9hq42e{)7-LS{H z6F5$3L5(@wjFoYDjV3gAanX3k55WwZP|xkvB*O)XmXiW@;8!#d$E_{{m?W4sNG&96 znn-~6E=7b*+1((?T6Y5a>fZzXi)}QtY_`Vd79#k2#6LiYYqwV_sPj&VR@i9m#3x_ zMLp+sSn>*%6ZAZd=EP+a4JWaM`al^jZsF+V$=B%x-tu9JfKwK2ZtR{XDll5{CA+`k zXUR>hPSOidI2EQ%_H02$dIOju52_wy@{e6NDCZdS917sNvBIjv3r0TVE~31G`0MfK z!FiD@|&^E}4}zyh?_qMUGXhAmuJ`L#Ii z@aM7#H9?D+n0!vtJJ)061AWL;(77NevBtus?T zXyxSOQYczR1F&d>PX7!QD)DR0!fIie7}3_DuxcsA^K!RPJ|IWOcNQ2DgIBa%#B-d; ze#WIajoX+*jOEmlrCjoq4;F<{mRGAB$6fGhZF_2sK2Y;{=V6elMuygj6R~w8l%}_& zo``v0toX;dY0(sz7`s9FcVr|*e+jJS0mtutjlX#D4E+6n$K*FS9u5dXxc=;GT--dz zo7eAfeD{R?y9eCgO?ZF%gbov~KKlix{T}<*KVtjwjKl3My6q)Y82|7e|1;)k#OvSu zJ%;0eKl$a?`0`J`z(@b=m%#1`mrp<9>u)a6ue3TLiXfXmtJu6LO`kRz*MJn9L&lCs zEZQ)`p+*qT6LN4B%%~NxSqa<$*V(SBc~6)z%>l#j{u3U5|F_UN#0cU(pg)Z0-><>D z5BTyp;Kk;EeNWg8Z}EIJ% z26Iw3C0*3e=}AwWu3{O*L`@@jKFiv3T060?Vw~N3Er!D`1X6g++1Z>0^R_51Se9kp zTT(esDR5D4xyFTaYh1Z0R@as|adk8xxlGLNR|oX%aH;G^rIbV}2M}6VyA{b0zmpI` z8qR1LmYpjaTM8%VnnA5&*s-fZR9Q3Q9?@;TLD&5iBwRpb2j&fw=OjRNQCK!*4yu|Q ze4+5qGE|<^+Un9dI;g;bA(%0ZL*=6<=N5Au`c4w@Bjgw$2j?VS!W}0bU~-Fk2Xa|l zl9P)*Dc5%5RDk6Yu%4Y3Cb3@wGDm^v;%0^EsJI#w*yo&tsArVkDTEa2WPjuP-o7Q!M$= z%ujT1&X@1hd!M*~4!I4Ax(y~)tS?_+8lKQO!9V=dYy9qyPw?}E)#Vz~aDciF(=_9o zZ$8GG?{9G&kI;F7^F4;g9rSSk#v?v^^#ZTo+(9P56JeSO<22%WyTYVI?`DjHV0+`Cn9+GxT++JWt+pvPI%(OCX#i7l6H;O<*w`zQ z5NNSXgn5>No5{n;5K{ZHgr=0ETiY_ z_UvZSShPSg5q|dFZyEJ%lE4nLih?!^um&*f5FT#fE}vmMPM8l5=q{fl%<+9*Z?C~B zkZC}ej*$Hh``r`z)f)2@?{hC-ya4+Zrs0U)n;$S8_PBiY3E(<({W|jfiLqW^qwCf< z&;t&`fbGQ^!(k81fXE1SGeW{_Oiz2vw@+|1#=(r|1fA28^wNSNGpt^dAv|@SQ&TXg zDivjx9G_7n)yNfAf)~YV*yF?h_;0xT{ta%fo?*P&VjKtDzj=dC{^D1d4oA>IF^@;A zH*0j84sw_=?jOC;SSYIEpxnan0zLzr_v#*D--r0Fa=B+v zF+{LI0JXR{>xQ*PxU=o`%~3UX16Z>In!j_I8nD7TjH$yKdQRN%Ski+^Ww$rMgjVPDmTDMvQ)%mn~lM8d@(+H+UM3XZLj`97b;mag};iIk{D z;?mDk+%KZ(+0`WU#fG=|V#RU=0-#5Qj)I9t_^Te=#YHuyihca`klb}WLnt16iV~m6 zqEc;D3d6Ki<9f>sK&3{EtucYcR1Q>Fb<4iY_>6FIAsH>VxIlA6C=2wZb(SbXz)?#H zY`KYL9~y3&_3M@hm38Qum?%v2);2&U zNJPV1itiIQB2w$nYa5T7_A^fF7%w6eST+Pp>-exPvy;}{#kwYTeF%*>mt&Ax%7N;E z3uXdV2QjC%`20@iXisL@*eG~)a?#I1t0N;$+W#%RTlQyeSfNHVTE^uw69!Brp)Quo zm%qak5t59XjLtf(Z3N~t{-Ran;_BL~4d4W$$DNPHNEKmin|9@diCO1#!OlW$YZf!r zG|rzoSw|5tRjj+!9JF)MfnoHi!t5!e$}brJ03ZNKL_t(7!^=88Yb3F@k)4Y^*j7Nv z%v)`-Bd}bT>F<7r!@Nc}1svb+a6AIj<2~k|e!?9O==&b~yB)s$&%eeeFK?h>f^#0T&RD>P_5pK)tu59dd`4WJf zqVKyAjCr2G;MjUi6h*!u5CSB`@6C5T zyzinrRzfTR7>CFO4Fi~B95IX&M2--dA(Mi+1Ne|!>wxd#WQQFmJWfoI*HP(rs$^aHi}F27O;q|`GkfU+j5*?QexQ*vb;Z+7_;lu;P~MKloWwI zu0H(?@I7=oLWe#2u7^$&Fe!MoO)0o2e?S!2?+3j69`5gO*za-N z9r5`39{>Gs{}IX_M}3d=MTe_rTim{R1DZzgc!b{mh%H9^%TFduum68s-}LA@h57)e ziIXk09Hu1FjH{l|I!@2A@?rr`dIt>Z!n)_Ic-V^e^1l?NPvY)~>v#xN7s~_p<=<(4 z#$s4A*g0$5YF)&J`L8(B6i#Q&Y$5_*+k>b$!D)I3*wCjVak71;{iOw;ldDmv?&H)SW%I^}9zxD+P<8R)E# zl(?0G;MAvhRNRPNmky)rILYl;5wS{KqEqSsY!bLRU%E7lJb9A9W>`?|`|(mYFNA9t zwiuZU5>9@fb&D?9Iw1rTo3CP4shKyCxiYyl;s^}y&NhUiX(J=!pwz`Aqq;A&lwGv$ z!~zUYLU2Ngnl~vuBT7vSt1uY2idJo;ks9~z!rIp~f5R}_3RiT=Q_xzbQ<^6QU^?Y9 zWp3uV%S~;6qE`GcjgNH3`LJ}>S@uj}>=?%hIagb_i)$C{Gq@~30vhL8{reTp^Vy4- z*eP;$4CX`d;=gn0yNS)!XxyLK^_mC)g;8nQd)Wb0L}00L=yEV99IE!`w8cNmG7XEP zsIk_C#Xf=AqkG3ULRpC>m#sz{Oh!KRTYhGYb=51jEyS|oS^kV`fl|x!$y~`ACr{59 zr{_>?nGZ~ypH2j9>te{mIp@eYHS=0wSzhh4Y2(cCVwFk*L>r;C2$aS9Nu{+c}yVUS8y=ot6;9%Tq9%Gc7H-Av1(QP{g0Z6T$HT8y4KWBFxhahGNzk z`@28j!@qxv<22*zZotLy0sXoMuUw4xU0=rm2NX_Y9J{*&Fg=1UIs|ovbDBB3L8LnN zQwe}t2PEa6-?U|$%UQ^ST&-(eFg#>wIq-FWmz`k6T`rZ^NEN*x>gqG61mZrcH-OVP z2utY?XX_zELB5K$;95$dL7--@m%@HU6Wd7%`w z)f9n*S~`~Bwjecjf9|k-j$hZt^R#uB!y+oAa-Pg^USS-(iJe?dcoP5ckg)|yn8q1; zn4xnT7Lnqzxo|U>T`V%lZb{J-zTqh|2_{Mv*On?EN;@oDIEr$DLnM^5sEs4%*<~yp zGI1-j7oC)=l$c=>I3)H#Qb%C-C6t~4I40Lw4-GS97$DOG_zrG;0R=EmBP7fbgnt+? zA4hnO{LXH*26Y~+GmgU%5(3tn3%GSW@G-^rK&Am;kCn$e|(SK+ef^5^$gpM z!?ZtOygwq`9?-9V?d2MR2_N08ar*{%I*hov>=C9JfMR!dz&MUL%mMqugw0nOT)cV(Vuyb1@%Yn^ z=%@!|f}?-{AWS2=zQ=GJ5Do`CjRQWud4b{fJuW}}9LM_ybiM<+4sH@?AJ$v0Jn zjsc{M@puI05q;mIa~*cCe~00Ihr>^A5PtY4T!#UxwS!+#HNljMZLi>JHy`2Njr@n!;k^%q~jZ#I~oc6j~$2i)&=`26e7@aDU>c#avrkUMM+5Ab0^ z=YgIB;1qNX;CfX_JNf$zHRev9Aah8?wOVxsG8}y4uW4EKTKYZN-1G7UBBu8#wJzf< z%P*tX67j#;&of4&a@$)Ij%AH65g&Q~El7+Eu*>K>q^946tIrmS*fec%_M>9TlI#S> z1Yg8Fh!KLcI;|F#QX2;=wn@iL8*QFiIO7T+&ES41j69XhxTto>4ByR-#5opFxNmhB zQJrl$-jMzb?_6}Ia2t(nrWI)sl4Rx2`EPD6(`-Fc&1B?Un_1&9jE&WRV+a=9E^7}r zJT(oL1GtPgE4*hTn6!?aR)EZhvbH6e=v=jQs~9)&Du!v|%n=|u3&aFRaiJELqz6-% z&Z-)pQL+UC*|8$kk&rkV)&45R+U%-KC7DT+9309fuP?=(Zlik^5>#56a}0>ofcuTQ z4}d(CmkCUf2RUbfLemtxwxdQHW9I8QC`mc=7NIzao@y@KvM6h?(VLc;(?%(?7}bit zb|Fe-K1%T-?iP)Cw!Jh@E=>b=!kF)p0$j zG5@?~iq7fXvCw}t5Nc^ym$q#;e+O|A7+b;1I=)0xFgK0s8kT~8Wo+@10h>9gw2S~% zc8*d7MoK{}zzA^Eh<`+_@K?7|Im4=zX3}aK|0;^X4k~C-zLNgzV9p#cj5Ts){SFdh zWFxyKwPdQBVkR-JX6ycnA}FOpUm8GFK0><0#b3A5XsNJapz9qZ5M&Baukfn~!(Vw1 zFM{VJAod8@mH`)M;Uk}4L@{UXUOLd{f(K}Ai7dE=RPdCYv++G~Od}Z!{<~2J~wg}5K1n2lx zX$;Tnvs6c8;b}_6J&56DRh)nnmrWfLh~ZvR5#k5{OFakkx&yDe`2Q0cCRzMW(jEw^ zg;Q{6OE^wA(kDW{o~#3P_-32I>%SL^XSQ)AGSva8Nc(Ga?o3);kO~I70MJXYyTN!o zz!AfD4q=Kz*6}#txZC04#dA!l?G`HVT?Z9GzX7IU!2b3z{=VnWaPi_9#{B_dn$fR% z4EML7;R*f41po&}z+wM{^~DxJ*)W?XgyRmgNO8i)p@+hE4C<0z?p*p_sknU4nHi9p zb)!gB_y2mAs>OS8e{t|V7x%fcA}}DcV13zRngrZAY=kgRf>{MGzW5vT&;LBRFts|2 zDG67qn5Sb}KLrv(EV@%g&{(|Z*hT)K15{>!QgbkB1Xz|m@b2%w#rEs3uzGw3Fro7v+;vcPSg(6b9vI&|;@i9bip!TTK|w(jpm6Zhh{ydC zo`3c^Go>z#m_-iG|1W$~{u{1BZazUPjx0C#Z+Cx-s7YM zNje;~)*DZ=Qk$309uPeiZg{O!-|RP z!8u3$Bu}`sTxid2sFwNEK6B}3U;+sj;4~8hEO49-+Gf)eGg+qW=qsX+iCHm~A`wWN z`7iFo8KnsuOsQowO>G*q5~DW8p5Y8}jr(vaSFkOq#r1^R(w8|7g^O8=HQFhdjVM4V zITf{(juc3|F09N{504^hTyVw_TjI6Rc2yj=pmGT-;vcn>O)`Zpxn#iXSWV2l2rX&E z$!r`7Tw9)&-hr3EaLE16k3W-xiEnUO)j~#u%{bin5BHr&ZRawP67$5iSpN&cX9Ql$db#`C#f*p z%urJ@km9_waGgN1aXuH)3D-kjQx;L2I@(y?<9=ZrBtl?ni*#y4(Uj+84oX2l#OYc{ zaoQ!@tH8bUtC~b~6rQb^5*za#aQ*x_Ux2v|mN*p=3?`f?SdPR=wbyMht1&dB_Ziz&lUTe2ir z44c~NvSnCuiX&nn(h=9ss$rxS0nP~A`5iR-L}ck2_N8I;m4pGOe{;uBi4F@}hiS#6 z90Mz^WHZ=aPS9c8q0|hC7O`~A`zFfJSu_Lfsd4pXG1kWO1} zcW0cdCDxyXeWcH-tzRk;z*9D^VRdng3D=rnJ=@Hixv6c`NDWwB7nl2G^D;H`%H_V= z77b8wqY~`UzzobpYw270GZ^3=-hBfJYxI4Gd7NU|00c5iZ1aV{_bX7hiVduZlc|^y zgn)U*;}36Rq<0%)E9+(r??^Ae#A;SdbHL{xUtxQ_#^cigFFtyP$HzVH zA9tW}f=naEorCWfK?R3##?@8i5#HY)@%Da?c@CJS8RIxNo~O$YfF9NTFvGTR@h#N zrK#O9 zY(RjnKrT9f!356{Gf@V!tU&1|(8P$W;&KQkp_%tme2XW)m6NP__vQaf@uaka5&V71Z2(uOK_ zvWw!h5V7KviGi8}T2Xk6d&M3u8c}U=P<72>oPpTV9JCBr%m7m359U);XUQ|kd34(@ z+K5V~yns_otClqw5f$1@y-$is;L2xI{ywcR*0}M^r8T#)5Sxou18psw7BQc-Jw)OR z_p2>f5lFds){+>MmRO?N_Gkr%3^>ah-6Av>HDjKx5 zZpB}lo5SRiXUAOm9rjXLtv2n%oEI138r#uUydkbkA9F!&-JXWommF+`d8I9I-$G0? zWH^LS3QbfAtXlbkMVOKzNTIwVndIyusS4Ix}MZ<17S9EU2ak~slY7& zI8R~nMprp0nJ<+h=8lW7z{cs@2o+82Y5^EF!k-nXGF(|gKA0PxfjOL5S9P6qCP%d4 z;$;3*d8}+v2i2y}_$3>+59Mb~oAxX7O+iHAJ6Gc@5>hjF2 zd9`I^!8DC)a$qr0gB-A|;L1y}Sc1Cu=klREhc6{O3+zrIE)kn3W@S*S=F*HbM9~^? zQWM(=qVV3QV@jyZM$-(=#m!9+PR|vLBS_4Ix`}93M`yew+iwx@(U6h8WAS!l@carEvj@Xo|ztW;a#KPz_GrRkr|d_ zA*{ziY~_$PXR_>`b0!|HF{YVIEn*B2(TRWsFLxnCwy}LQB9lFzlo+DQiL>uiM9{O0 zzvcOZC=Ptf88buM%b8ZvaL6SG?*?#Nfchl*JGd^k-H!VqI{Q~13PqR%y|2tg_8o>{ z2d9MlxA*vPH{gH&;uZSs6}r_LhutGq-3sG)ga;vn366UVyB)l90QZ1Ph6vY#yA>|3 zUt#z720GeErD>5zoANm|0o0PkD$-Kr@ir)*x`*V?I*N61J@uW7XF^~1X$Jy#0^l0` z#g`!NQ_;i-BA3{T0#+Aey9x}$p{Bu-IOREh9&yN@+J9#c-*s_#lAn-n8H-n`_d&}_=0(Xv{Jq(*lOy2y_vW&dGMLN`F4VmN zjq7$%WS7sra$4X~a15mcu%2UU(FC2A^!!X|g61=;wo%GxuE=M`#FsDoNn-jUCIOl> z1w}DkL=*ZZ4z4s0Er;{V$4}^O+5R221j$h(VE8ac@IzQR7#(@ z;SFYOhMBCS~~%bL$y zQk-USg*p zO(!`!g6!c$YqTtWd7)MExw1P7a2qw6U_}rS0hjjG+Bz0ONHs4oP;x4cwT3@Xa@$5I z;p&Q^GZRHJi=DN`FNGy*=PoXya-nORh$LIrS<=D3oo-dvn4DTjms##2?wJU(KvXxMhvGcGJr2CWwj6K_W+P5qE~++*M)RMq9YN=vV+o3;#yWmoh9{ zWSeH6vm77+q*jQFiL*lUCZ1c1DtoS8o~wIOW;Jd}8y^fs^mN<`9SUo;$Aa|%bEwpl zLqc}2HZcMt{?lSA2%=c3V_YcJd20SU8_P?R7=@g~ezK*?H9V6n>`-xO#RENqve;oO zL(Fm>7~_`&rIO0mgHk+~r6P7_m483g=zA%)}^YEI_JL zca6U>32xLdP0KV;YYT3^Fp~h@QE@Fn4HGTJe5?Rhd@*F4&t(8awXjAlHXsR_IqIK9 zeljIbmZy=~pKPr9Xwd=0)C^Eg-6f{P#z5!MM6cU;eHG@kxS(vyGIKp3hsM1#Mi#0oS(Ts@pogKv`#DYQ^Sj0y2CsaAo3jxLBqB5N zKrFEvLsB=X97MUs!6mLELX|~Y95Kj+6)vqyQeetaWK2R|sLxex=g#7&ly#xF0n261 zoP7kznMn|?asQ%8H|ub0XXbo*rWgw zdzmGEM`TRKP4i{-q?I@(QVVQ~Z{{yH{*@BlcMB(Gb8%*d6qau)1TcsGT51;RdZ_XJ z%BTVnYM}C^0o;lLs-;2OIskLDun|aYc|gVin1ZeBWI7#C)U)DJAZX88OXud2r~NZr zTqZEq2w{MPJAk(6y2$yPhCP@QyVDcq5U{!2K*q5Mz~1@D?HUFQPXqdk4L<(G*AU-9 z0d%W1CQ)2mJ;VOtJt$1r@Ai;kz~laa=a<)DCLE6gJaG~wSLpjo%ya>Qi%h_U6ShW% z^`unB@^$0+$dzG!z5uJpLL8PwQ9vpOQiA8WmPfm4i0^`KaC80FfV+v&U8>yJELg>T zK%!vgd^uyP2#&i0`t=5Iu8PuWv7DzVazI^Q+0=6_E(;;RJBKh$#dSkG$`2y?+_jh|7A13q|arI(_e#`Ki6#^o6 zOR>T*jaaQ1t5t`0cLzKiW^^uzd@YX zPSOR-cef;qz)dW&KKE&TPiIfZj2Tie3>ITp?i6X)Q>}4ok;Fkp652SHLFO}>l7Kz1 z@&TJ}h6r%&XGn;9lLWymA8`2g0pI`aukp#M!}Dtg55mfC;F-X$JZ8NDk%uB6q;*eJ z(RW?^?72%~CMRQrLg%??7)^#z)g%l0;k>rlm2?kc;;@ zn03xN?dWHQg4s!1Q#J|3WX)nsg*}+>ti?f?+LI;e5}J7AMR+twu5@*c#u-!&a%Pyf z?-|PHWfK|M%~$*$2G4_qL)SZlC^EX6rhS3Zx&D6wQP^E-NUqyz00000NkvXXu0mjf DED`To literal 0 HcmV?d00001 diff --git a/flux/static/generated-mlx.png b/flux/static/generated-mlx.png new file mode 100644 index 0000000000000000000000000000000000000000..5c274ef400c315c2cbdcff4b804f8c5ed1784e1b GIT binary patch literal 156875 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

    ;CHW#%90WpFN*;mo$@79F&|_ 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 utRx@{ri@5nC>(8kksjCKK*god}3Qo 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 Date: Sat, 12 Oct 2024 15:14:01 +0200 Subject: [PATCH 048/188] Small typo fixed in flux README.md (#1035) --- flux/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flux/README.md b/flux/README.md index 33bebfd6..62eb9b62 100644 --- a/flux/README.md +++ b/flux/README.md @@ -160,8 +160,8 @@ The adapters are saved in `mlx_output` and can be used directly by the `txt2image.py` script. For instance, ```shell -python txt2img.py --model dev --save-raw --image-size 512x512 --n-images 1 \ - --adapter mlx_output/mlx_output/0001200_adapters.safetensors \ +python txt2image.py --model dev --save-raw --image-size 512x512 --n-images 1 \ + --adapter mlx_output/0001200_adapters.safetensors \ --fuse-adapter \ --no-t5-padding \ 'A photo of an sks dog lying on the sand at a beach in Greece' From 7612c646f3957a5d588dddb115c49e88a4c78058 Mon Sep 17 00:00:00 2001 From: Shunta Saito Date: Sun, 13 Oct 2024 07:26:50 +0900 Subject: [PATCH 049/188] Fix PLaMo model to support Grouped Query Attention (#1037) --- llms/mlx_lm/models/plamo.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/llms/mlx_lm/models/plamo.py b/llms/mlx_lm/models/plamo.py index 090922ae..b0fd1a6c 100644 --- a/llms/mlx_lm/models/plamo.py +++ b/llms/mlx_lm/models/plamo.py @@ -89,6 +89,9 @@ class Attention(nn.Module): queries = self.rotary_emb(queries) keys = self.rotary_emb(keys) + keys = mx.tile(keys, [1, self.config.n_shared_head, 1, 1]) + values = mx.tile(values, [1, self.config.n_shared_head, 1, 1]) + output = mx.fast.scaled_dot_product_attention( queries, keys, From 1e0cda68c642d66a2829a3bdc93457c963d6d44b Mon Sep 17 00:00:00 2001 From: Seitaro Sugawara <11994333+SeitaroSugawara@users.noreply.github.com> Date: Mon, 14 Oct 2024 22:21:25 +0900 Subject: [PATCH 050/188] Update README.md (#1045) * Update README.md A small typo was fixed in the musicgen README.md. * Update musicgen/README.md --------- Co-authored-by: Awni Hannun --- musicgen/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/musicgen/README.md b/musicgen/README.md index baef4fad..c0e340c9 100644 --- a/musicgen/README.md +++ b/musicgen/README.md @@ -16,8 +16,7 @@ pip install -r requirements.txt An example using the model: ```python -import mlx.core as mx -from music_gen import MusicGen +from musicgen import MusicGen from utils import save_audio model = MusicGen.from_pretrained("facebook/musicgen-medium") From c799133998a943affdc395c5cb159ead2576a225 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Mon, 14 Oct 2024 10:25:24 -0700 Subject: [PATCH 051/188] Make llm async eval less brittle (#1040) * Make llm async eval less brittle * nit --- llms/mlx_lm/utils.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index 1e07546e..4f872982 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -246,10 +246,10 @@ def generate_step( y, logprobs = _step(y) - mx.async_eval(y) + mx.async_eval(y, logprobs) while True: next_y, next_logprobs = _step(y) - mx.async_eval(next_y) + mx.async_eval(next_y, next_logprobs) yield y.item(), logprobs y, logprobs = next_y, next_logprobs @@ -348,7 +348,9 @@ def generate( if formatter: # We have to finalize so that the prob corresponds to the last segment detokenizer.finalize() - formatter(detokenizer.last_segment, mx.exp(logprobs[token]).item()) + with mx.stream(mx.cpu): + prob = mx.exp(logprobs[token]).item() + formatter(detokenizer.last_segment, prob) else: print(detokenizer.last_segment, end="", flush=True) From 6c368f212473dc11e46d19c5dc65410ee70b2594 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Mon, 14 Oct 2024 10:40:36 -0700 Subject: [PATCH 052/188] bump mac tests to use py39 (#1047) --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 02fa1de8..cecd2d57 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -26,8 +26,8 @@ jobs: - run: name: Install dependencies command: | - brew install python@3.8 - python3.8 -m venv env + brew install python@3.9 + python3.9 -m venv env source env/bin/activate pip install --upgrade pip pip install unittest-xml-reporting From 8dca1a2f6091f443ffb54b5f39a390f6971e677c Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Mon, 14 Oct 2024 10:48:46 -0700 Subject: [PATCH 053/188] Tokenizer updates + tests (#1024) * tokenizer updates + tests * nit * add can_trim_prompt_cache * nits --- llms/mlx_lm/models/cache.py | 9 +++- llms/mlx_lm/models/deepseek_v2.py | 6 +-- llms/mlx_lm/tokenizer_utils.py | 40 ++++++++-------- llms/tests/test_tokenizers.py | 76 +++++++++++++++++++++++++++++++ 4 files changed, 108 insertions(+), 23 deletions(-) create mode 100644 llms/tests/test_tokenizers.py diff --git a/llms/mlx_lm/models/cache.py b/llms/mlx_lm/models/cache.py index b06422e5..a6a56e0a 100644 --- a/llms/mlx_lm/models/cache.py +++ b/llms/mlx_lm/models/cache.py @@ -77,6 +77,13 @@ def load_prompt_cache(file_name, return_metadata=False): return cache +def can_trim_prompt_cache(cache: List[Any]) -> bool: + """ + Check if model's cache can be trimmed. + """ + return all(c.is_trimmable() for c in cache) + + def trim_prompt_cache(cache: List[Any], num_tokens: int) -> List[Any]: """ Trim the model's cache by the given number of tokens. @@ -91,7 +98,7 @@ def trim_prompt_cache(cache: List[Any], num_tokens: int) -> List[Any]: Returns: (int): The number of tokens that were trimmed. """ - if not all(c.is_trimmable() for c in cache) or len(cache) == 0: + if not can_trim_prompt_cache(cache) or len(cache) == 0: return 0 return [c.trim(num_tokens) for c in cache][0] diff --git a/llms/mlx_lm/models/deepseek_v2.py b/llms/mlx_lm/models/deepseek_v2.py index 17d061a8..bb3e5184 100644 --- a/llms/mlx_lm/models/deepseek_v2.py +++ b/llms/mlx_lm/models/deepseek_v2.py @@ -220,17 +220,17 @@ class DeepseekV2Attention(nn.Module): k_nope, values = mx.split(kv, [self.qk_nope_head_dim], axis=-1) - k_pe = mx.concatenate([k_pe] * self.num_heads, axis=1) - if cache is not None: q_pe = self.rope(q_pe, cache.offset) k_pe = self.rope(k_pe, cache.offset) + k_pe = mx.repeat(k_pe, self.num_heads, axis=1) keys, values = cache.update_and_fetch( mx.concatenate([k_nope, k_pe], axis=-1), values ) else: q_pe = self.rope(q_pe) k_pe = self.rope(k_pe) + k_pe = mx.repeat(k_pe, self.num_heads, axis=1) keys = mx.concatenate([k_nope, k_pe], axis=-1) queries = mx.concatenate([q_nope, q_pe], axis=-1) @@ -291,7 +291,7 @@ class MoEGate(nn.Module): scores = scores.reshape(bsz, seq_len, -1) k = self.top_k - inds = mx.stop_gradient(mx.argpartition(-scores, kth=k - 1, axis=-1)[..., :k]) + inds = mx.argpartition(-scores, kth=k - 1, axis=-1)[..., :k] scores = mx.take_along_axis(scores, inds, axis=-1) scores = scores * self.routed_scaling_factor diff --git a/llms/mlx_lm/tokenizer_utils.py b/llms/mlx_lm/tokenizer_utils.py index 04bbbcc5..d8694d86 100644 --- a/llms/mlx_lm/tokenizer_utils.py +++ b/llms/mlx_lm/tokenizer_utils.py @@ -97,6 +97,11 @@ class NaiveStreamingDetokenizer(StreamingDetokenizer): def text(self): if self._current_tokens: self._current_text = self._tokenizer.decode(self._current_tokens) + if ( + self._tokenizer.clean_up_tokenization_spaces + and self._current_text[-1] == " " + ): + self._current_text = self._current_text[:-1] if self._current_text and self._current_text[-1] == "\n": self._tokens.extend(self._current_tokens) self._text += self._current_text @@ -164,9 +169,11 @@ class BPEStreamingDetokenizer(StreamingDetokenizer): """ _byte_decoder = None + _space_matches = (".", "?", "!", ",", "'", "n't", "'m", "'s", "'ve", "'re") - def __init__(self, tokenizer, trim_space=False): - self.trim_space = trim_space + def __init__(self, tokenizer): + + self.clean_spaces = tokenizer.clean_up_tokenization_spaces # Extract the tokens in a list from id to text self.tokenmap = [None] * len(tokenizer.vocab) @@ -185,17 +192,22 @@ class BPEStreamingDetokenizer(StreamingDetokenizer): self.text = "" self.tokens = [] + def _maybe_trim_space(self, current_text): + if current_text[0] != " ": + return current_text + elif not self.text: + return current_text[1:] + elif self.clean_spaces and current_text[1:].startswith(self._space_matches): + return current_text[1:] + return current_text + def add_token(self, token): v = self.tokenmap[token] - # if the token starts with space if self._byte_decoder[v[0]] == 32: current_text = bytearray( self._byte_decoder[c] for c in self._unflushed ).decode("utf-8") - if self.text or not self.trim_space: - self.text += current_text - else: - self.text += _remove_space(current_text) + self.text += self._maybe_trim_space(current_text) self._unflushed = v else: self._unflushed += v @@ -204,10 +216,7 @@ class BPEStreamingDetokenizer(StreamingDetokenizer): current_text = bytearray(self._byte_decoder[c] for c in self._unflushed).decode( "utf-8" ) - if self.text or not self.trim_space: - self.text += current_text - else: - self.text += _remove_space(current_text) + self.text += self._maybe_trim_space(current_text) self._unflushed = "" @classmethod @@ -303,14 +312,7 @@ def _is_spm_decoder_no_space(decoder): def _is_bpe_decoder(decoder): - _target_description = { - "type": "ByteLevel", - "add_prefix_space": False, - "trim_offsets": False, - "use_regex": False, - } - - return _match(_target_description, decoder) + return isinstance(decoder, dict) and decoder.get("type", None) == "ByteLevel" def load_tokenizer(model_path, tokenizer_config_extra={}): diff --git a/llms/tests/test_tokenizers.py b/llms/tests/test_tokenizers.py new file mode 100644 index 00000000..7b4828b1 --- /dev/null +++ b/llms/tests/test_tokenizers.py @@ -0,0 +1,76 @@ +# Copyright © 2024 Apple Inc. + +import unittest +from pathlib import Path + +from huggingface_hub import snapshot_download +from mlx_lm.tokenizer_utils import ( + BPEStreamingDetokenizer, + NaiveStreamingDetokenizer, + SPMStreamingDetokenizer, + load_tokenizer, +) + + +class TestTokenizers(unittest.TestCase): + + def download_tokenizer(self, repo): + path = Path( + snapshot_download( + repo_id=repo, + allow_patterns=[ + "tokenizer.json", + "tokenizer_config.json", + "special_tokens_map.json", + "tokenizer.model", + ], + ) + ) + return load_tokenizer(path) + + def check_tokenizer(self, tokenizer): + def check(tokens): + expected_text = tokenizer.decode(tokens) + detokenizer = tokenizer.detokenizer + detokenizer.reset() + text = "" + for t in tokens: + detokenizer.add_token(t) + seg = detokenizer.last_segment + text += seg + detokenizer.finalize() + text += detokenizer.last_segment + self.assertEqual(text, expected_text) + + tokens = tokenizer.encode("a ,b") + check(tokens) + + tokens = tokenizer.encode('{"why_its_funny" :"a_joke_explainer" ,"rating":3.5}') + check(tokens) + + tokens = tokenizer.encode("3 3") + check(tokens) + + def test_tokenizers(self): + tokenizer_repos = [ + ("mlx-community/Qwen1.5-0.5B-Chat-4bit", BPEStreamingDetokenizer), + ("mlx-community/Mistral-7B-v0.2-4bit", SPMStreamingDetokenizer), + ("mlx-community/Phi-3.5-mini-instruct-4bit", SPMStreamingDetokenizer), + ("mlx-community/Mistral-7B-Instruct-v0.3", SPMStreamingDetokenizer), + ("mlx-community/Llama-3.2-1B-Instruct-4bit", BPEStreamingDetokenizer), + ] + for tokenizer_repo, expected_detokenizer in tokenizer_repos: + with self.subTest(tokenizer=tokenizer_repo): + tokenizer = self.download_tokenizer(tokenizer_repo) + tokenizer.decode([0, 1, 2]) + self.assertTrue(isinstance(tokenizer.detokenizer, expected_detokenizer)) + self.check_tokenizer(tokenizer) + + # Try one with a naive detokenizer + tokenizer = self.download_tokenizer("mlx-community/Llama-3.2-1B-Instruct-4bit") + tokenizer._detokenizer = NaiveStreamingDetokenizer(tokenizer) + self.check_tokenizer(tokenizer) + + +if __name__ == "__main__": + unittest.main() From 605c4854f1547e8eb0ef3f9c9d81c8aef3196c15 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Mon, 14 Oct 2024 10:57:22 -0700 Subject: [PATCH 054/188] Prompt caching in `mlx_lm.server` (#1026) * caching in server * nits * fix tests * don't throw if no metal * comments --- llms/mlx_lm/SERVER.md | 38 ++++++++-- llms/mlx_lm/server.py | 121 ++++++++++++++++++++++++-------- llms/tests/test_prompt_cache.py | 23 ++++++ llms/tests/test_server.py | 1 + 4 files changed, 151 insertions(+), 32 deletions(-) diff --git a/llms/mlx_lm/SERVER.md b/llms/mlx_lm/SERVER.md index 55be1c9c..2976a09f 100644 --- a/llms/mlx_lm/SERVER.md +++ b/llms/mlx_lm/SERVER.md @@ -50,7 +50,7 @@ curl localhost:8080/v1/chat/completions \ - `role_mapping`: (Optional) A dictionary to customize the role prefixes in the generated prompt. If not provided, the default mappings are used. -- `stop`: (Optional) An array of strings or a single string. Thesse are +- `stop`: (Optional) An array of strings or a single string. These are sequences of tokens on which the generation should stop. - `max_tokens`: (Optional) An integer specifying the maximum number of tokens @@ -84,7 +84,37 @@ curl localhost:8080/v1/chat/completions \ started in. - `adapters`: (Optional) A string path to low-rank adapters. The path must be - rlative to the directory the server was started in. + relative to the directory the server was started in. + +### Response Fields + +- `id`: A unique identifier for the chat. + +- `system_fingerprint`: A unique identifier for the system. + +- `object`: Any of "chat.completions", "chat.completions.chunk" (for + streaming), or "text.completion". + +- `model`: The model repo or path (e.g. `"mlx-community/Llama-3.2-3B-Instruct-4bit"`). + +- `created`: A time-stamp for when the request was processed. + +- `choices`: A list of outputs. Each output is a dictionary containing the fields: + - `index`: The index in the list. + - `logprobs`: A dictionary containing the fields: + - `token_logprobs`: A list of the log probabilities for the generated + tokens. + - `tokens`: A list of the generated token ids. + - `top_logprobs`: A list of lists. Each list contains the `logprobs` + top tokens (if requested) with their corresponding probabilities. + - `finish_reason`: The reason the completion ended. This can be either of + `"stop"` or `"length"`. + - `message`: The text response from the model. + +- `usage`: A dictionary containing the fields: + - `prompt_tokens`: The number of prompt tokens processed. + - `completion_tokens`: The number of tokens generated. + - `total_tokens`: The total number of tokens, i.e. the sum of the above two fields. ### List Models @@ -97,5 +127,5 @@ curl localhost:8080/v1/models -H "Content-Type: application/json" This will return a list of locally available models where each model in the list contains the following fields: -- `"id"`: The Hugging Face repo id. -- `"created"`: A timestamp representing the model creation time. +- `id`: The Hugging Face repo id. +- `created`: A time-stamp representing the model creation time. diff --git a/llms/mlx_lm/server.py b/llms/mlx_lm/server.py index 42962b54..ec659969 100644 --- a/llms/mlx_lm/server.py +++ b/llms/mlx_lm/server.py @@ -3,19 +3,38 @@ import argparse import json import logging +import platform import time import uuid import warnings +from dataclasses import dataclass, field from http.server import BaseHTTPRequestHandler, HTTPServer from pathlib import Path -from typing import Dict, List, Literal, NamedTuple, Optional, Sequence, Union +from typing import ( + Any, + Dict, + List, + Literal, + NamedTuple, + Optional, + Sequence, + Tuple, + Union, +) import mlx.core as mx from huggingface_hub import scan_cache_dir +from ._version import __version__ +from .models.cache import make_prompt_cache from .utils import generate_step, load +def get_system_fingerprint(): + gpu_arch = mx.metal.device_info()["architecture"] if mx.metal.is_available() else "" + return f"{__version__}-{mx.__version__}-{platform.platform()}-{gpu_arch}" + + class StopCondition(NamedTuple): stop_met: bool trim_length: int @@ -94,6 +113,13 @@ def convert_chat(messages: List[dict], role_mapping: Optional[dict] = None): return prompt.rstrip() +@dataclass +class PromptCache: + cache: List[Any] = field(default_factory=list) + model_key: Tuple[str, Optional[str]] = ("", None) + tokens: List[int] = field(default_factory=list) + + class ModelProvider: def __init__(self, cli_args: argparse.Namespace): """Load models on demand and persist them across the whole process.""" @@ -156,12 +182,21 @@ class ModelProvider: class APIHandler(BaseHTTPRequestHandler): - def __init__(self, model_provider: ModelProvider, *args, **kwargs): + def __init__( + self, + model_provider: ModelProvider, + *args, + prompt_cache: Optional[PromptCache] = None, + system_fingerprint: Optional[str] = None, + **kwargs, + ): """ Create static request specific metadata """ self.created = int(time.time()) self.model_provider = model_provider + self.prompt_cache = prompt_cache or PromptCache() + self.system_fingerprint = system_fingerprint or get_system_fingerprint() super().__init__(*args, **kwargs) def _set_cors_headers(self): @@ -215,7 +250,9 @@ class APIHandler(BaseHTTPRequestHandler): self.stream_options = self.body.get("stream_options", None) self.requested_model = self.body.get("model", "default_model") self.adapter = self.body.get("adapters", None) - self.max_tokens = self.body.get("max_tokens", 100) + self.max_tokens = self.body.get("max_completion_tokens", None) + if self.max_tokens is None: + self.max_tokens = self.body.get("max_tokens", 512) self.temperature = self.body.get("temperature", 1.0) self.top_p = self.body.get("top_p", 1.0) self.repetition_penalty = self.body.get("repetition_penalty", 1.0) @@ -343,7 +380,7 @@ class APIHandler(BaseHTTPRequestHandler): # Static response response = { "id": self.request_id, - "system_fingerprint": f"fp_{uuid.uuid4()}", + "system_fingerprint": self.system_fingerprint, "object": self.object_type, "model": self.requested_model, "created": self.created, @@ -388,16 +425,30 @@ class APIHandler(BaseHTTPRequestHandler): return response + def get_prompt_cache(self, prompt): + cache_len = len(self.prompt_cache.tokens) + if ( + self.prompt_cache.model_key != self.model_provider.model_key + or cache_len >= len(prompt) + or self.prompt_cache.tokens != prompt[:cache_len] + ): + self.prompt_cache.model_key = self.model_provider.model_key + self.prompt_cache.cache = make_prompt_cache(self.model_provider.model) + else: + prompt = prompt[cache_len:] + self.prompt_cache.tokens.extend(prompt) + return prompt + def handle_completion( self, - prompt: mx.array, + prompt: List[int], stop_id_sequences: List[List[int]], ): """ Generate a response to a prompt and send it to the client in a single batch. Args: - prompt (mx.array): The prompt, in token form inside of a mlx array + prompt (List[int]): The tokenized prompt. stop_id_sequences (List[List[int]]): A list of stop words passed to the stopping_criteria function """ @@ -409,17 +460,21 @@ class APIHandler(BaseHTTPRequestHandler): logging.debug(f"Starting completion:") token_logprobs = [] top_tokens = [] - for (token, logprobs), _ in zip( + + prompt = self.get_prompt_cache(prompt) + + for _, (token, logprobs) in zip( + range(self.max_tokens), generate_step( - prompt=prompt, + prompt=mx.array(prompt), model=self.model, temp=self.temperature, top_p=self.top_p, repetition_penalty=self.repetition_penalty, repetition_context_size=self.repetition_context_size, logit_bias=self.logit_bias, + prompt_cache=self.prompt_cache.cache, ), - range(self.max_tokens), ): detokenizer.add_token(token) logging.debug(detokenizer.text) @@ -430,7 +485,7 @@ class APIHandler(BaseHTTPRequestHandler): top_indices = sorted_indices[: self.logprobs] top_logprobs = logprobs[top_indices] top_token_info = zip(top_indices.tolist(), top_logprobs.tolist()) - top_tokens.append(dict(top_token_info)) + top_tokens.append(tuple(top_token_info)) token_logprobs.append(logprobs[token].item()) @@ -445,6 +500,7 @@ class APIHandler(BaseHTTPRequestHandler): ) break + self.prompt_cache.tokens.extend(tokens) detokenizer.finalize() text = ( detokenizer.text @@ -474,7 +530,7 @@ class APIHandler(BaseHTTPRequestHandler): def handle_stream( self, - prompt: mx.array, + prompt: List[int], stop_id_sequences: List[List[int]], ): """ @@ -482,7 +538,7 @@ class APIHandler(BaseHTTPRequestHandler): Sent Events (SSE) stream. Args: - prompt (mx.array): The prompt, in token form inside of a mlx array + prompt (mx.array): The tokenized prompt stop_id_sequences (List[List[int]]): A list of stop words passed to the stopping_criteria function """ @@ -496,16 +552,19 @@ class APIHandler(BaseHTTPRequestHandler): stop_sequence_suffix = None logging.debug(f"Starting stream:") - for (token, _), _ in zip( + prompt = self.get_prompt_cache(prompt) + + for _, (token, _) in zip( + range(self.max_tokens), generate_step( - prompt=prompt, + prompt=mx.array(prompt), model=self.model, temp=self.temperature, top_p=self.top_p, repetition_penalty=self.repetition_penalty, repetition_context_size=self.repetition_context_size, + prompt_cache=self.prompt_cache.cache, ), - range(self.max_tokens), ): detokenizer.add_token(token) logging.debug(detokenizer.text) @@ -531,9 +590,12 @@ class APIHandler(BaseHTTPRequestHandler): continue new_text = detokenizer.last_segment - response = self.generate_response(new_text, None) - self.wfile.write(f"data: {json.dumps(response)}\n\n".encode()) - self.wfile.flush() + if new_text: + response = self.generate_response(new_text, None) + self.wfile.write(f"data: {json.dumps(response)}\n\n".encode()) + self.wfile.flush() + + self.prompt_cache.tokens.extend(tokens) # check is there any remaining text to send detokenizer.finalize() @@ -559,7 +621,7 @@ class APIHandler(BaseHTTPRequestHandler): ): response = { "id": self.request_id, - "system_fingerprint": f"fp_{uuid.uuid4()}", + "system_fingerprint": self.system_fingerprint, "object": "chat.completion", "model": self.requested_model, "created": self.created, @@ -572,7 +634,7 @@ class APIHandler(BaseHTTPRequestHandler): } return response - def handle_chat_completions(self) -> mx.array: + def handle_chat_completions(self) -> List[int]: """ Handle a chat completion request. @@ -587,7 +649,6 @@ class APIHandler(BaseHTTPRequestHandler): self.object_type = ( "chat.completions.chunk" if self.stream else "chat.completions" ) - if ( hasattr(self.tokenizer, "apply_chat_template") and self.tokenizer.chat_template @@ -602,9 +663,9 @@ class APIHandler(BaseHTTPRequestHandler): prompt = convert_chat(body["messages"], body.get("role_mapping")) prompt = self.tokenizer.encode(prompt) - return mx.array(prompt) + return prompt - def handle_text_completions(self) -> mx.array: + def handle_text_completions(self) -> List[int]: """ Handle a text completion request. @@ -614,11 +675,8 @@ class APIHandler(BaseHTTPRequestHandler): # Determine response type self.request_id = f"cmpl-{uuid.uuid4()}" self.object_type = "text_completion" - assert "prompt" in self.body, "Request did not contain a prompt" - prompt_text = self.body["prompt"] - prompt = self.tokenizer.encode(prompt_text) - return mx.array(prompt) + return self.tokenizer.encode(self.body["prompt"]) def do_GET(self): """ @@ -669,9 +727,16 @@ def run( handler_class=APIHandler, ): server_address = (host, port) + prompt_cache = PromptCache() httpd = server_class( server_address, - lambda *args, **kwargs: handler_class(model_provider, *args, **kwargs), + lambda *args, **kwargs: handler_class( + model_provider, + prompt_cache=prompt_cache, + system_fingerprint=get_system_fingerprint(), + *args, + **kwargs, + ), ) warnings.warn( "mlx_lm.server is not recommended for production as " diff --git a/llms/tests/test_prompt_cache.py b/llms/tests/test_prompt_cache.py index 3c1ef49b..64cd9486 100644 --- a/llms/tests/test_prompt_cache.py +++ b/llms/tests/test_prompt_cache.py @@ -1,5 +1,6 @@ # Copyright © 2024 Apple Inc. +import copy import os import tempfile import unittest @@ -215,6 +216,28 @@ class TestPromptCache(unittest.TestCase): all(mx.allclose(l, l2) for l, l2 in zip(all_logits, second_all_logits)) ) + def test_cache_copying(self): + cache = [KVCache()] + + x = mx.random.uniform(shape=(1, 8, 10, 4)) + cache[0].update_and_fetch(x, x) + + y = mx.random.uniform(shape=(1, 8, 1, 4)) + cache[0].update_and_fetch(y, y) + + old_cache = copy.deepcopy(cache) + + trim_prompt_cache(cache, 1) + + self.assertTrue(old_cache[0].offset, 11) + self.assertTrue(cache[0].offset, 10) + + z = mx.random.uniform(shape=(1, 8, 1, 4)) + cache[0].update_and_fetch(z, z) + + self.assertTrue(mx.allclose(old_cache[0].keys[..., 10:11, :], y)) + self.assertTrue(mx.allclose(cache[0].keys[..., 10:11, :], z)) + if __name__ == "__main__": unittest.main() diff --git a/llms/tests/test_server.py b/llms/tests/test_server.py index cbcccfbe..ad17554d 100644 --- a/llms/tests/test_server.py +++ b/llms/tests/test_server.py @@ -14,6 +14,7 @@ class DummyModelProvider: def __init__(self): HF_MODEL_PATH = "mlx-community/Qwen1.5-0.5B-Chat-4bit" self.model, self.tokenizer = load(HF_MODEL_PATH) + self.model_key = (HF_MODEL_PATH, None) def load(self, model, adapter=None): assert model in ["default_model", "chat_model"] From bbd20030476737c1b9e027e3358392c40821987f Mon Sep 17 00:00:00 2001 From: madroid Date: Tue, 15 Oct 2024 02:21:41 +0800 Subject: [PATCH 055/188] FLUX: update README.md (#1036) --- README.md | 4 +++- flux/README.md | 26 +++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bd180975..88888ad0 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,10 @@ Some more useful examples are listed below. ### Image Models +- Generating images + - [FLUX](flux) + - [Stable Diffusion or SDXL](stable_diffusion) - Image classification using [ResNets on CIFAR-10](cifar). -- Generating images with [Stable Diffusion or SDXL](stable_diffusion). - Convolutional variational autoencoder [(CVAE) on MNIST](cvae). ### Audio Models diff --git a/flux/README.md b/flux/README.md index 62eb9b62..0496c71b 100644 --- a/flux/README.md +++ b/flux/README.md @@ -28,6 +28,26 @@ You can install all of the above with the `requirements.txt` as follows: pip install -r requirements.txt + +Usage +--------- + +You can use the following command to generate an image, using `--output` to specify the storage location of the image, defaulting to `out.png`. + +```shell +python txt2image.py --model schnell \ + --n-images 1 \ + --image-size 256x512 \ + --verbose \ + 'A photo of an astronaut riding a horse on Mars.' +``` + +For more parameters, please use the `--help` command to view. + +```shell +python txt2image.py --help +``` + Inference --------- @@ -78,7 +98,11 @@ except for some additional logic to quantize and/or load trained adapters. One can use the script as follows: ```shell -python txt2image.py --n-images 4 --n-rows 2 --image-size 256x512 'A photo of an astronaut riding a horse on Mars.' +python txt2image.py \ + --n-images 4 \ + --n-rows 2 \ + --image-size 256x512 \ + 'A photo of an astronaut riding a horse on Mars.' ``` ### Experimental Options From 3d62b058a4235019c945957da575ad3e36036cee Mon Sep 17 00:00:00 2001 From: "Zak B. Elep" Date: Wed, 16 Oct 2024 00:13:01 +0800 Subject: [PATCH 056/188] fix: typo on flux model preloading (#1050) --- flux/txt2image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flux/txt2image.py b/flux/txt2image.py index bf2f7294..5ebec81a 100644 --- a/flux/txt2image.py +++ b/flux/txt2image.py @@ -77,7 +77,7 @@ if __name__ == "__main__": nn.quantize(flux.clip, class_predicate=quantization_predicate) if args.preload_models: - sd.ensure_models_are_loaded() + flux.ensure_models_are_loaded() # Make the generator latent_size = to_latent_size(args.image_size) From f491d473a332fc3ff9daf74be2bd154c0f9231b5 Mon Sep 17 00:00:00 2001 From: madroid Date: Wed, 16 Oct 2024 01:37:45 +0800 Subject: [PATCH 057/188] FLUX: Optimize dataset loading logic (#1038) --- flux/README.md | 47 ++++---- flux/dreambooth.py | 121 +++------------------ flux/flux/__init__.py | 239 +--------------------------------------- flux/flux/datasets.py | 75 +++++++++++++ flux/flux/flux.py | 246 ++++++++++++++++++++++++++++++++++++++++++ flux/flux/trainer.py | 98 +++++++++++++++++ 6 files changed, 461 insertions(+), 365 deletions(-) create mode 100644 flux/flux/datasets.py create mode 100644 flux/flux/flux.py create mode 100644 flux/flux/trainer.py diff --git a/flux/README.md b/flux/README.md index 0496c71b..1a17e386 100644 --- a/flux/README.md +++ b/flux/README.md @@ -21,8 +21,9 @@ The dependencies are minimal, namely: - `huggingface-hub` to download the checkpoints. - `regex` for the tokenization -- `tqdm`, `PIL`, and `numpy` for the `txt2image.py` script +- `tqdm`, `PIL`, and `numpy` for the scripts - `sentencepiece` for the T5 tokenizer +- `datasets` for using an HF dataset directly You can install all of the above with the `requirements.txt` as follows: @@ -118,17 +119,12 @@ Finetuning The `dreambooth.py` script supports LoRA finetuning of FLUX-dev (and schnell but ymmv) on a provided image dataset. The dataset folder must have an -`index.json` file with the following format: +`train.jsonl` file with the following format: -```json -{ - "data": [ - {"image": "path-to-image-relative-to-dataset", "text": "Prompt to use with this image"}, - {"image": "path-to-image-relative-to-dataset", "text": "Prompt to use with this image"}, - {"image": "path-to-image-relative-to-dataset", "text": "Prompt to use with this image"}, - ... - ] -} +```jsonl +{"image": "path-to-image-relative-to-dataset", "prompt": "Prompt to use with this image"} +{"image": "path-to-image-relative-to-dataset", "prompt": "Prompt to use with this image"} +... ``` The training script by default trains for 600 iterations with a batch size of @@ -150,19 +146,15 @@ The training images are the following 5 images [^2]: ![dog6](static/dog6.png) -We start by making the following `index.json` file and placing it in the same +We start by making the following `train.jsonl` file and placing it in the same folder as the images. -```json -{ - "data": [ - {"image": "00.jpg", "text": "A photo of sks dog"}, - {"image": "01.jpg", "text": "A photo of sks dog"}, - {"image": "02.jpg", "text": "A photo of sks dog"}, - {"image": "03.jpg", "text": "A photo of sks dog"}, - {"image": "04.jpg", "text": "A photo of sks dog"} - ] -} +```jsonl +{"image": "00.jpg", "prompt": "A photo of sks dog"} +{"image": "01.jpg", "prompt": "A photo of sks dog"} +{"image": "02.jpg", "prompt": "A photo of sks dog"} +{"image": "03.jpg", "prompt": "A photo of sks dog"} +{"image": "04.jpg", "prompt": "A photo of sks dog"} ``` Subsequently we finetune FLUX using the following command: @@ -175,6 +167,17 @@ python dreambooth.py \ path/to/dreambooth/dataset/dog6 ``` + +Or you can directly use the pre-processed Hugging Face dataset [mlx-community/dreambooth-dog6](https://huggingface.co/datasets/mlx-community/dreambooth-dog6) for fine-tuning. + +```shell +python dreambooth.py \ + --progress-prompt 'A photo of an sks dog lying on the sand at a beach in Greece' \ + --progress-every 600 --iterations 1200 --learning-rate 0.0001 \ + --lora-rank 4 --grad-accumulate 8 \ + mlx-community/dreambooth-dog6 +``` + The training requires approximately 50GB of RAM and on an M2 Ultra it takes a bit more than 1 hour. diff --git a/flux/dreambooth.py b/flux/dreambooth.py index 4a4dbb08..48dcad47 100644 --- a/flux/dreambooth.py +++ b/flux/dreambooth.py @@ -1,7 +1,6 @@ # Copyright © 2024 Apple Inc. import argparse -import json import time from functools import partial from pathlib import Path @@ -13,105 +12,8 @@ import numpy as np from mlx.nn.utils import average_gradients from mlx.utils import tree_flatten, tree_map, tree_reduce from PIL import Image -from tqdm import tqdm -from flux import FluxPipeline - - -class FinetuningDataset: - def __init__(self, flux, args): - self.args = args - self.flux = flux - self.dataset_base = Path(args.dataset) - dataset_index = self.dataset_base / "index.json" - if not dataset_index.exists(): - raise ValueError(f"'{args.dataset}' is not a valid finetuning dataset") - with open(dataset_index, "r") as f: - self.index = json.load(f) - - self.latents = [] - self.t5_features = [] - self.clip_features = [] - - def _random_crop_resize(self, img): - resolution = self.args.resolution - width, height = img.size - - a, b, c, d = mx.random.uniform(shape=(4,), stream=mx.cpu).tolist() - - # Random crop the input image between 0.8 to 1.0 of its original dimensions - crop_size = ( - max((0.8 + 0.2 * a) * width, resolution[0]), - max((0.8 + 0.2 * a) * height, resolution[1]), - ) - pan = (width - crop_size[0], height - crop_size[1]) - img = img.crop( - ( - pan[0] * b, - pan[1] * c, - crop_size[0] + pan[0] * b, - crop_size[1] + pan[1] * c, - ) - ) - - # Fit the largest rectangle with the ratio of resolution in the image - # rectangle. - width, height = crop_size - ratio = resolution[0] / resolution[1] - r1 = (height * ratio, height) - r2 = (width, width / ratio) - r = r1 if r1[0] <= width else r2 - img = img.crop( - ( - (width - r[0]) / 2, - (height - r[1]) / 2, - (width + r[0]) / 2, - (height + r[1]) / 2, - ) - ) - - # Finally resize the image to resolution - img = img.resize(resolution, Image.LANCZOS) - - return mx.array(np.array(img)) - - def encode_images(self): - """Encode the images in the latent space to prepare for training.""" - self.flux.ae.eval() - for sample in tqdm(self.index["data"]): - input_img = Image.open(self.dataset_base / sample["image"]) - for i in range(self.args.num_augmentations): - img = self._random_crop_resize(input_img) - img = (img[:, :, :3].astype(self.flux.dtype) / 255) * 2 - 1 - x_0 = self.flux.ae.encode(img[None]) - x_0 = x_0.astype(self.flux.dtype) - mx.eval(x_0) - self.latents.append(x_0) - - def encode_prompts(self): - """Pre-encode the prompts so that we don't recompute them during - training (doesn't allow finetuning the text encoders).""" - for sample in tqdm(self.index["data"]): - t5_tok, clip_tok = self.flux.tokenize([sample["text"]]) - t5_feat = self.flux.t5(t5_tok) - clip_feat = self.flux.clip(clip_tok).pooled_output - mx.eval(t5_feat, clip_feat) - self.t5_features.append(t5_feat) - self.clip_features.append(clip_feat) - - def iterate(self, batch_size): - xs = mx.concatenate(self.latents) - t5 = mx.concatenate(self.t5_features) - clip = mx.concatenate(self.clip_features) - mx.eval(xs, t5, clip) - n_aug = self.args.num_augmentations - while True: - x_indices = mx.random.permutation(len(self.latents)) - c_indices = x_indices // n_aug - for i in range(0, len(self.latents), batch_size): - x_i = x_indices[i : i + batch_size] - c_i = c_indices[i : i + batch_size] - yield xs[x_i], t5[c_i], clip[c_i] +from flux import FluxPipeline, Trainer, load_dataset def generate_progress_images(iteration, flux, args): @@ -157,7 +59,8 @@ def save_adapters(iteration, flux, args): ) -if __name__ == "__main__": +def setup_arg_parser(): + """Set up and return the argument parser.""" parser = argparse.ArgumentParser( description="Finetune Flux to generate images with a specific subject" ) @@ -247,7 +150,11 @@ if __name__ == "__main__": ) parser.add_argument("dataset") + return parser + +if __name__ == "__main__": + parser = setup_arg_parser() args = parser.parse_args() # Load the model and set it up for LoRA training. We use the same random @@ -267,7 +174,7 @@ if __name__ == "__main__": trainable_params = tree_reduce( lambda acc, x: acc + x.size, flux.flow.trainable_parameters(), 0 ) - print(f"Training {trainable_params / 1024**2:.3f}M parameters", flush=True) + print(f"Training {trainable_params / 1024 ** 2:.3f}M parameters", flush=True) # Set up the optimizer and training steps. The steps are a bit verbose to # support gradient accumulation together with compilation. @@ -340,10 +247,10 @@ if __name__ == "__main__": x, t5_feat, clip_feat, guidance, prev_grads ) - print("Create the training dataset.", flush=True) - dataset = FinetuningDataset(flux, args) - dataset.encode_images() - dataset.encode_prompts() + dataset = load_dataset(args.dataset) + trainer = Trainer(flux, dataset, args) + trainer.encode_dataset() + guidance = mx.full((args.batch_size,), args.guidance, dtype=flux.dtype) # An initial generation to compare @@ -352,7 +259,7 @@ if __name__ == "__main__": grads = None losses = [] tic = time.time() - for i, batch in zip(range(args.iterations), dataset.iterate(args.batch_size)): + for i, batch in zip(range(args.iterations), trainer.iterate(args.batch_size)): loss, grads = step(*batch, guidance, grads, (i + 1) % args.grad_accumulate == 0) mx.eval(loss, grads, state) losses.append(loss.item()) @@ -361,7 +268,7 @@ if __name__ == "__main__": toc = time.time() peak_mem = mx.metal.get_peak_memory() / 1024**3 print( - f"Iter: {i+1} Loss: {sum(losses) / 10:.3f} " + f"Iter: {i + 1} Loss: {sum(losses) / 10:.3f} " f"It/s: {10 / (toc - tic):.3f} " f"Peak mem: {peak_mem:.3f} GB", flush=True, diff --git a/flux/flux/__init__.py b/flux/flux/__init__.py index 8d39d605..b1122d75 100644 --- a/flux/flux/__init__.py +++ b/flux/flux/__init__.py @@ -1,16 +1,10 @@ # Copyright © 2024 Apple Inc. -import math -import time -from typing import Tuple - -import mlx.core as mx -import mlx.nn as nn -from mlx.utils import tree_unflatten -from tqdm import tqdm - +from .datasets import Dataset, load_dataset +from .flux import FluxPipeline from .lora import LoRALinear from .sampler import FluxSampler +from .trainer import Trainer from .utils import ( load_ae, load_clip, @@ -19,230 +13,3 @@ from .utils import ( load_t5, load_t5_tokenizer, ) - - -class FluxPipeline: - def __init__(self, name: str, t5_padding: bool = True): - self.dtype = mx.bfloat16 - self.name = name - self.t5_padding = t5_padding - - self.ae = load_ae(name) - self.flow = load_flow_model(name) - self.clip = load_clip(name) - self.clip_tokenizer = load_clip_tokenizer(name) - self.t5 = load_t5(name) - self.t5_tokenizer = load_t5_tokenizer(name) - self.sampler = FluxSampler(name) - - def ensure_models_are_loaded(self): - mx.eval( - self.ae.parameters(), - self.flow.parameters(), - self.clip.parameters(), - self.t5.parameters(), - ) - - def reload_text_encoders(self): - self.t5 = load_t5(self.name) - self.clip = load_clip(self.name) - - def tokenize(self, text): - t5_tokens = self.t5_tokenizer.encode(text, pad=self.t5_padding) - clip_tokens = self.clip_tokenizer.encode(text) - return t5_tokens, clip_tokens - - def _prepare_latent_images(self, x): - b, h, w, c = x.shape - - # Pack the latent image to 2x2 patches - x = x.reshape(b, h // 2, 2, w // 2, 2, c) - x = x.transpose(0, 1, 3, 5, 2, 4).reshape(b, h * w // 4, c * 4) - - # Create positions ids used to positionally encode each patch. Due to - # the way RoPE works, this results in an interesting positional - # encoding where parts of the feature are holding different positional - # information. Namely, the first part holds information independent of - # the spatial position (hence 0s), the 2nd part holds vertical spatial - # information and the last one horizontal. - i = mx.zeros((h // 2, w // 2), dtype=mx.int32) - j, k = mx.meshgrid(mx.arange(h // 2), mx.arange(w // 2), indexing="ij") - x_ids = mx.stack([i, j, k], axis=-1) - x_ids = mx.repeat(x_ids.reshape(1, h * w // 4, 3), b, 0) - - return x, x_ids - - def _prepare_conditioning(self, n_images, t5_tokens, clip_tokens): - # Prepare the text features - txt = self.t5(t5_tokens) - if len(txt) == 1 and n_images > 1: - txt = mx.broadcast_to(txt, (n_images, *txt.shape[1:])) - txt_ids = mx.zeros((n_images, txt.shape[1], 3), dtype=mx.int32) - - # Prepare the clip text features - vec = self.clip(clip_tokens).pooled_output - if len(vec) == 1 and n_images > 1: - vec = mx.broadcast_to(vec, (n_images, *vec.shape[1:])) - - return txt, txt_ids, vec - - def _denoising_loop( - self, - x_t, - x_ids, - txt, - txt_ids, - vec, - num_steps: int = 35, - guidance: float = 4.0, - start: float = 1, - stop: float = 0, - ): - B = len(x_t) - - def scalar(x): - return mx.full((B,), x, dtype=self.dtype) - - guidance = scalar(guidance) - timesteps = self.sampler.timesteps( - num_steps, - x_t.shape[1], - start=start, - stop=stop, - ) - for i in range(num_steps): - t = timesteps[i] - t_prev = timesteps[i + 1] - - pred = self.flow( - img=x_t, - img_ids=x_ids, - txt=txt, - txt_ids=txt_ids, - y=vec, - timesteps=scalar(t), - guidance=guidance, - ) - x_t = self.sampler.step(pred, x_t, t, t_prev) - - yield x_t - - def generate_latents( - self, - text: str, - n_images: int = 1, - num_steps: int = 35, - guidance: float = 4.0, - latent_size: Tuple[int, int] = (64, 64), - seed=None, - ): - # Set the PRNG state - if seed is not None: - mx.random.seed(seed) - - # Create the latent variables - x_T = self.sampler.sample_prior((n_images, *latent_size, 16), dtype=self.dtype) - x_T, x_ids = self._prepare_latent_images(x_T) - - # Get the conditioning - t5_tokens, clip_tokens = self.tokenize(text) - txt, txt_ids, vec = self._prepare_conditioning(n_images, t5_tokens, clip_tokens) - - # Yield the conditioning for controlled evaluation by the caller - yield (x_T, x_ids, txt, txt_ids, vec) - - # Yield the latent sequences from the denoising loop - yield from self._denoising_loop( - x_T, x_ids, txt, txt_ids, vec, num_steps=num_steps, guidance=guidance - ) - - def decode(self, x, latent_size: Tuple[int, int] = (64, 64)): - h, w = latent_size - x = x.reshape(len(x), h // 2, w // 2, -1, 2, 2) - x = x.transpose(0, 1, 4, 2, 5, 3).reshape(len(x), h, w, -1) - x = self.ae.decode(x) - return mx.clip(x + 1, 0, 2) * 0.5 - - def generate_images( - self, - text: str, - n_images: int = 1, - num_steps: int = 35, - guidance: float = 4.0, - latent_size: Tuple[int, int] = (64, 64), - seed=None, - reload_text_encoders: bool = True, - progress: bool = True, - ): - latents = self.generate_latents( - text, n_images, num_steps, guidance, latent_size, seed - ) - mx.eval(next(latents)) - - if reload_text_encoders: - self.reload_text_encoders() - - for x_t in tqdm(latents, total=num_steps, disable=not progress, leave=True): - mx.eval(x_t) - - images = [] - for i in tqdm(range(len(x_t)), disable=not progress): - images.append(self.decode(x_t[i : i + 1])) - mx.eval(images[-1]) - images = mx.concatenate(images, axis=0) - mx.eval(images) - - return images - - def training_loss( - self, - x_0: mx.array, - t5_features: mx.array, - clip_features: mx.array, - guidance: mx.array, - ): - # Get the text conditioning - txt = t5_features - txt_ids = mx.zeros(txt.shape[:-1] + (3,), dtype=mx.int32) - vec = clip_features - - # Prepare the latent input - x_0, x_ids = self._prepare_latent_images(x_0) - - # Forward process - t = self.sampler.random_timesteps(*x_0.shape[:2], dtype=self.dtype) - eps = mx.random.normal(x_0.shape, dtype=self.dtype) - x_t = self.sampler.add_noise(x_0, t, noise=eps) - x_t = mx.stop_gradient(x_t) - - # Do the denoising - pred = self.flow( - img=x_t, - img_ids=x_ids, - txt=txt, - txt_ids=txt_ids, - y=vec, - timesteps=t, - guidance=guidance, - ) - - return (pred + x_0 - eps).square().mean() - - def linear_to_lora_layers(self, rank: int = 8, num_blocks: int = -1): - """Swap the linear layers in the transformer blocks with LoRA layers.""" - all_blocks = self.flow.double_blocks + self.flow.single_blocks - all_blocks.reverse() - num_blocks = num_blocks if num_blocks > 0 else len(all_blocks) - for i, block in zip(range(num_blocks), all_blocks): - loras = [] - for name, module in block.named_modules(): - if isinstance(module, nn.Linear): - loras.append((name, LoRALinear.from_base(module, r=rank))) - block.update_modules(tree_unflatten(loras)) - - def fuse_lora_layers(self): - fused_layers = [] - for name, module in self.flow.named_modules(): - if isinstance(module, LoRALinear): - fused_layers.append((name, module.fuse())) - self.flow.update_modules(tree_unflatten(fused_layers)) diff --git a/flux/flux/datasets.py b/flux/flux/datasets.py new file mode 100644 index 00000000..d31a09f1 --- /dev/null +++ b/flux/flux/datasets.py @@ -0,0 +1,75 @@ +import json +from pathlib import Path + +from PIL import Image + + +class Dataset: + def __getitem__(self, index: int): + raise NotImplementedError() + + def __len__(self): + raise NotImplementedError() + + +class LocalDataset(Dataset): + prompt_key = "prompt" + + def __init__(self, dataset: str, data_file): + self.dataset_base = Path(dataset) + with open(data_file, "r") as fid: + self._data = [json.loads(l) for l in fid] + + def __len__(self): + return len(self._data) + + def __getitem__(self, index: int): + item = self._data[index] + image = Image.open(self.dataset_base / item["image"]) + return image, item[self.prompt_key] + + +class LegacyDataset(LocalDataset): + prompt_key = "text" + + def __init__(self, dataset: str): + self.dataset_base = Path(dataset) + with open(self.dataset_base / "index.json") as f: + self._data = json.load(f)["data"] + + +class HuggingFaceDataset(Dataset): + + def __init__(self, dataset: str): + from datasets import load_dataset as hf_load_dataset + + self._df = hf_load_dataset(dataset)["train"] + + def __len__(self): + return len(self._df) + + def __getitem__(self, index: int): + item = self._df[index] + return item["image"], item["prompt"] + + +def load_dataset(dataset: str): + dataset_base = Path(dataset) + data_file = dataset_base / "train.jsonl" + legacy_file = dataset_base / "index.json" + + if data_file.exists(): + print(f"Load the local dataset {data_file} .", flush=True) + dataset = LocalDataset(dataset, data_file) + elif legacy_file.exists(): + print(f"Load the local dataset {legacy_file} .") + print() + print(" WARNING: 'index.json' is deprecated in favor of 'train.jsonl'.") + print(" See the README for details.") + print(flush=True) + dataset = LegacyDataset(dataset) + else: + print(f"Load the Hugging Face dataset {dataset} .", flush=True) + dataset = HuggingFaceDataset(dataset) + + return dataset diff --git a/flux/flux/flux.py b/flux/flux/flux.py new file mode 100644 index 00000000..3fd044ac --- /dev/null +++ b/flux/flux/flux.py @@ -0,0 +1,246 @@ +# Copyright © 2024 Apple Inc. + +from typing import Tuple + +import mlx.core as mx +import mlx.nn as nn +from mlx.utils import tree_unflatten +from tqdm import tqdm + +from .lora import LoRALinear +from .sampler import FluxSampler +from .utils import ( + load_ae, + load_clip, + load_clip_tokenizer, + load_flow_model, + load_t5, + load_t5_tokenizer, +) + + +class FluxPipeline: + def __init__(self, name: str, t5_padding: bool = True): + self.dtype = mx.bfloat16 + self.name = name + self.t5_padding = t5_padding + + self.ae = load_ae(name) + self.flow = load_flow_model(name) + self.clip = load_clip(name) + self.clip_tokenizer = load_clip_tokenizer(name) + self.t5 = load_t5(name) + self.t5_tokenizer = load_t5_tokenizer(name) + self.sampler = FluxSampler(name) + + def ensure_models_are_loaded(self): + mx.eval( + self.ae.parameters(), + self.flow.parameters(), + self.clip.parameters(), + self.t5.parameters(), + ) + + def reload_text_encoders(self): + self.t5 = load_t5(self.name) + self.clip = load_clip(self.name) + + def tokenize(self, text): + t5_tokens = self.t5_tokenizer.encode(text, pad=self.t5_padding) + clip_tokens = self.clip_tokenizer.encode(text) + return t5_tokens, clip_tokens + + def _prepare_latent_images(self, x): + b, h, w, c = x.shape + + # Pack the latent image to 2x2 patches + x = x.reshape(b, h // 2, 2, w // 2, 2, c) + x = x.transpose(0, 1, 3, 5, 2, 4).reshape(b, h * w // 4, c * 4) + + # Create positions ids used to positionally encode each patch. Due to + # the way RoPE works, this results in an interesting positional + # encoding where parts of the feature are holding different positional + # information. Namely, the first part holds information independent of + # the spatial position (hence 0s), the 2nd part holds vertical spatial + # information and the last one horizontal. + i = mx.zeros((h // 2, w // 2), dtype=mx.int32) + j, k = mx.meshgrid(mx.arange(h // 2), mx.arange(w // 2), indexing="ij") + x_ids = mx.stack([i, j, k], axis=-1) + x_ids = mx.repeat(x_ids.reshape(1, h * w // 4, 3), b, 0) + + return x, x_ids + + def _prepare_conditioning(self, n_images, t5_tokens, clip_tokens): + # Prepare the text features + txt = self.t5(t5_tokens) + if len(txt) == 1 and n_images > 1: + txt = mx.broadcast_to(txt, (n_images, *txt.shape[1:])) + txt_ids = mx.zeros((n_images, txt.shape[1], 3), dtype=mx.int32) + + # Prepare the clip text features + vec = self.clip(clip_tokens).pooled_output + if len(vec) == 1 and n_images > 1: + vec = mx.broadcast_to(vec, (n_images, *vec.shape[1:])) + + return txt, txt_ids, vec + + def _denoising_loop( + self, + x_t, + x_ids, + txt, + txt_ids, + vec, + num_steps: int = 35, + guidance: float = 4.0, + start: float = 1, + stop: float = 0, + ): + B = len(x_t) + + def scalar(x): + return mx.full((B,), x, dtype=self.dtype) + + guidance = scalar(guidance) + timesteps = self.sampler.timesteps( + num_steps, + x_t.shape[1], + start=start, + stop=stop, + ) + for i in range(num_steps): + t = timesteps[i] + t_prev = timesteps[i + 1] + + pred = self.flow( + img=x_t, + img_ids=x_ids, + txt=txt, + txt_ids=txt_ids, + y=vec, + timesteps=scalar(t), + guidance=guidance, + ) + x_t = self.sampler.step(pred, x_t, t, t_prev) + + yield x_t + + def generate_latents( + self, + text: str, + n_images: int = 1, + num_steps: int = 35, + guidance: float = 4.0, + latent_size: Tuple[int, int] = (64, 64), + seed=None, + ): + # Set the PRNG state + if seed is not None: + mx.random.seed(seed) + + # Create the latent variables + x_T = self.sampler.sample_prior((n_images, *latent_size, 16), dtype=self.dtype) + x_T, x_ids = self._prepare_latent_images(x_T) + + # Get the conditioning + t5_tokens, clip_tokens = self.tokenize(text) + txt, txt_ids, vec = self._prepare_conditioning(n_images, t5_tokens, clip_tokens) + + # Yield the conditioning for controlled evaluation by the caller + yield (x_T, x_ids, txt, txt_ids, vec) + + # Yield the latent sequences from the denoising loop + yield from self._denoising_loop( + x_T, x_ids, txt, txt_ids, vec, num_steps=num_steps, guidance=guidance + ) + + def decode(self, x, latent_size: Tuple[int, int] = (64, 64)): + h, w = latent_size + x = x.reshape(len(x), h // 2, w // 2, -1, 2, 2) + x = x.transpose(0, 1, 4, 2, 5, 3).reshape(len(x), h, w, -1) + x = self.ae.decode(x) + return mx.clip(x + 1, 0, 2) * 0.5 + + def generate_images( + self, + text: str, + n_images: int = 1, + num_steps: int = 35, + guidance: float = 4.0, + latent_size: Tuple[int, int] = (64, 64), + seed=None, + reload_text_encoders: bool = True, + progress: bool = True, + ): + latents = self.generate_latents( + text, n_images, num_steps, guidance, latent_size, seed + ) + mx.eval(next(latents)) + + if reload_text_encoders: + self.reload_text_encoders() + + for x_t in tqdm(latents, total=num_steps, disable=not progress, leave=True): + mx.eval(x_t) + + images = [] + for i in tqdm(range(len(x_t)), disable=not progress, desc="generate images"): + images.append(self.decode(x_t[i : i + 1])) + mx.eval(images[-1]) + images = mx.concatenate(images, axis=0) + mx.eval(images) + + return images + + def training_loss( + self, + x_0: mx.array, + t5_features: mx.array, + clip_features: mx.array, + guidance: mx.array, + ): + # Get the text conditioning + txt = t5_features + txt_ids = mx.zeros(txt.shape[:-1] + (3,), dtype=mx.int32) + vec = clip_features + + # Prepare the latent input + x_0, x_ids = self._prepare_latent_images(x_0) + + # Forward process + t = self.sampler.random_timesteps(*x_0.shape[:2], dtype=self.dtype) + eps = mx.random.normal(x_0.shape, dtype=self.dtype) + x_t = self.sampler.add_noise(x_0, t, noise=eps) + x_t = mx.stop_gradient(x_t) + + # Do the denoising + pred = self.flow( + img=x_t, + img_ids=x_ids, + txt=txt, + txt_ids=txt_ids, + y=vec, + timesteps=t, + guidance=guidance, + ) + + return (pred + x_0 - eps).square().mean() + + def linear_to_lora_layers(self, rank: int = 8, num_blocks: int = -1): + """Swap the linear layers in the transformer blocks with LoRA layers.""" + all_blocks = self.flow.double_blocks + self.flow.single_blocks + all_blocks.reverse() + num_blocks = num_blocks if num_blocks > 0 else len(all_blocks) + for i, block in zip(range(num_blocks), all_blocks): + loras = [] + for name, module in block.named_modules(): + if isinstance(module, nn.Linear): + loras.append((name, LoRALinear.from_base(module, r=rank))) + block.update_modules(tree_unflatten(loras)) + + def fuse_lora_layers(self): + fused_layers = [] + for name, module in self.flow.named_modules(): + if isinstance(module, LoRALinear): + fused_layers.append((name, module.fuse())) + self.flow.update_modules(tree_unflatten(fused_layers)) diff --git a/flux/flux/trainer.py b/flux/flux/trainer.py new file mode 100644 index 00000000..40a126e8 --- /dev/null +++ b/flux/flux/trainer.py @@ -0,0 +1,98 @@ +import mlx.core as mx +import numpy as np +from PIL import Image, ImageFile +from tqdm import tqdm + +from .datasets import Dataset +from .flux import FluxPipeline + + +class Trainer: + + def __init__(self, flux: FluxPipeline, dataset: Dataset, args): + self.flux = flux + self.dataset = dataset + self.args = args + self.latents = [] + self.t5_features = [] + self.clip_features = [] + + def _random_crop_resize(self, img): + resolution = self.args.resolution + width, height = img.size + + a, b, c, d = mx.random.uniform(shape=(4,), stream=mx.cpu).tolist() + + # Random crop the input image between 0.8 to 1.0 of its original dimensions + crop_size = ( + max((0.8 + 0.2 * a) * width, resolution[0]), + max((0.8 + 0.2 * b) * height, resolution[1]), + ) + pan = (width - crop_size[0], height - crop_size[1]) + img = img.crop( + ( + pan[0] * c, + pan[1] * d, + crop_size[0] + pan[0] * c, + crop_size[1] + pan[1] * d, + ) + ) + + # Fit the largest rectangle with the ratio of resolution in the image + # rectangle. + width, height = crop_size + ratio = resolution[0] / resolution[1] + r1 = (height * ratio, height) + r2 = (width, width / ratio) + r = r1 if r1[0] <= width else r2 + img = img.crop( + ( + (width - r[0]) / 2, + (height - r[1]) / 2, + (width + r[0]) / 2, + (height + r[1]) / 2, + ) + ) + + # Finally resize the image to resolution + img = img.resize(resolution, Image.LANCZOS) + + return mx.array(np.array(img)) + + def _encode_image(self, input_img: ImageFile.ImageFile, num_augmentations: int): + for i in range(num_augmentations): + img = self._random_crop_resize(input_img) + img = (img[:, :, :3].astype(self.flux.dtype) / 255) * 2 - 1 + x_0 = self.flux.ae.encode(img[None]) + x_0 = x_0.astype(self.flux.dtype) + mx.eval(x_0) + self.latents.append(x_0) + + def _encode_prompt(self, prompt): + t5_tok, clip_tok = self.flux.tokenize([prompt]) + t5_feat = self.flux.t5(t5_tok) + clip_feat = self.flux.clip(clip_tok).pooled_output + mx.eval(t5_feat, clip_feat) + self.t5_features.append(t5_feat) + self.clip_features.append(clip_feat) + + def encode_dataset(self): + """Encode the images & prompt in the latent space to prepare for training.""" + self.flux.ae.eval() + for image, prompt in tqdm(self.dataset, desc="encode dataset"): + self._encode_image(image, self.args.num_augmentations) + self._encode_prompt(prompt) + + def iterate(self, batch_size): + xs = mx.concatenate(self.latents) + t5 = mx.concatenate(self.t5_features) + clip = mx.concatenate(self.clip_features) + mx.eval(xs, t5, clip) + n_aug = self.args.num_augmentations + while True: + x_indices = mx.random.permutation(len(self.latents)) + c_indices = x_indices // n_aug + for i in range(0, len(self.latents), batch_size): + x_i = x_indices[i : i + batch_size] + c_i = c_indices[i : i + batch_size] + yield xs[x_i], t5[c_i], clip[c_i] From 743763bc2e4113dcf3c478058623cb02e770a237 Mon Sep 17 00:00:00 2001 From: aronson Date: Sun, 20 Oct 2024 22:46:43 -0500 Subject: [PATCH 058/188] Handle empty string case in maybe_trim_space (#1055) * Handle empty string case in maybe_trim_space * nit --------- Co-authored-by: Awni Hannun --- llms/mlx_lm/tokenizer_utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/llms/mlx_lm/tokenizer_utils.py b/llms/mlx_lm/tokenizer_utils.py index d8694d86..78ec2ff8 100644 --- a/llms/mlx_lm/tokenizer_utils.py +++ b/llms/mlx_lm/tokenizer_utils.py @@ -193,7 +193,9 @@ class BPEStreamingDetokenizer(StreamingDetokenizer): self.tokens = [] def _maybe_trim_space(self, current_text): - if current_text[0] != " ": + if len(current_text) == 0: + return current_text + elif current_text[0] != " ": return current_text elif not self.text: return current_text[1:] From 66e7bcb8866a050727849d9a303c54a0119f0f99 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Tue, 22 Oct 2024 09:56:45 -0700 Subject: [PATCH 059/188] override dtype with quant (#1062) --- llms/mlx_lm/convert.py | 2 +- llms/mlx_lm/models/gemma2.py | 2 +- llms/mlx_lm/utils.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/llms/mlx_lm/convert.py b/llms/mlx_lm/convert.py index a3f43f71..9bac77a5 100644 --- a/llms/mlx_lm/convert.py +++ b/llms/mlx_lm/convert.py @@ -31,7 +31,7 @@ def configure_parser() -> argparse.ArgumentParser: ) parser.add_argument( "--dtype", - help="Type to save the parameters, ignored if -q is given.", + help="Type to save the non-quantized parameters.", type=str, choices=["float16", "bfloat16", "float32"], default="float16", diff --git a/llms/mlx_lm/models/gemma2.py b/llms/mlx_lm/models/gemma2.py index ccc327a8..64951ae4 100644 --- a/llms/mlx_lm/models/gemma2.py +++ b/llms/mlx_lm/models/gemma2.py @@ -111,7 +111,7 @@ class MLP(nn.Module): self.up_proj = nn.Linear(dim, hidden_dim, bias=False) def __call__(self, x) -> mx.array: - return self.down_proj(nn.gelu(self.gate_proj(x)) * self.up_proj(x)) + return self.down_proj(nn.gelu_approx(self.gate_proj(x)) * self.up_proj(x)) class TransformerBlock(nn.Module): diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index 4f872982..92741b68 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -720,7 +720,7 @@ def convert( model, config, tokenizer = fetch_from_hub(model_path, lazy=True) weights = dict(tree_flatten(model.parameters())) - dtype = mx.float16 if quantize else getattr(mx, dtype) + dtype = getattr(mx, dtype) weights = {k: v.astype(dtype) for k, v in weights.items()} if quantize and dequantize: From d1d480867b2248fb95fedcf7f9d33b41689d9991 Mon Sep 17 00:00:00 2001 From: madroid Date: Wed, 23 Oct 2024 03:19:11 +0800 Subject: [PATCH 060/188] LoRA: update tools datasets docs (#1063) * LoRA: update tools datasets docs * nits * nits --------- Co-authored-by: Awni Hannun --- llms/mlx_lm/LORA.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/llms/mlx_lm/LORA.md b/llms/mlx_lm/LORA.md index 2d0dcf60..15676360 100644 --- a/llms/mlx_lm/LORA.md +++ b/llms/mlx_lm/LORA.md @@ -222,6 +222,17 @@ data formats. Here are examples of these formats: } ``` + +The format for the `arguments` field in a function varies for different models. +Common formats include JSON strings and dictionaries. The example provided +follows the format used by +[OpenAI](https://platform.openai.com/docs/guides/fine-tuning/fine-tuning-examples) +and [Mistral +AI](https://github.com/mistralai/mistral-finetune?tab=readme-ov-file#instruct). +A dictionary format is used in Hugging Face's [chat +templates](https://huggingface.co/docs/transformers/main/en/chat_templating#a-complete-tool-use-example). +Refer to the documentation for the model you are fine-tuning for more details. + `completions`: @@ -241,7 +252,7 @@ each line not expected by the loader will be ignored. > [!NOTE] > Each example in the datasets must be on a single line. Do not put more than -> one example per line and do not split an example accross multiple lines. +> one example per line and do not split an example across multiple lines. ### Hugging Face Datasets From 9000e280aeb56c2bcce128001ab157030095687a Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Tue, 22 Oct 2024 15:44:08 -0700 Subject: [PATCH 061/188] fix mamba models conversion (#1065) --- llms/mlx_lm/models/mamba.py | 2 +- llms/mlx_lm/models/recurrent_gemma.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/llms/mlx_lm/models/mamba.py b/llms/mlx_lm/models/mamba.py index d2740dc1..84f498e9 100644 --- a/llms/mlx_lm/models/mamba.py +++ b/llms/mlx_lm/models/mamba.py @@ -205,7 +205,7 @@ class Model(nn.Module): def sanitize(self, weights): for k, v in weights.items(): - if "conv1d.weight" in k and v.ndim == 3: + if "conv1d.weight" in k and v.shape[-1] != 1: weights[k] = v.moveaxis(2, 1) return weights diff --git a/llms/mlx_lm/models/recurrent_gemma.py b/llms/mlx_lm/models/recurrent_gemma.py index 06a307a6..5595d311 100644 --- a/llms/mlx_lm/models/recurrent_gemma.py +++ b/llms/mlx_lm/models/recurrent_gemma.py @@ -440,7 +440,7 @@ class Model(nn.Module): def sanitize(self, weights): for k, v in weights.items(): - if "conv_1d.weight" in k and v.ndim == 3: + if "conv_1d.weight" in k and v.shape[-1] != 1: weights[k] = v.moveaxis(2, 1) if "lm_head.weight" not in weights: self.pop("lm_head") From 4971462bf0dd7bba07d9f18fb0fd2752a51fde40 Mon Sep 17 00:00:00 2001 From: Saurav Maheshkar Date: Fri, 25 Oct 2024 05:56:17 +0100 Subject: [PATCH 062/188] feat(clip): add linear probe evaluation script (#960) --- clip/linear_probe.py | 56 +++++++++++++++++++++++++++++++++++++++++++ clip/requirements.txt | 1 + 2 files changed, 57 insertions(+) create mode 100644 clip/linear_probe.py diff --git a/clip/linear_probe.py b/clip/linear_probe.py new file mode 100644 index 00000000..2649e397 --- /dev/null +++ b/clip/linear_probe.py @@ -0,0 +1,56 @@ +# Mirror of the Linear Probe Evaluation Script +# from the official CLIP Repository. + +import mlx.core as mx +import numpy as np +from image_processor import CLIPImageProcessor +from mlx.data.datasets import load_cifar10 +from model import CLIPModel +from PIL import Image +from sklearn.linear_model import LogisticRegression +from tqdm import tqdm + + +def get_cifar10(batch_size, root=None): + tr = load_cifar10(root=root).batch(batch_size) + test = load_cifar10(root=root, train=False).batch(batch_size) + + return tr, test + + +def get_features(model, image_proc, iter): + all_features = [] + all_labels = [] + + for batch in tqdm(iter): + image, label = batch["image"], batch["label"] + x = image_proc([Image.fromarray(im) for im in image]) + y = mx.array(label) + + image_embeds = model.get_image_features(x) + mx.eval(image_embeds) + + all_features.append(image_embeds) + all_labels.append(y) + + return mx.concatenate(all_features), mx.concatenate(all_labels) + + +if __name__ == "__main__": + model = CLIPModel.from_pretrained("mlx_model") + image_proc = CLIPImageProcessor.from_pretrained("mlx_model") + + train_iter, test_iter = get_cifar10(batch_size=256) + train_features, train_labels = get_features(model, image_proc, train_iter) + test_features, test_labels = get_features(model, image_proc, test_iter) + + # Perform logistic regression + # NOTE: The value of C should be determined via a hyperparameter sweep + # using a validation split + classifier = LogisticRegression(random_state=0, C=0.316, max_iter=1000, verbose=1) + classifier.fit(train_features, train_labels) + + # Evaluate using the logistic regression classifier + predictions = classifier.predict(test_features) + accuracy = (test_labels.squeeze() == predictions).mean().item() * 100 + print(f"Accuracy = {accuracy:.3f}") diff --git a/clip/requirements.txt b/clip/requirements.txt index 74f826ea..8e05620e 100644 --- a/clip/requirements.txt +++ b/clip/requirements.txt @@ -1,4 +1,5 @@ mlx +mlx-data numpy transformers torch From ab4bf05c6e72928ec0ca0143a3f976f6e787e40c Mon Sep 17 00:00:00 2001 From: hschaeufler <9865991+hschaeufler@users.noreply.github.com> Date: Sat, 26 Oct 2024 19:34:46 +0300 Subject: [PATCH 063/188] Update lora_config.yaml with new param: num_layers (#1068) --- llms/mlx_lm/examples/lora_config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llms/mlx_lm/examples/lora_config.yaml b/llms/mlx_lm/examples/lora_config.yaml index 4ec9a23c..530272c7 100644 --- a/llms/mlx_lm/examples/lora_config.yaml +++ b/llms/mlx_lm/examples/lora_config.yaml @@ -14,7 +14,7 @@ data: "/path/to/training/data" seed: 0 # Number of layers to fine-tune -lora_layers: 16 +num_layers: 16 # Minibatch size. batch_size: 4 From 8fe9539af76075405b2c3071ba9657aa921d749d Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Sun, 27 Oct 2024 15:06:07 -0700 Subject: [PATCH 064/188] Fix detokenizer space match for quote (#1072) * fix + test * remove transformer flax/torch warning * format --- llms/mlx_lm/__init__.py | 5 +++++ llms/mlx_lm/tokenizer_utils.py | 2 +- llms/tests/test_tokenizers.py | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/llms/mlx_lm/__init__.py b/llms/mlx_lm/__init__.py index 502c78e5..538be927 100644 --- a/llms/mlx_lm/__init__.py +++ b/llms/mlx_lm/__init__.py @@ -1,4 +1,9 @@ # Copyright © 2023-2024 Apple Inc. +import os + from ._version import __version__ + +os.environ["TRANSFORMERS_NO_ADVISORY_WARNINGS"] = "1" + from .utils import convert, generate, load, stream_generate diff --git a/llms/mlx_lm/tokenizer_utils.py b/llms/mlx_lm/tokenizer_utils.py index 78ec2ff8..0cbc3b9b 100644 --- a/llms/mlx_lm/tokenizer_utils.py +++ b/llms/mlx_lm/tokenizer_utils.py @@ -169,7 +169,7 @@ class BPEStreamingDetokenizer(StreamingDetokenizer): """ _byte_decoder = None - _space_matches = (".", "?", "!", ",", "'", "n't", "'m", "'s", "'ve", "'re") + _space_matches = (".", "?", "!", ",", "n't", "'m", "'s", "'ve", "'re") def __init__(self, tokenizer): diff --git a/llms/tests/test_tokenizers.py b/llms/tests/test_tokenizers.py index 7b4828b1..03445c1f 100644 --- a/llms/tests/test_tokenizers.py +++ b/llms/tests/test_tokenizers.py @@ -51,6 +51,9 @@ class TestTokenizers(unittest.TestCase): tokens = tokenizer.encode("3 3") check(tokens) + tokens = tokenizer.encode("import 'package:flutter/material.dart';") + check(tokens) + def test_tokenizers(self): tokenizer_repos = [ ("mlx-community/Qwen1.5-0.5B-Chat-4bit", BPEStreamingDetokenizer), From 9f34fdbda4527e85ab6b98d9f343f7a2972085f1 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Thu, 31 Oct 2024 08:17:14 -0700 Subject: [PATCH 065/188] Wire models in MLX LM (#1069) * wired in MLX LM * fix synch * comment + nit * version * mlx lm version * bump to 0.19.2 --- llms/README.md | 25 ++++++++ llms/mlx_lm/_version.py | 2 +- llms/mlx_lm/chat.py | 2 +- llms/mlx_lm/requirements.txt | 2 +- llms/mlx_lm/utils.py | 115 +++++++++++++++++++++++------------ 5 files changed, 104 insertions(+), 42 deletions(-) diff --git a/llms/README.md b/llms/README.md index 20863041..f539988a 100644 --- a/llms/README.md +++ b/llms/README.md @@ -248,3 +248,28 @@ model, tokenizer = load( tokenizer_config={"eos_token": "<|endoftext|>", "trust_remote_code": True}, ) ``` + +### Large Models + +> [!NOTE] + This requires macOS 15.0 or higher to work. + +Models which are large relative to the total RAM available on the machine can +be slow. `mlx-lm` will attempt to make them faster by wiring the memory +occupied by the model and cache. This requires macOS 15 or higher to +work. + +If you see the following warning message: + +> [WARNING] Generating with a model that requires ... + +then the model will likely be slow on the given machine. If the model fits in +RAM then it can often be sped up by increasing the system wired memory limit. +To increase the limit, set the following `sysctl`: + +```bash +sudo sysctl iogpu.wired_limit_mb=N +``` + +The value `N` should be larger than the size of the model in megabytes but +smaller than the memory size of the machine. diff --git a/llms/mlx_lm/_version.py b/llms/mlx_lm/_version.py index 70239db6..3811616f 100644 --- a/llms/mlx_lm/_version.py +++ b/llms/mlx_lm/_version.py @@ -1,3 +1,3 @@ # Copyright © 2023-2024 Apple Inc. -__version__ = "0.19.1" +__version__ = "0.19.3" diff --git a/llms/mlx_lm/chat.py b/llms/mlx_lm/chat.py index 7968a868..ea1a99c7 100644 --- a/llms/mlx_lm/chat.py +++ b/llms/mlx_lm/chat.py @@ -56,7 +56,7 @@ def main(): tokenizer_config={"trust_remote_code": True}, ) - print(f"[INFO] Starting chat sessiong with {args.model}. To exit, enter 'q'.") + print(f"[INFO] Starting chat session with {args.model}. To exit, enter 'q'.") prompt_cache = make_prompt_cache(model, args.max_kv_size) while True: query = input(">> ") diff --git a/llms/mlx_lm/requirements.txt b/llms/mlx_lm/requirements.txt index 814c03cc..48012863 100644 --- a/llms/mlx_lm/requirements.txt +++ b/llms/mlx_lm/requirements.txt @@ -1,4 +1,4 @@ -mlx>=0.17.0 +mlx>=0.19.2 numpy transformers[sentencepiece]>=4.39.3 protobuf diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index 92741b68..5b437c98 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -1,5 +1,6 @@ # Copyright © 2023-2024 Apple Inc. +import contextlib import copy import glob import importlib @@ -14,7 +15,7 @@ from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Type, import mlx.core as mx import mlx.nn as nn from huggingface_hub import snapshot_download -from mlx.utils import tree_flatten +from mlx.utils import tree_flatten, tree_reduce from transformers import PreTrainedTokenizer # Local imports @@ -39,6 +40,40 @@ class ModelNotFoundError(Exception): super().__init__(self.message) +@contextlib.contextmanager +def wired_limit(model: nn.Module, streams: Optional[List[mx.Stream]] = None): + """ + A context manager to temporarily change the wired limit. + + Note, the wired limit should not be changed during an async eval. If an + async eval could be running pass in the streams to synchronize with prior + to exiting the context manager. + """ + model_bytes = tree_reduce( + lambda acc, x: acc + x.nbytes if isinstance(x, mx.array) else acc, model, 0 + ) + max_rec_size = mx.metal.device_info()["max_recommended_working_set_size"] + if model_bytes > 0.9 * max_rec_size: + model_mb = model_bytes // 2**20 + max_rec_mb = max_rec_size // 2**20 + print( + "[WARNING] Generating with a model that requires {model_mb} MB " + "which is close to the maximum recommended size of {max_rec_mb} " + "MB. This can be slow. See the documentation for possible work-arounds: " + "https://github.com/ml-explore/mlx-examples/tree/main/llms#large-models" + ) + old_limit = mx.metal.set_wired_limit(max_rec_size) + try: + yield None + finally: + if streams is not None: + for s in streams: + mx.synchronize(s) + else: + mx.synchronize() + mx.metal.set_wired_limit(old_limit) + + def _get_classes(config: dict): """ Retrieve the model and model args classes based on the configuration. @@ -330,48 +365,50 @@ def generate( prompt_tokens = mx.array(tokenizer.encode(prompt)) detokenizer = tokenizer.detokenizer - tic = time.perf_counter() - detokenizer.reset() + with wired_limit(model): + tic = time.perf_counter() + detokenizer.reset() + for n, (token, logprobs) in zip( + range(max_tokens), + generate_step(prompt_tokens, model, **kwargs), + ): + if n == 0: + prompt_time = time.perf_counter() - tic + tic = time.perf_counter() + if token == tokenizer.eos_token_id: + break + detokenizer.add_token(token) - for n, (token, logprobs) in zip( - range(max_tokens), - generate_step(prompt_tokens, model, **kwargs), - ): - if n == 0: - prompt_time = time.perf_counter() - tic - tic = time.perf_counter() - if token == tokenizer.eos_token_id: - break - detokenizer.add_token(token) + if verbose: + if formatter: + # We have to finalize so that the prob corresponds to the last segment + detokenizer.finalize() + with mx.stream(mx.cpu): + prob = mx.exp(logprobs[token]).item() + formatter(detokenizer.last_segment, prob) + else: + print(detokenizer.last_segment, end="", flush=True) + + token_count = n + 1 + detokenizer.finalize() if verbose: - if formatter: - # We have to finalize so that the prob corresponds to the last segment - detokenizer.finalize() - with mx.stream(mx.cpu): - prob = mx.exp(logprobs[token]).item() - formatter(detokenizer.last_segment, prob) - else: - print(detokenizer.last_segment, end="", flush=True) + gen_time = time.perf_counter() - tic + print(detokenizer.last_segment, flush=True) + print("=" * 10) + if token_count == 0: + print("No tokens generated for this prompt") + return + prompt_tps = prompt_tokens.size / prompt_time + gen_tps = (token_count - 1) / gen_time + print( + f"Prompt: {prompt_tokens.size} tokens, {prompt_tps:.3f} tokens-per-sec" + ) + print(f"Generation: {token_count} tokens, {gen_tps:.3f} tokens-per-sec") + peak_mem = mx.metal.get_peak_memory() / 2**30 + print(f"Peak memory: {peak_mem:.3f} GB") - token_count = n + 1 - detokenizer.finalize() - - if verbose: - gen_time = time.perf_counter() - tic - print(detokenizer.last_segment, flush=True) - print("=" * 10) - if token_count == 0: - print("No tokens generated for this prompt") - return - prompt_tps = prompt_tokens.size / prompt_time - gen_tps = (token_count - 1) / gen_time - print(f"Prompt: {prompt_tokens.size} tokens, {prompt_tps:.3f} tokens-per-sec") - print(f"Generation: {token_count} tokens, {gen_tps:.3f} tokens-per-sec") - peak_mem = mx.metal.get_peak_memory() / 2**30 - print(f"Peak memory: {peak_mem:.3f} GB") - - return detokenizer.text + return detokenizer.text def load_config(model_path: Path) -> dict: From 85ffd2c96a45a8cb900f95a2ded61d858d673399 Mon Sep 17 00:00:00 2001 From: Alex Barron Date: Thu, 31 Oct 2024 16:59:52 -0700 Subject: [PATCH 066/188] Quantized KV Cache (#1075) * add QuantizedKVCache * simplify * add tests * single sdpa function * fix sed * in place * fix tests * support different k and v head dims --- llms/mlx_lm/cache_prompt.py | 30 +++++++- llms/mlx_lm/generate.py | 38 +++++++++- llms/mlx_lm/models/base.py | 63 ++++++++++++++++ llms/mlx_lm/models/cache.py | 102 +++++++++++++++++++++++++- llms/mlx_lm/models/cohere.py | 6 +- llms/mlx_lm/models/dbrx.py | 6 +- llms/mlx_lm/models/deepseek.py | 6 +- llms/mlx_lm/models/deepseek_v2.py | 6 +- llms/mlx_lm/models/gemma.py | 6 +- llms/mlx_lm/models/gpt2.py | 6 +- llms/mlx_lm/models/gpt_bigcode.py | 6 +- llms/mlx_lm/models/gpt_neox.py | 6 +- llms/mlx_lm/models/internlm2.py | 6 +- llms/mlx_lm/models/llama.py | 9 ++- llms/mlx_lm/models/minicpm.py | 6 +- llms/mlx_lm/models/mixtral.py | 6 +- llms/mlx_lm/models/nemotron.py | 6 +- llms/mlx_lm/models/openelm.py | 6 +- llms/mlx_lm/models/phi.py | 11 ++- llms/mlx_lm/models/phi3.py | 6 +- llms/mlx_lm/models/phi3small.py | 6 +- llms/mlx_lm/models/phimoe.py | 6 +- llms/mlx_lm/models/phixtral.py | 11 ++- llms/mlx_lm/models/plamo.py | 5 +- llms/mlx_lm/models/qwen.py | 6 +- llms/mlx_lm/models/qwen2.py | 6 +- llms/mlx_lm/models/qwen2_moe.py | 6 +- llms/mlx_lm/models/recurrent_gemma.py | 6 +- llms/mlx_lm/models/stablelm.py | 6 +- llms/mlx_lm/models/starcoder2.py | 6 +- llms/mlx_lm/utils.py | 32 +++++++- llms/tests/test_prompt_cache.py | 63 ++++++++++++++++ 32 files changed, 411 insertions(+), 85 deletions(-) diff --git a/llms/mlx_lm/cache_prompt.py b/llms/mlx_lm/cache_prompt.py index 04e75a3e..7bb06411 100644 --- a/llms/mlx_lm/cache_prompt.py +++ b/llms/mlx_lm/cache_prompt.py @@ -8,7 +8,9 @@ import time import mlx.core as mx from .models.cache import make_prompt_cache, save_prompt_cache -from .utils import load +from .utils import load, maybe_quantize_kv_cache + +DEFAULT_QUANTIZED_KV_START = 5000 def setup_arg_parser(): @@ -70,6 +72,26 @@ def setup_arg_parser(): required=True, help="Message to be processed by the model ('-' reads from stdin)", ) + parser.add_argument( + "--kv-bits", + type=int, + help="Number of bits for KV cache quantization. " + "Defaults to no quantization.", + default=None, + ) + parser.add_argument( + "--kv-group-size", + type=int, + help="Group size for KV cache quantization.", + default=64, + ) + parser.add_argument( + "--quantized-kv-start", + help="When --kv-bits is set, start quantizing the KV cache " + "from this step onwards.", + type=int, + default=DEFAULT_QUANTIZED_KV_START, + ) return parser @@ -127,6 +149,7 @@ def main(): start = time.time() max_msg_len = 0 while y.size > 0: + model(y[:step_size][None], cache=cache) mx.eval([c.state for c in cache]) processed += min(y.size, step_size) @@ -136,6 +159,11 @@ def main(): msg = f"\rProcessed {processed:6d} tokens ({speed:6.2f} tok/s)" max_msg_len = max(max_msg_len, len(msg)) print(msg + " " * (max_msg_len - len(msg)), end="", flush=True) + + maybe_quantize_kv_cache( + cache, args.quantized_kv_start, args.kv_group_size, args.kv_bits + ) + print() print(f"Peak memory: {mx.metal.get_peak_memory() / 2**30:.3f} GB") diff --git a/llms/mlx_lm/generate.py b/llms/mlx_lm/generate.py index 0bf98ab2..0355ca29 100644 --- a/llms/mlx_lm/generate.py +++ b/llms/mlx_lm/generate.py @@ -6,7 +6,7 @@ import sys import mlx.core as mx -from .models.cache import load_prompt_cache +from .models.cache import QuantizedKVCache, load_prompt_cache from .utils import generate, load DEFAULT_PROMPT = "hello" @@ -15,6 +15,7 @@ DEFAULT_TEMP = 0.0 DEFAULT_TOP_P = 1.0 DEFAULT_SEED = 0 DEFAULT_MODEL = "mlx-community/Llama-3.2-3B-Instruct-4bit" +DEFAULT_QUANTIZED_KV_START = 5000 def str2bool(string): @@ -107,6 +108,26 @@ def setup_arg_parser(): default=None, help="A file containing saved KV caches to avoid recomputing them", ) + parser.add_argument( + "--kv-bits", + type=int, + help="Number of bits for KV cache quantization. " + "Defaults to no quantization.", + default=None, + ) + parser.add_argument( + "--kv-group-size", + type=int, + help="Group size for KV cache quantization.", + default=64, + ) + parser.add_argument( + "--quantized-kv-start", + help="When --kv-bits is set, start quantizing the KV cache " + "from this step onwards.", + type=int, + default=DEFAULT_QUANTIZED_KV_START, + ) return parser @@ -150,8 +171,18 @@ def main(): using_cache = args.prompt_cache_file is not None if using_cache: prompt_cache, metadata = load_prompt_cache( - args.prompt_cache_file, return_metadata=True + args.prompt_cache_file, + return_metadata=True, ) + if isinstance(prompt_cache[0], QuantizedKVCache): + if args.kv_bits is not None and args.kv_bits != prompt_cache[0].bits: + raise ValueError( + "--kv-bits does not match the kv cache loaded from --prompt-cache-file." + ) + if args.kv_group_size != prompt_cache[0].group_size: + raise ValueError( + "--kv-group-size does not match the kv cache loaded from --prompt-cache-file." + ) # Building tokenizer_config tokenizer_config = ( @@ -227,6 +258,9 @@ def main(): top_p=args.top_p, max_kv_size=args.max_kv_size, prompt_cache=prompt_cache if using_cache else None, + kv_bits=args.kv_bits, + kv_group_size=args.kv_group_size, + quantized_kv_start=args.quantized_kv_start, ) if not args.verbose: print(response) diff --git a/llms/mlx_lm/models/base.py b/llms/mlx_lm/models/base.py index 3628a808..cda41c79 100644 --- a/llms/mlx_lm/models/base.py +++ b/llms/mlx_lm/models/base.py @@ -5,6 +5,9 @@ from dataclasses import dataclass from typing import Any, Optional import mlx.core as mx +from mlx.utils import tree_map + +from .cache import QuantizedKVCache @dataclass @@ -48,3 +51,63 @@ def create_attention_mask(h: mx.array, cache: Optional[Any] = None): else: mask = None return mask + + +def quantized_scaled_dot_product_attention( + queries: mx.array, + q_keys: tuple[mx.array, mx.array, mx.array], + q_values: tuple[mx.array, mx.array, mx.array], + scale: float, + mask: Optional[mx.array], + group_size: int = 64, + bits: int = 8, +) -> mx.array: + B, n_q_heads, L, D = queries.shape + n_kv_heads = q_keys[0].shape[-3] + n_repeats = n_q_heads // n_kv_heads + + queries *= scale + + if n_repeats > 1: + queries = mx.reshape(queries, (B, n_kv_heads, n_repeats, L, D)) + q_keys = tree_map(lambda x: mx.expand_dims(x, axis=-3), q_keys) + q_values = tree_map(lambda x: mx.expand_dims(x, axis=-3), q_values) + + scores = mx.quantized_matmul( + queries, *q_keys, transpose=True, group_size=group_size, bits=bits + ) + if mask is not None: + scores += mask + scores = mx.softmax(scores, axis=-1, precise=True) + out = mx.quantized_matmul( + scores, *q_values, transpose=False, group_size=group_size, bits=bits + ) + + if n_repeats > 1: + out = mx.reshape(out, (B, n_q_heads, L, D)) + + return out + + +def scaled_dot_product_attention( + queries, + keys, + values, + cache, + scale: float, + mask: Optional[mx.array], +) -> mx.array: + if isinstance(cache, QuantizedKVCache): + return quantized_scaled_dot_product_attention( + queries, + keys, + values, + scale=scale, + mask=mask, + group_size=cache.group_size, + bits=cache.bits, + ) + else: + return mx.fast.scaled_dot_product_attention( + queries, keys, values, scale=scale, mask=mask + ) diff --git a/llms/mlx_lm/models/cache.py b/llms/mlx_lm/models/cache.py index a6a56e0a..1cd5289d 100644 --- a/llms/mlx_lm/models/cache.py +++ b/llms/mlx_lm/models/cache.py @@ -4,10 +4,13 @@ from typing import Any, Dict, List, Optional import mlx.core as mx import mlx.nn as nn -from mlx.utils import tree_flatten, tree_unflatten +from mlx.utils import tree_flatten, tree_map, tree_unflatten -def make_prompt_cache(model: nn.Module, max_kv_size: Optional[int] = None) -> List[Any]: +def make_prompt_cache( + model: nn.Module, + max_kv_size: Optional[int] = None, +) -> List[Any]: """ Construct the model's cache for use when cgeneration. @@ -126,6 +129,88 @@ class _BaseCache: return False +class QuantizedKVCache(_BaseCache): + def __init__(self, group_size: int = 64, bits: int = 8): + self.keys = None + self.values = None + self.offset = 0 + self.step = 256 + self.group_size = group_size + self.bits = bits + + def update_and_fetch(self, keys, values): + B, n_kv_heads, num_steps, k_head_dim = keys.shape + v_head_dim = values.shape[-1] + prev = self.offset + + if self.keys is None or (prev + num_steps) > self.keys[0].shape[-2]: + el_per_int = 8 * mx.uint32.size // self.bits + new_steps = (self.step + num_steps - 1) // self.step * self.step + shape = (B, n_kv_heads, new_steps) + + def init_quant(dim): + return ( + mx.zeros((*shape, dim // el_per_int), dtype=mx.uint32), + mx.zeros((*shape, dim // self.group_size), dtype=keys.dtype), + mx.zeros((*shape, dim // self.group_size), dtype=keys.dtype), + ) + + def expand_quant(x): + new_x = mx.zeros((*shape, x.shape[-1]), dtype=x.dtype) + return mx.concatenate([x, new_x], axis=-2) + + if self.keys is not None: + if prev % self.step != 0: + self.keys, self.values = tree_map( + lambda x: x[..., :prev, :], (self.keys, self.values) + ) + + self.keys, self.values = tree_map( + expand_quant, (self.keys, self.values) + ) + else: + self.keys, self.values = init_quant(k_head_dim), init_quant(v_head_dim) + + self.offset += num_steps + + keys = mx.quantize(keys, group_size=self.group_size, bits=self.bits) + values = mx.quantize(values, group_size=self.group_size, bits=self.bits) + for i in range(len(self.keys)): + self.keys[i][..., prev : self.offset, :] = keys[i] + self.values[i][..., prev : self.offset, :] = values[i] + + return tree_map(lambda x: x[..., : self.offset, :], (self.keys, self.values)) + + @property + def state(self): + if self.offset == self.keys[0].shape[2]: + return self.keys, self.values + else: + return tree_map( + lambda x: x[..., : self.offset, :], (self.keys, self.values) + ) + + @state.setter + def state(self, v): + self.keys, self.values = v + + @property + def meta_state(self): + return tuple(map(str, (self.step, self.offset, self.group_size, self.bits))) + + @meta_state.setter + def meta_state(self, v): + self.step, self.offset, self.group_size, self.bits = map(int, v) + + def is_trimmable(self): + return True + + def trim(self, n): + n = min(self.offset, n) + self.offset -= n + return n + + class KVCache(_BaseCache): def __init__(self): self.keys = None @@ -180,6 +265,16 @@ class KVCache(_BaseCache): self.offset -= n return n + def to_quantized(self, group_size: int = 64, bits: int = 4) -> QuantizedKVCache: + quant_cache = QuantizedKVCache(group_size=group_size, bits=bits) + quant_cache.offset = self.offset + if self.keys is not None: + quant_cache.keys = mx.quantize(self.keys, group_size=group_size, bits=bits) + quant_cache.values = mx.quantize( + self.values, group_size=group_size, bits=bits + ) + return quant_cache + class RotatingKVCache(_BaseCache): @@ -320,6 +415,9 @@ class RotatingKVCache(_BaseCache): self._idx -= n return n + def to_quantized(self, group_size: int = 64, bits: int = 4) -> QuantizedKVCache: + raise NotImplementedError("RotatingKVCache Quantization NYI") + class MambaCache(_BaseCache): def __init__(self): diff --git a/llms/mlx_lm/models/cohere.py b/llms/mlx_lm/models/cohere.py index 057c816d..7e002b0c 100644 --- a/llms/mlx_lm/models/cohere.py +++ b/llms/mlx_lm/models/cohere.py @@ -6,7 +6,7 @@ from typing import Any, Optional, Tuple import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, create_attention_mask +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention @dataclass @@ -93,8 +93,8 @@ class Attention(nn.Module): queries = self.rope(queries) keys = self.rope(keys) - output = mx.fast.scaled_dot_product_attention( - queries, keys, values, scale=self.scale, mask=mask + output = scaled_dot_product_attention( + queries, keys, values, cache=cache, scale=self.scale, mask=mask ) output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) diff --git a/llms/mlx_lm/models/dbrx.py b/llms/mlx_lm/models/dbrx.py index 3b7e83d7..7be274cc 100644 --- a/llms/mlx_lm/models/dbrx.py +++ b/llms/mlx_lm/models/dbrx.py @@ -7,7 +7,7 @@ import mlx.core as mx import mlx.nn as nn import numpy as np -from .base import BaseModelArgs, create_attention_mask +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention @dataclass @@ -74,8 +74,8 @@ class Attention(nn.Module): queries = self.rope(queries) keys = self.rope(keys) - output = mx.fast.scaled_dot_product_attention( - queries, keys, values, scale=self.scale, mask=mask + output = scaled_dot_product_attention( + queries, keys, values, cache=cache, scale=self.scale, mask=mask ) output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) return self.out_proj(output) diff --git a/llms/mlx_lm/models/deepseek.py b/llms/mlx_lm/models/deepseek.py index 03cb3b1a..b7b24dba 100644 --- a/llms/mlx_lm/models/deepseek.py +++ b/llms/mlx_lm/models/deepseek.py @@ -4,7 +4,7 @@ from typing import Any, Dict, Optional import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, create_attention_mask +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention from .switch_layers import SwitchGLU @@ -97,8 +97,8 @@ class DeepseekAttention(nn.Module): queries = self.rope(queries) keys = self.rope(keys) - output = mx.fast.scaled_dot_product_attention( - queries, keys, values, scale=self.scale, mask=mask + output = scaled_dot_product_attention( + queries, keys, values, cache=cache, scale=self.scale, mask=mask ) output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) return self.o_proj(output) diff --git a/llms/mlx_lm/models/deepseek_v2.py b/llms/mlx_lm/models/deepseek_v2.py index bb3e5184..444813b9 100644 --- a/llms/mlx_lm/models/deepseek_v2.py +++ b/llms/mlx_lm/models/deepseek_v2.py @@ -7,7 +7,7 @@ from typing import Any, Dict, Optional, Tuple import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, create_attention_mask +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention from .switch_layers import SwitchGLU @@ -235,8 +235,8 @@ class DeepseekV2Attention(nn.Module): queries = mx.concatenate([q_nope, q_pe], axis=-1) - output = mx.fast.scaled_dot_product_attention( - queries, keys, values, scale=self.scale, mask=mask + output = scaled_dot_product_attention( + queries, keys, values, cache=cache, scale=self.scale, mask=mask ) output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) return self.o_proj(output) diff --git a/llms/mlx_lm/models/gemma.py b/llms/mlx_lm/models/gemma.py index 61de781e..3f384c3f 100644 --- a/llms/mlx_lm/models/gemma.py +++ b/llms/mlx_lm/models/gemma.py @@ -6,7 +6,7 @@ from typing import Any, Optional, Tuple import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, create_attention_mask +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention @dataclass @@ -79,8 +79,8 @@ class Attention(nn.Module): queries = self.rope(queries) keys = self.rope(keys) - output = mx.fast.scaled_dot_product_attention( - queries, keys, values, scale=self.scale, mask=mask + output = scaled_dot_product_attention( + queries, keys, values, cache=cache, scale=self.scale, mask=mask ) output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) diff --git a/llms/mlx_lm/models/gpt2.py b/llms/mlx_lm/models/gpt2.py index 97d9a8ff..52076a34 100644 --- a/llms/mlx_lm/models/gpt2.py +++ b/llms/mlx_lm/models/gpt2.py @@ -7,7 +7,7 @@ import mlx.core as mx import mlx.nn as nn import numpy as np -from .base import BaseModelArgs, create_attention_mask +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention @dataclass @@ -61,8 +61,8 @@ class Attention(nn.Module): if cache is not None: keys, values = cache.update_and_fetch(keys, values) - output = mx.fast.scaled_dot_product_attention( - queries, keys, values, scale=self.scale, mask=mask + output = scaled_dot_product_attention( + queries, keys, values, cache=cache, scale=self.scale, mask=mask ) output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) diff --git a/llms/mlx_lm/models/gpt_bigcode.py b/llms/mlx_lm/models/gpt_bigcode.py index 068046ea..23e86e20 100644 --- a/llms/mlx_lm/models/gpt_bigcode.py +++ b/llms/mlx_lm/models/gpt_bigcode.py @@ -7,7 +7,7 @@ import mlx.core as mx import mlx.nn as nn import numpy as np -from .base import BaseModelArgs, create_attention_mask +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention @dataclass @@ -74,8 +74,8 @@ class Attention(nn.Module): if cache is not None: keys, values = cache.update_and_fetch(keys, values) - output = mx.fast.scaled_dot_product_attention( - queries, keys, values, scale=self.scale, mask=mask + output = scaled_dot_product_attention( + queries, keys, values, cache=cache, scale=self.scale, mask=mask ) output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) return self.c_proj(output) diff --git a/llms/mlx_lm/models/gpt_neox.py b/llms/mlx_lm/models/gpt_neox.py index 9f662491..ccb0b28b 100644 --- a/llms/mlx_lm/models/gpt_neox.py +++ b/llms/mlx_lm/models/gpt_neox.py @@ -7,7 +7,7 @@ import mlx.core as mx import mlx.nn as nn import numpy as np -from .base import BaseModelArgs, create_attention_mask +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention # Based on the transformers implementation at: # https://github.com/huggingface/transformers/blob/main/src/transformers/models/gpt_neox/modeling_gpt_neox.py @@ -79,8 +79,8 @@ class Attention(nn.Module): queries = self.rope(queries) keys = self.rope(keys) - output = mx.fast.scaled_dot_product_attention( - queries, keys, values, scale=self.scale, mask=mask + output = scaled_dot_product_attention( + queries, keys, values, cache=cache, scale=self.scale, mask=mask ) output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) diff --git a/llms/mlx_lm/models/internlm2.py b/llms/mlx_lm/models/internlm2.py index 5264cb57..f5ce057e 100644 --- a/llms/mlx_lm/models/internlm2.py +++ b/llms/mlx_lm/models/internlm2.py @@ -6,7 +6,7 @@ from typing import Any, Dict, Optional, Tuple, Union import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, create_attention_mask +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention @dataclass @@ -141,8 +141,8 @@ class Attention(nn.Module): queries = self.rope(queries) keys = self.rope(keys) - output = mx.fast.scaled_dot_product_attention( - queries, keys, values, scale=self.scale, mask=mask + output = scaled_dot_product_attention( + queries, keys, values, cache=cache, scale=self.scale, mask=mask ) output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) return self.wo(output) diff --git a/llms/mlx_lm/models/llama.py b/llms/mlx_lm/models/llama.py index 7da6b333..438278e5 100644 --- a/llms/mlx_lm/models/llama.py +++ b/llms/mlx_lm/models/llama.py @@ -1,12 +1,12 @@ # Copyright © 2023-2024 Apple Inc. from dataclasses import dataclass -from typing import Any, Dict, Optional, Tuple, Union +from typing import Any, Dict, Optional, Union import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, create_attention_mask +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention @dataclass @@ -190,9 +190,10 @@ class Attention(nn.Module): queries = self.rope(queries) keys = self.rope(keys) - output = mx.fast.scaled_dot_product_attention( - queries, keys, values, scale=self.scale, mask=mask + output = scaled_dot_product_attention( + queries, keys, values, cache=cache, scale=self.scale, mask=mask ) + output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) return self.o_proj(output) diff --git a/llms/mlx_lm/models/minicpm.py b/llms/mlx_lm/models/minicpm.py index 4ac3c3b4..907beb2a 100644 --- a/llms/mlx_lm/models/minicpm.py +++ b/llms/mlx_lm/models/minicpm.py @@ -7,7 +7,7 @@ import mlx.core as mx import mlx.nn as nn import numpy as np -from .base import BaseModelArgs, create_attention_mask +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention @dataclass @@ -105,8 +105,8 @@ class Attention(nn.Module): queries = self.rope(queries) keys = self.rope(keys) - attn_output = mx.fast.scaled_dot_product_attention( - queries, keys, values, scale=self.scale, mask=mask + attn_output = scaled_dot_product_attention( + queries, keys, values, cache=cache, scale=self.scale, mask=mask ) attn_output = attn_output.transpose(0, 2, 1, 3).reshape(B, L, -1) diff --git a/llms/mlx_lm/models/mixtral.py b/llms/mlx_lm/models/mixtral.py index 20944fe3..dd94d1f4 100644 --- a/llms/mlx_lm/models/mixtral.py +++ b/llms/mlx_lm/models/mixtral.py @@ -7,7 +7,7 @@ from typing import Any, Dict, Optional, Tuple, Union import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, create_attention_mask +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention from .switch_layers import SwitchGLU @@ -87,8 +87,8 @@ class MixtralAttention(nn.Module): queries = self.rope(queries) keys = self.rope(keys) - output = mx.fast.scaled_dot_product_attention( - queries, keys, values, scale=self.scale, mask=mask + output = scaled_dot_product_attention( + queries, keys, values, cache=cache, scale=self.scale, mask=mask ) output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) return self.o_proj(output) diff --git a/llms/mlx_lm/models/nemotron.py b/llms/mlx_lm/models/nemotron.py index 3ea06e27..f73c0277 100644 --- a/llms/mlx_lm/models/nemotron.py +++ b/llms/mlx_lm/models/nemotron.py @@ -7,7 +7,7 @@ from typing import Any, Dict, Optional, Union import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, create_attention_mask +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention @dataclass @@ -113,8 +113,8 @@ class Attention(nn.Module): queries = self.rope(queries) keys = self.rope(keys) - output = mx.fast.scaled_dot_product_attention( - queries, keys, values, scale=self.scale, mask=mask + output = scaled_dot_product_attention( + queries, keys, values, cache=cache, scale=self.scale, mask=mask ) output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) return self.o_proj(output) diff --git a/llms/mlx_lm/models/openelm.py b/llms/mlx_lm/models/openelm.py index 090e21c6..408802f4 100644 --- a/llms/mlx_lm/models/openelm.py +++ b/llms/mlx_lm/models/openelm.py @@ -6,7 +6,7 @@ from typing import Any, Dict, List, Optional, Tuple, Union import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, create_attention_mask +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention @dataclass @@ -107,8 +107,8 @@ class Attention(nn.Module): queries = self.rope(queries) keys = self.rope(keys) - output = mx.fast.scaled_dot_product_attention( - queries, keys, values, scale=self.scale, mask=mask + output = scaled_dot_product_attention( + queries, keys, values, cache=cache, scale=self.scale, mask=mask ) output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) diff --git a/llms/mlx_lm/models/phi.py b/llms/mlx_lm/models/phi.py index 56b383b2..510025ea 100644 --- a/llms/mlx_lm/models/phi.py +++ b/llms/mlx_lm/models/phi.py @@ -7,7 +7,7 @@ from typing import Tuple import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, create_attention_mask +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention @dataclass @@ -93,8 +93,13 @@ class PhiAttention(nn.Module): keys = self.rope(keys) scale = math.sqrt(1 / queries.shape[-1]) - output = mx.fast.scaled_dot_product_attention( - queries.astype(mx.float32), keys, values, scale=scale, mask=mask + output = scaled_dot_product_attention( + queries.astype(mx.float32), + keys, + values, + cache=cache, + scale=scale, + mask=mask, ).astype(values.dtype) output = output.moveaxis(2, 1).reshape(B, L, -1) diff --git a/llms/mlx_lm/models/phi3.py b/llms/mlx_lm/models/phi3.py index 9ef76f04..ee6efc49 100644 --- a/llms/mlx_lm/models/phi3.py +++ b/llms/mlx_lm/models/phi3.py @@ -6,7 +6,7 @@ from typing import Any, Dict, List, Optional, Tuple, Union import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, create_attention_mask +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention from .su_rope import SuScaledRotaryEmbedding @@ -107,8 +107,8 @@ class Attention(nn.Module): queries = self.rope(queries) keys = self.rope(keys) - output = mx.fast.scaled_dot_product_attention( - queries, keys, values, scale=self.scale, mask=mask + output = scaled_dot_product_attention( + queries, keys, values, cache=cache, scale=self.scale, mask=mask ) output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) return self.o_proj(output) diff --git a/llms/mlx_lm/models/phi3small.py b/llms/mlx_lm/models/phi3small.py index 6b0759b4..53e1a638 100644 --- a/llms/mlx_lm/models/phi3small.py +++ b/llms/mlx_lm/models/phi3small.py @@ -8,7 +8,7 @@ from typing import Any, Optional import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, create_attention_mask +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention @dataclass @@ -188,8 +188,8 @@ class Attention(nn.Module): queries, keys, values, scale=self.scale, mask=mask ) else: - output = mx.fast.scaled_dot_product_attention( - queries, keys, values, scale=self.scale, mask=mask + output = scaled_dot_product_attention( + queries, keys, values, cache=cache, scale=self.scale, mask=mask ) output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) return self.dense(output) diff --git a/llms/mlx_lm/models/phimoe.py b/llms/mlx_lm/models/phimoe.py index ca20a388..f42a6dd0 100644 --- a/llms/mlx_lm/models/phimoe.py +++ b/llms/mlx_lm/models/phimoe.py @@ -6,7 +6,7 @@ from typing import Dict, List, Optional, Union import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, create_attention_mask +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention from .su_rope import SuScaledRotaryEmbedding from .switch_layers import SwitchGLU @@ -79,8 +79,8 @@ class Attention(nn.Module): queries = self.rope(queries) keys = self.rope(keys) - output = mx.fast.scaled_dot_product_attention( - queries, keys, values, scale=self.scale, mask=mask + output = scaled_dot_product_attention( + queries, keys, values, cache=cache, scale=self.scale, mask=mask ) output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) return self.o_proj(output) diff --git a/llms/mlx_lm/models/phixtral.py b/llms/mlx_lm/models/phixtral.py index 865d0d8e..42d647b0 100644 --- a/llms/mlx_lm/models/phixtral.py +++ b/llms/mlx_lm/models/phixtral.py @@ -8,7 +8,7 @@ from typing import Tuple import mlx.core as mx import mlx.nn as nn -from .base import create_attention_mask +from .base import create_attention_mask, scaled_dot_product_attention from .switch_layers import SwitchMLP @@ -71,8 +71,13 @@ class RoPEAttention(nn.Module): # Finally perform the attention computation scale = math.sqrt(1 / queries.shape[-1]) - output = mx.fast.scaled_dot_product_attention( - queries.astype(mx.float32), keys, values, scale=scale, mask=mask + output = scaled_dot_product_attention( + queries.astype(mx.float32), + keys, + values, + cache=cache, + scale=scale, + mask=mask, ).astype(values.dtype) output = output.moveaxis(2, 1).reshape(B, L, -1) diff --git a/llms/mlx_lm/models/plamo.py b/llms/mlx_lm/models/plamo.py index b0fd1a6c..c8e5bf50 100644 --- a/llms/mlx_lm/models/plamo.py +++ b/llms/mlx_lm/models/plamo.py @@ -7,7 +7,7 @@ import mlx.core as mx import mlx.nn as nn import numpy as np -from .base import BaseModelArgs, create_attention_mask +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention @dataclass @@ -92,10 +92,11 @@ class Attention(nn.Module): keys = mx.tile(keys, [1, self.config.n_shared_head, 1, 1]) values = mx.tile(values, [1, self.config.n_shared_head, 1, 1]) - output = mx.fast.scaled_dot_product_attention( + output = scaled_dot_product_attention( queries, keys, values, + cache=cache, scale=self.scale, mask=attention_mask, ) diff --git a/llms/mlx_lm/models/qwen.py b/llms/mlx_lm/models/qwen.py index 2b69d5ec..8145a890 100644 --- a/llms/mlx_lm/models/qwen.py +++ b/llms/mlx_lm/models/qwen.py @@ -5,7 +5,7 @@ from dataclasses import dataclass import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, create_attention_mask +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention @dataclass @@ -64,8 +64,8 @@ class Attention(nn.Module): queries = self.rotary_emb(queries) keys = self.rotary_emb(keys) - output = mx.fast.scaled_dot_product_attention( - queries, keys, values, scale=self.scale, mask=mask + output = scaled_dot_product_attention( + queries, keys, values, cache=cache, scale=self.scale, mask=mask ) output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) diff --git a/llms/mlx_lm/models/qwen2.py b/llms/mlx_lm/models/qwen2.py index 4e7858de..fac59d78 100644 --- a/llms/mlx_lm/models/qwen2.py +++ b/llms/mlx_lm/models/qwen2.py @@ -6,7 +6,7 @@ from typing import Any, Dict, Optional, Union import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, create_attention_mask +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention @dataclass @@ -89,8 +89,8 @@ class Attention(nn.Module): queries = self.rope(queries) keys = self.rope(keys) - output = mx.fast.scaled_dot_product_attention( - queries, keys, values, scale=self.scale, mask=mask + output = scaled_dot_product_attention( + queries, keys, values, cache=cache, scale=self.scale, mask=mask ) output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) return self.o_proj(output) diff --git a/llms/mlx_lm/models/qwen2_moe.py b/llms/mlx_lm/models/qwen2_moe.py index d199116f..167fc5dd 100644 --- a/llms/mlx_lm/models/qwen2_moe.py +++ b/llms/mlx_lm/models/qwen2_moe.py @@ -7,7 +7,7 @@ from typing import Any, Dict, Optional, Union import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, create_attention_mask +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention from .switch_layers import SwitchGLU @@ -89,8 +89,8 @@ class Attention(nn.Module): queries = self.rope(queries) keys = self.rope(keys) - output = mx.fast.scaled_dot_product_attention( - queries, keys, values, scale=self.scale, mask=mask + output = scaled_dot_product_attention( + queries, keys, values, cache=cache, scale=self.scale, mask=mask ) output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) return self.o_proj(output) diff --git a/llms/mlx_lm/models/recurrent_gemma.py b/llms/mlx_lm/models/recurrent_gemma.py index 5595d311..49e4bb8f 100644 --- a/llms/mlx_lm/models/recurrent_gemma.py +++ b/llms/mlx_lm/models/recurrent_gemma.py @@ -7,7 +7,7 @@ from typing import List, Literal, Optional import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, create_attention_mask +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention from .cache import MambaCache, RotatingKVCache @@ -263,8 +263,8 @@ class LocalAttentionBlock(nn.Module): queries = self.rope(queries) keys = self.rope(keys) - output = mx.fast.scaled_dot_product_attention( - queries, keys, values, scale=self.scale, mask=mask + output = scaled_dot_product_attention( + queries, keys, values, cache=cache, scale=self.scale, mask=mask ) output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) return self.o_proj(output) diff --git a/llms/mlx_lm/models/stablelm.py b/llms/mlx_lm/models/stablelm.py index 11202b02..482bb324 100644 --- a/llms/mlx_lm/models/stablelm.py +++ b/llms/mlx_lm/models/stablelm.py @@ -6,7 +6,7 @@ from dataclasses import dataclass import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, create_attention_mask +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention @dataclass @@ -120,8 +120,8 @@ class Attention(nn.Module): # Finally perform the attention computation scale = math.sqrt(1 / queries.shape[-1]) - output = mx.fast.scaled_dot_product_attention( - queries, keys, values, scale=scale, mask=mask + output = scaled_dot_product_attention( + queries, keys, values, cache=cache, scale=scale, mask=mask ).astype(values.dtype) output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) return self.o_proj(output) diff --git a/llms/mlx_lm/models/starcoder2.py b/llms/mlx_lm/models/starcoder2.py index ce0a2ec5..d7e626f2 100644 --- a/llms/mlx_lm/models/starcoder2.py +++ b/llms/mlx_lm/models/starcoder2.py @@ -6,7 +6,7 @@ from typing import Any, Optional import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, create_attention_mask +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention @dataclass @@ -64,8 +64,8 @@ class Attention(nn.Module): queries = self.rope(queries) keys = self.rope(keys) - output = mx.fast.scaled_dot_product_attention( - queries, keys, values, scale=self.scale, mask=mask + output = scaled_dot_product_attention( + queries, keys, values, cache=cache, scale=self.scale, mask=mask ) output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index 5b437c98..06784f10 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -19,7 +19,7 @@ from mlx.utils import tree_flatten, tree_reduce from transformers import PreTrainedTokenizer # Local imports -from .models import base, cache +from .models import cache from .sample_utils import categorical_sampling, min_p_sampling, top_p_sampling from .tokenizer_utils import TokenizerWrapper, load_tokenizer from .tuner.utils import dequantize as dequantize_model @@ -159,6 +159,18 @@ def apply_repetition_penalty(logits: mx.array, tokens: mx.array, penalty: float) return logits +def maybe_quantize_kv_cache(prompt_cache, quantized_kv_start, kv_group_size, kv_bits): + if ( + kv_bits is not None + and not isinstance(prompt_cache[0], cache.QuantizedKVCache) + and prompt_cache[0].offset > quantized_kv_start + ): + for i in range(len(prompt_cache)): + prompt_cache[i] = prompt_cache[i].to_quantized( + group_size=kv_group_size, bits=kv_bits + ) + + def generate_step( prompt: mx.array, model: nn.Module, @@ -173,6 +185,9 @@ def generate_step( prompt_cache: Optional[Any] = None, logit_bias: Optional[Dict[int, float]] = None, logits_processor: Optional[List[Callable[[mx.array, mx.array], mx.array]]] = None, + kv_bits: Optional[int] = None, + kv_group_size: int = 64, + quantized_kv_start: int = 0, ) -> Generator[Tuple[mx.array, mx.array], None, None]: """ A generator producing token ids based on the given prompt from the model. @@ -201,6 +216,11 @@ def generate_step( logits_processor (List[Callable[[mx.array, mx.array], mx.array]], optional): A list of functions that take tokens and logits and return the processed logits. Default: ``None``. + kv_bits (int, optional): Number of bits to use for KV cache quantization. + None implies no cache quantization. Default: ``None``. + kv_group_size (int): Group size for KV cache quantization. Default: ``64``. + quantized_kv_start (int): Step to begin using a quantized KV cache. + when ``kv_bits`` is non-None. Default: ``0``. Yields: Generator[Tuple[mx.array, mx.array], None, None]: A generator producing @@ -255,11 +275,15 @@ def generate_step( # Create the KV cache for generation if prompt_cache is None: - prompt_cache = cache.make_prompt_cache(model, max_kv_size) + prompt_cache = cache.make_prompt_cache( + model, + max_kv_size=max_kv_size, + ) elif len(prompt_cache) != len(model.layers): raise ValueError("Wrong number of layers in the prompt cache.") def _step(y): + logits = model(y[None], cache=prompt_cache) logits = logits[:, -1, :] @@ -270,6 +294,10 @@ def generate_step( for processor in logits_processor: logits = processor(tokens, logits) + maybe_quantize_kv_cache( + prompt_cache, quantized_kv_start, kv_group_size, kv_bits + ) + y, logprobs = sample(logits) return y, logprobs.squeeze(0) diff --git a/llms/tests/test_prompt_cache.py b/llms/tests/test_prompt_cache.py index 64cd9486..1e57bd86 100644 --- a/llms/tests/test_prompt_cache.py +++ b/llms/tests/test_prompt_cache.py @@ -9,6 +9,7 @@ import mlx.core as mx from mlx_lm.models.cache import ( KVCache, MambaCache, + QuantizedKVCache, RotatingKVCache, load_prompt_cache, make_prompt_cache, @@ -186,6 +187,18 @@ class TestPromptCache(unittest.TestCase): num_trimmed = trim_prompt_cache(cache, 4) self.assertEqual(num_trimmed, 0) + cache = [QuantizedKVCache() for _ in range(2)] + for c in cache: + x = mx.random.uniform(shape=(1, 8, 10, 64)) + c.update_and_fetch(x, x) + + num_trimmed = trim_prompt_cache(cache, 7) + self.assertEqual(num_trimmed, 7) + + # Trim more tokens than remain + num_trimmed = trim_prompt_cache(cache, 4) + self.assertEqual(num_trimmed, 3) + def test_trim_cache_with_generate(self): model, tokenizer = load(HF_MODEL_PATH) prompt = tokenizer.encode("this is a prompt", return_tensors="mlx")[0] @@ -238,6 +251,56 @@ class TestPromptCache(unittest.TestCase): self.assertTrue(mx.allclose(old_cache[0].keys[..., 10:11, :], y)) self.assertTrue(mx.allclose(cache[0].keys[..., 10:11, :], z)) + def test_save_load_quantized_cache(self): + cache = [QuantizedKVCache(bits=4, group_size=32) for _ in range(4)] + for c in cache: + x = mx.random.uniform(shape=(1, 8, 10, 32)) + c.update_and_fetch(x, x) + cache_file = os.path.join(self.test_dir, "prompt_cache.safetensors") + save_prompt_cache(cache_file, cache) + loaded_cache = load_prompt_cache(cache_file) + self.assertTrue(loaded_cache[0].bits == cache[0].bits) + self.assertTrue(loaded_cache[0].group_size == cache[0].group_size) + self.assertTrue(len(cache), len(loaded_cache)) + for c, lc in zip(cache, loaded_cache): + self.assertEqual(c.offset, lc.offset) + # Loop over quantized tuple + for i in range(3): + self.assertTrue(mx.array_equal(c.state[0][i], lc.state[0][i])) + self.assertTrue(mx.array_equal(c.state[1][i], lc.state[1][i])) + + # Test with metadata + cache_file = os.path.join(self.test_dir, "prompt_cache.safetensors") + metadata = {"a": "b", "c": "d"} + save_prompt_cache(cache_file, cache, metadata) + _, loaded_metadata = load_prompt_cache(cache_file, return_metadata=True) + self.assertEqual(metadata, loaded_metadata) + + def test_cache_to_quantized(self): + model, tokenizer = load(HF_MODEL_PATH) + prompt = tokenizer.encode("this is a prompt", return_tensors="mlx")[0] + results = zip(range(4), generate_step(prompt, model)) + toks, all_logits = zip(*(r[1] for r in results)) + + prompt_cache = make_prompt_cache(model) + i = 0 + for _, (tok, logits) in zip( + range(2), generate_step(prompt, model, prompt_cache=prompt_cache) + ): + self.assertEqual(tok, toks[i]) + self.assertTrue(mx.allclose(logits, all_logits[i])) + i += 1 + + prompt_cache = [c.to_quantized(bits=8, group_size=32) for c in prompt_cache] + + for _, (tok, logits) in zip( + range(1), + generate_step(mx.array([toks[i]]), model, prompt_cache=prompt_cache), + ): + i += 1 + self.assertEqual(tok, toks[i]) + self.assertTrue(mx.allclose(logits, all_logits[i], rtol=1e-2)) + if __name__ == "__main__": unittest.main() From 8160e0c4e56df261d0c8406f68d40b42ef0a188b Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Fri, 1 Nov 2024 10:52:28 -0700 Subject: [PATCH 067/188] Whisper improvements (#1080) * use safetensors in whisper * speed up decoder * version --- whisper/convert.py | 4 +- whisper/mlx_whisper/_version.py | 2 +- whisper/mlx_whisper/decoding.py | 132 ++++++++++++++++------------- whisper/mlx_whisper/load_models.py | 5 +- whisper/mlx_whisper/transcribe.py | 1 + whisper/mlx_whisper/whisper.py | 5 +- 6 files changed, 85 insertions(+), 64 deletions(-) diff --git a/whisper/convert.py b/whisper/convert.py index cdd50bc5..301fd5b4 100644 --- a/whisper/convert.py +++ b/whisper/convert.py @@ -181,7 +181,7 @@ def load_torch_weights_and_config( ) if name_or_path.endswith(".pt"): - checkpoint = torch.load(name_or_path, map_location="cpu") + checkpoint = torch.load(name_or_path, map_location="cpu", weights_only=False) weights, config = checkpoint["model_state_dict"], checkpoint["dims"] else: name_or_path = Path(name_or_path) @@ -387,7 +387,7 @@ if __name__ == "__main__": # Save weights print("[INFO] Saving") - np.savez(str(mlx_path / "weights.npz"), **weights) + mx.save_safetensors(str(mlx_path / "weights.safetensors"), weights) # Save config.json with model_type with open(str(mlx_path / "config.json"), "w") as f: diff --git a/whisper/mlx_whisper/_version.py b/whisper/mlx_whisper/_version.py index 67c7397c..45e522d1 100644 --- a/whisper/mlx_whisper/_version.py +++ b/whisper/mlx_whisper/_version.py @@ -1,3 +1,3 @@ # Copyright © 2023-2024 Apple Inc. -__version__ = "0.3.0" +__version__ = "0.4.0" diff --git a/whisper/mlx_whisper/decoding.py b/whisper/mlx_whisper/decoding.py index 41c2ec6d..6bf975d5 100644 --- a/whisper/mlx_whisper/decoding.py +++ b/whisper/mlx_whisper/decoding.py @@ -58,11 +58,12 @@ def detect_language( logits = model.logits(x, mel)[:, 0] # collect detected languages; suppress all non-language tokens - mask = np.full(logits.shape[-1], -np.inf, dtype=np.float32) + mask = mx.full(logits.shape[-1], -mx.inf, dtype=mx.float32) mask[list(tokenizer.all_language_tokens)] = 0.0 - logits += mx.array(mask) + logits += mask language_tokens = mx.argmax(logits, axis=-1) language_token_probs = mx.softmax(logits, axis=-1) + language_token_probs = np.array(language_token_probs) language_probs = [ { c: language_token_probs[i, j].item() @@ -129,17 +130,12 @@ class DecodingResult: class Inference: - def __init__(self, model: "Whisper", initial_token_length: int): + def __init__(self, model: "Whisper"): self.model: "Whisper" = model - self.initial_token_length = initial_token_length self.kv_cache = None def logits(self, tokens: mx.array, audio_features: mx.array) -> mx.array: """Perform a forward pass on the decoder and return per-token logits""" - if tokens.shape[-1] > self.initial_token_length: - # only need to use the last token except in the first forward pass - tokens = tokens[:, -1:] - logits, self.kv_cache, _ = self.model.decoder( tokens, audio_features, kv_cache=self.kv_cache ) @@ -251,6 +247,11 @@ class TokenDecoder: raise NotImplementedError +@mx.compile +def categorical(logits, temp): + return mx.random.categorical(logits / temp) + + class GreedyDecoder(TokenDecoder): def __init__(self, temperature: float, eot: int): self.temperature = temperature @@ -262,10 +263,8 @@ class GreedyDecoder(TokenDecoder): if self.temperature == 0: next_tokens = logits.argmax(axis=-1) else: - next_tokens = mx.random.categorical(logits=logits / self.temperature) + next_tokens = categorical(logits, self.temperature) - next_tokens = mx.argmax(logits, axis=-1) - logits = logits.astype(mx.float32) logprobs = logits - mx.logsumexp(logits, axis=-1) current_logprobs = logprobs[mx.arange(logprobs.shape[0]), next_tokens] @@ -281,7 +280,7 @@ class GreedyDecoder(TokenDecoder): def finalize(self, tokens: mx.array, sum_logprobs: mx.array): # make sure each sequence has at least one EOT token at the end tokens = mx.pad(tokens, [(0, 0), (0, 0), (0, 1)], constant_values=self.eot) - return tokens, sum_logprobs.tolist() + return tokens, sum_logprobs class LogitFilter: @@ -340,10 +339,10 @@ class ApplyTimestampRules(LogitFilter): if self.tokenizer.no_timestamps is not None: mask[:, self.tokenizer.no_timestamps] = -np.inf - # timestamps have to appear in pairs, except directly before EOT; mask logits accordingly - for k in range(tokens.shape[0]): - sampled_tokens = tokens[k, self.sample_begin :] - seq = sampled_tokens.tolist() + ## timestamps have to appear in pairs, except directly before EOT; mask logits accordingly + tokens = tokens.tolist() + for k in range(len(tokens)): + seq = tokens[k][self.sample_begin :] last_was_timestamp = ( len(seq) >= 1 and seq[-1] >= self.tokenizer.timestamp_begin ) @@ -368,7 +367,7 @@ class ApplyTimestampRules(LogitFilter): last_timestamp += 1 mask[k, self.tokenizer.timestamp_begin : last_timestamp] = -np.inf - if tokens.shape[1] == self.sample_begin: + if len(tokens[0]) == self.sample_begin: # suppress generating non-timestamp tokens at the beginning mask[:, : self.tokenizer.timestamp_begin] = -np.inf @@ -380,16 +379,20 @@ class ApplyTimestampRules(LogitFilter): mask[:, last_allowed + 1 :] = -np.inf # if sum of probability over timestamps is above any other token, sample timestamp + mask = mx.array(mask) logprobs = logits - mx.logsumexp(logits, axis=-1) - for k in range(tokens.shape[0]): - timestamp_logprob = logprobs[k, self.tokenizer.timestamp_begin :].logsumexp( - axis=-1 - ) - max_text_token_logprob = logprobs[k, : self.tokenizer.timestamp_begin].max() - if timestamp_logprob > max_text_token_logprob: - mask[k, : self.tokenizer.timestamp_begin] = -np.inf - - return logits + mx.array(mask, logits.dtype) + timestamp_logprob = logprobs[:, self.tokenizer.timestamp_begin :].logsumexp( + axis=-1, keepdims=True + ) + max_text_token_logprob = logprobs[:, : self.tokenizer.timestamp_begin].max( + axis=-1, keepdims=True + ) + mask[:, : self.tokenizer.timestamp_begin] = mx.where( + timestamp_logprob > max_text_token_logprob, + -mx.inf, + mask[:, : self.tokenizer.timestamp_begin], + ) + return logits + mask class DecodingTask: @@ -424,7 +427,7 @@ class DecodingTask: self.sot_index: int = self.initial_tokens.index(tokenizer.sot) # inference: implements the forward pass through the decoder, including kv caching - self.inference = Inference(model, len(self.initial_tokens)) + self.inference = Inference(model) # sequence ranker: implements how to rank a group of sampled sequences self.sequence_ranker = MaximumLikelihoodRanker(options.length_penalty) @@ -432,9 +435,6 @@ class DecodingTask: # decoder: implements how to select the next tokens, given the autoregressive distribution if options.beam_size is not None: raise NotImplementedError("Beam search decoder is not yet implemented") - # self.decoder = BeamSearchDecoder( - # options.beam_size, tokenizer.eot, self.inference, options.patience - # ) else: self.decoder = GreedyDecoder(options.temperature, tokenizer.eot) @@ -448,6 +448,7 @@ class DecodingTask: self.logit_filters.append( SuppressTokens(self._get_suppress_tokens(), model.dims.n_vocab) ) + if not options.without_timestamps: precision = CHUNK_LENGTH / model.dims.n_audio_ctx # usually 0.02 seconds max_initial_timestamp_index = None @@ -570,35 +571,47 @@ class DecodingTask: def _main_loop(self, audio_features: mx.array, tokens: mx.array): n_batch = tokens.shape[0] - sum_logprobs: mx.array = mx.zeros(n_batch) - no_speech_probs = [np.nan] * n_batch + sum_logprobs = mx.zeros(n_batch) + + def _step(inputs, audio_features, tokens, sum_logprobs): + pre_logits = self.inference.logits(inputs, audio_features) + + # consider the logits at the last token only + logits = pre_logits[:, -1] + + # apply the logit filters, e.g. for suppressing or applying penalty to + for logit_filter in self.logit_filters: + logits = logit_filter.apply(logits, tokens) + + # expand the tokens tensor with the selected next tokens + tokens, completed, sum_logprobs = self.decoder.update( + tokens, logits, sum_logprobs + ) + return tokens, completed, sum_logprobs, pre_logits try: - for i in range(self.sample_len): - logits = self.inference.logits(tokens, audio_features) + tokens, completed, sum_logprobs, pre_logits = _step( + tokens, audio_features, tokens, sum_logprobs + ) + if self.tokenizer.no_speech is not None: # compute no_speech_probs + probs_at_sot = mx.softmax(pre_logits[:, self.sot_index], axis=-1) + no_speech_probs = probs_at_sot[:, self.tokenizer.no_speech] + else: + no_speech_probs = mx.full(n_batch, mx.nan) + mx.async_eval(completed, tokens, sum_logprobs, no_speech_probs) - if ( - i == 0 and self.tokenizer.no_speech is not None - ): # save no_speech_probs - probs_at_sot = mx.softmax( - logits[:, self.sot_index].astype(mx.float32), axis=-1 - ) - no_speech_probs = probs_at_sot[:, self.tokenizer.no_speech].tolist() - - # now we need to consider the logits at the last token only - logits = logits[:, -1] - - # apply the logit filters, e.g. for suppressing or applying penalty to - for logit_filter in self.logit_filters: - logits = logit_filter.apply(logits, tokens) - - # expand the tokens tensor with the selected next tokens - tokens, completed, sum_logprobs = self.decoder.update( - tokens, logits, sum_logprobs + for i in range(1, self.sample_len): + inputs = tokens[:, -1:] + next_tokens, next_completed, next_sum_logprobs, _ = _step( + inputs, audio_features, tokens, sum_logprobs ) - + mx.async_eval(next_completed, next_tokens, next_sum_logprobs) if completed or tokens.shape[-1] > self.n_ctx: break + tokens = next_tokens + completed = next_completed + sum_logprobs = next_sum_logprobs + finally: self.inference.reset() @@ -610,8 +623,8 @@ class DecodingTask: n_audio: int = mel.shape[0] audio_features: mx.array = self._get_audio_features(mel) # encoder forward pass - tokens: np.array = np.array(self.initial_tokens) - tokens = np.broadcast_to(tokens, (n_audio, len(self.initial_tokens))).copy() + tokens: mx.array = mx.array(self.initial_tokens) + tokens = mx.broadcast_to(tokens, (n_audio, len(self.initial_tokens))) # detect language if requested, overwriting the language token languages, language_probs = self._detect_language(audio_features, tokens) @@ -626,7 +639,6 @@ class DecodingTask: ] # repeat tokens by the group size, for beam search or best-of-n sampling - tokens = mx.array(tokens) if self.n_group > 1: tokens = tokens[:, None, :] tokens = mx.broadcast_to( @@ -649,7 +661,13 @@ class DecodingTask: # get the final candidates for each group, and slice between the first sampled token and EOT tokens, sum_logprobs = self.decoder.finalize(tokens, sum_logprobs) - tokens = tokens[..., self.sample_begin :].tolist() + tokens = tokens[..., self.sample_begin :] + + # eval and convert to list + mx.eval(tokens, sum_logprobs, no_speech_probs) + tokens = tokens.tolist() + sum_logprobs = sum_logprobs.tolist() + no_speech_probs = no_speech_probs.tolist() tokens = [[t[: t.index(tokenizer.eot)] for t in s] for s in tokens] # select the top-ranked sample in each group diff --git a/whisper/mlx_whisper/load_models.py b/whisper/mlx_whisper/load_models.py index 6705385d..60766ab2 100644 --- a/whisper/mlx_whisper/load_models.py +++ b/whisper/mlx_whisper/load_models.py @@ -26,7 +26,10 @@ def load_model( model_args = whisper.ModelDimensions(**config) - weights = mx.load(str(model_path / "weights.npz")) + wf = model_path / "weights.safetensors" + if not wf.exists(): + wf = model_path / "weights.npz" + weights = mx.load(str(wf)) model = whisper.Whisper(model_args, dtype) diff --git a/whisper/mlx_whisper/transcribe.py b/whisper/mlx_whisper/transcribe.py index 786b4232..7057679b 100644 --- a/whisper/mlx_whisper/transcribe.py +++ b/whisper/mlx_whisper/transcribe.py @@ -293,6 +293,7 @@ def transcribe( decode_options["prompt"] = all_tokens[prompt_reset_since:] result: DecodingResult = decode_with_fallback(mel_segment) + tokens = np.array(result.tokens) if no_speech_threshold is not None: diff --git a/whisper/mlx_whisper/whisper.py b/whisper/mlx_whisper/whisper.py index e691792c..1c2b390e 100644 --- a/whisper/mlx_whisper/whisper.py +++ b/whisper/mlx_whisper/whisper.py @@ -80,12 +80,11 @@ class MultiHeadAttention(nn.Module): qk = q @ k if mask is not None: qk = qk + mask[:n_ctx, :n_ctx] - qk = qk.astype(mx.float32) - w = mx.softmax(qk, axis=-1).astype(q.dtype) + w = mx.softmax(qk, axis=-1, precise=True) out = (w @ v).transpose(0, 2, 1, 3) out = out.reshape(n_batch, n_ctx, n_state) - return out, qk + return out, qk.astype(mx.float32) class ResidualAttentionBlock(nn.Module): From e510987870fdc0c9741d8448fe37a776d2ee52a0 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Fri, 1 Nov 2024 14:15:32 -0700 Subject: [PATCH 068/188] Clear cache every now and then (#1081) * clear cache every now and then * don't need user arg anymore --- llms/mlx_lm/generate.py | 9 --------- llms/mlx_lm/utils.py | 4 ++++ 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/llms/mlx_lm/generate.py b/llms/mlx_lm/generate.py index 0355ca29..29976da2 100644 --- a/llms/mlx_lm/generate.py +++ b/llms/mlx_lm/generate.py @@ -90,12 +90,6 @@ def setup_arg_parser(): action="store_true", help="Colorize output based on T[0] probability", ) - parser.add_argument( - "--cache-limit-gb", - type=int, - default=None, - help="Set the MLX cache limit in GB", - ) parser.add_argument( "--max-kv-size", type=int, @@ -164,9 +158,6 @@ def main(): mx.random.seed(args.seed) - if args.cache_limit_gb is not None: - mx.metal.set_cache_limit(args.cache_limit_gb * 1024 * 1024 * 1024) - # Load the prompt cache and metadata if a cache file is provided using_cache = args.prompt_cache_file is not None if using_cache: diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index 06784f10..b9fc202d 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -310,10 +310,14 @@ def generate_step( y, logprobs = _step(y) mx.async_eval(y, logprobs) + n = 0 while True: next_y, next_logprobs = _step(y) mx.async_eval(next_y, next_logprobs) yield y.item(), logprobs + if n % 256 == 0: + mx.metal.clear_cache() + n += 1 y, logprobs = next_y, next_logprobs From 0f799947d0c73ff4901ce17188aceaa933b3c02e Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Fri, 1 Nov 2024 16:30:32 -0700 Subject: [PATCH 069/188] fix (#1079) --- llms/mlx_lm/tokenizer_utils.py | 11 +++++++++-- llms/tests/test_tokenizers.py | 11 +++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/llms/mlx_lm/tokenizer_utils.py b/llms/mlx_lm/tokenizer_utils.py index 0cbc3b9b..568a672d 100644 --- a/llms/mlx_lm/tokenizer_utils.py +++ b/llms/mlx_lm/tokenizer_utils.py @@ -186,6 +186,8 @@ class BPEStreamingDetokenizer(StreamingDetokenizer): # https://github.com/openai/gpt-2/blob/master/src/encoder.py self.make_byte_decoder() + self._added_ids = set(tokenizer.added_tokens_decoder.keys()) + def reset(self): self.offset = 0 self._unflushed = "" @@ -205,12 +207,17 @@ class BPEStreamingDetokenizer(StreamingDetokenizer): def add_token(self, token): v = self.tokenmap[token] - if self._byte_decoder[v[0]] == 32: + is_added = token in self._added_ids + if is_added or self._byte_decoder[v[0]] == 32: current_text = bytearray( self._byte_decoder[c] for c in self._unflushed ).decode("utf-8") self.text += self._maybe_trim_space(current_text) - self._unflushed = v + if is_added: + self.text += v + self._unflushed = "" + else: + self._unflushed = v else: self._unflushed += v diff --git a/llms/tests/test_tokenizers.py b/llms/tests/test_tokenizers.py index 03445c1f..3c93fbe2 100644 --- a/llms/tests/test_tokenizers.py +++ b/llms/tests/test_tokenizers.py @@ -74,6 +74,17 @@ class TestTokenizers(unittest.TestCase): tokenizer._detokenizer = NaiveStreamingDetokenizer(tokenizer) self.check_tokenizer(tokenizer) + def test_special_tokens(self): + tokenizer_repo = "mlx-community/DeepSeek-Coder-V2-Lite-Instruct-4bit-mlx" + tokenizer = self.download_tokenizer(tokenizer_repo) + + detokenizer = tokenizer.detokenizer + detokenizer.reset() + detokenizer.add_token(tokenizer.eos_token_id) + detokenizer.finalize() + + self.assertEqual(detokenizer.last_segment, tokenizer.eos_token) + if __name__ == "__main__": unittest.main() From 29c954f4cb3eb708a6b7115327168ca83c5c0972 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Sat, 2 Nov 2024 13:51:38 -0700 Subject: [PATCH 070/188] fix (#1082) --- whisper/mlx_whisper/_version.py | 2 +- whisper/mlx_whisper/decoding.py | 47 ++++++++++++++++----------------- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/whisper/mlx_whisper/_version.py b/whisper/mlx_whisper/_version.py index 45e522d1..8280e038 100644 --- a/whisper/mlx_whisper/_version.py +++ b/whisper/mlx_whisper/_version.py @@ -1,3 +1,3 @@ # Copyright © 2023-2024 Apple Inc. -__version__ = "0.4.0" +__version__ = "0.4.1" diff --git a/whisper/mlx_whisper/decoding.py b/whisper/mlx_whisper/decoding.py index 6bf975d5..4e060cd5 100644 --- a/whisper/mlx_whisper/decoding.py +++ b/whisper/mlx_whisper/decoding.py @@ -589,35 +589,34 @@ class DecodingTask: ) return tokens, completed, sum_logprobs, pre_logits - try: - tokens, completed, sum_logprobs, pre_logits = _step( - tokens, audio_features, tokens, sum_logprobs + tokens, completed, sum_logprobs, pre_logits = _step( + tokens, audio_features, tokens, sum_logprobs + ) + if self.tokenizer.no_speech is not None: # compute no_speech_probs + probs_at_sot = mx.softmax(pre_logits[:, self.sot_index], axis=-1) + no_speech_probs = probs_at_sot[:, self.tokenizer.no_speech] + else: + no_speech_probs = mx.full(n_batch, mx.nan) + mx.async_eval(completed, tokens, sum_logprobs, no_speech_probs) + + for i in range(1, self.sample_len): + inputs = tokens[:, -1:] + if tokens.shape[-1] > self.n_ctx: + break + next_tokens, next_completed, next_sum_logprobs, _ = _step( + inputs, audio_features, tokens, sum_logprobs ) - if self.tokenizer.no_speech is not None: # compute no_speech_probs - probs_at_sot = mx.softmax(pre_logits[:, self.sot_index], axis=-1) - no_speech_probs = probs_at_sot[:, self.tokenizer.no_speech] - else: - no_speech_probs = mx.full(n_batch, mx.nan) - mx.async_eval(completed, tokens, sum_logprobs, no_speech_probs) - - for i in range(1, self.sample_len): - inputs = tokens[:, -1:] - next_tokens, next_completed, next_sum_logprobs, _ = _step( - inputs, audio_features, tokens, sum_logprobs - ) - mx.async_eval(next_completed, next_tokens, next_sum_logprobs) - if completed or tokens.shape[-1] > self.n_ctx: - break - tokens = next_tokens - completed = next_completed - sum_logprobs = next_sum_logprobs - - finally: - self.inference.reset() + mx.async_eval(next_completed, next_tokens, next_sum_logprobs) + if completed: + break + tokens = next_tokens + completed = next_completed + sum_logprobs = next_sum_logprobs return tokens, sum_logprobs, no_speech_probs def run(self, mel: mx.array) -> List[DecodingResult]: + self.inference.reset() self.decoder.reset() tokenizer: Tokenizer = self.tokenizer n_audio: int = mel.shape[0] From 331148d8ec05ce2f1dd50444570c61805b700039 Mon Sep 17 00:00:00 2001 From: Angelos Katharopoulos Date: Sat, 2 Nov 2024 18:02:31 -0700 Subject: [PATCH 071/188] Enable distributed LoRA training (#821) --- llms/mlx_lm/tuner/trainer.py | 81 ++++++++++++++++++++++++------------ llms/tests/test_finetune.py | 51 ++++++++++++++--------- 2 files changed, 86 insertions(+), 46 deletions(-) diff --git a/llms/mlx_lm/tuner/trainer.py b/llms/mlx_lm/tuner/trainer.py index 1d934a72..38619d95 100644 --- a/llms/mlx_lm/tuner/trainer.py +++ b/llms/mlx_lm/tuner/trainer.py @@ -10,6 +10,7 @@ from typing import Union import mlx.core as mx import mlx.nn as nn import numpy as np +from mlx.nn.utils import average_gradients from mlx.utils import tree_flatten @@ -84,9 +85,16 @@ def iterate_batches(dataset, tokenizer, batch_size, max_seq_length, train=False) f" examples but only has {len(dataset)}." ) + # If running in distributed mode (N machines) then each one should skip N-1 + # samples + step = mx.distributed.init().size() + if batch_size % step != 0: + raise ValueError("The batch size must be divisible by the number of workers") + # Make the batches: batch_idx = [ - idx[i : i + batch_size] for i in range(0, len(idx) - batch_size + 1, batch_size) + idx[i : i + batch_size : step] + for i in range(0, len(idx) - batch_size + 1, batch_size) ] while True: @@ -112,9 +120,9 @@ def iterate_batches(dataset, tokenizer, batch_size, max_seq_length, train=False) max_length_in_batch = pad_to * ((max(lengths) + pad_to - 1) // pad_to) max_length_in_batch = min(max_length_in_batch, max_seq_length) - batch_arr = np.zeros((batch_size, max_length_in_batch), np.int32) + batch_arr = np.zeros((batch_size // step, max_length_in_batch), np.int32) - for j in range(batch_size): + for j in range(batch_size // step): truncated_length = min(lengths[j], max_seq_length) batch_arr[j, :truncated_length] = batch[j][:truncated_length] lengths[j] = ( @@ -138,7 +146,7 @@ def evaluate( loss: callable = default_loss, iterate_batches: callable = iterate_batches, ): - all_losses = [] + all_losses = 0 ntokens = 0 index_iterator = iter(range(num_batches)) if num_batches != -1 else iter(int, 1) @@ -153,10 +161,14 @@ def evaluate( ), ): losses, toks = loss(model, *batch) - all_losses.append((losses * toks).item()) - ntokens += toks.item() + all_losses += losses * toks + ntokens += toks + mx.eval(all_losses, ntokens) - return np.sum(all_losses) / ntokens + all_losses = mx.distributed.all_sum(all_losses) + ntokens = mx.distributed.all_sum(ntokens) + + return (all_losses / ntokens).item() class TrainingCallback: @@ -182,6 +194,11 @@ def train( training_callback: TrainingCallback = None, ): print(f"Starting training..., iters: {args.iters}") + world = mx.distributed.init() + world_size = world.size() + rank = world.rank() + if world_size > 1: + print(f"Node {rank} of {world_size}") if args.grad_checkpoint: grad_checkpoint(model.layers[0]) @@ -192,6 +209,9 @@ def train( # Forward and backward pass (lvalue, toks), grad = loss_value_and_grad(model, *batch) + # All reduce the gradients if running in distributed mode + grad = average_gradients(grad) + # Model update optimizer.update(model, grad) @@ -199,8 +219,9 @@ def train( loss_value_and_grad = nn.value_and_grad(model, loss) - losses = [] + losses = 0 n_tokens = 0 + steps = 0 trained_tokens = 0 # Main training loop start = time.perf_counter() @@ -229,9 +250,13 @@ def train( iterate_batches=iterate_batches, ) val_time = time.perf_counter() - stop - print( - f"Iter {it}: " f"Val loss {val_loss:.3f}, " f"Val took {val_time:.3f}s" - ) + if rank == 0: + print( + f"Iter {it}: " + f"Val loss {val_loss:.3f}, " + f"Val took {val_time:.3f}s", + flush=True, + ) if training_callback is not None: val_info = { @@ -244,30 +269,33 @@ def train( start = time.perf_counter() lvalue, toks = step(batch) - mx.eval(state, lvalue, toks) - - # Record loss - losses.append(lvalue.item()) - n_tokens += toks.item() + losses += lvalue + n_tokens += toks + steps += 1 + mx.eval(state, losses, n_tokens) # Report training loss if needed if it % args.steps_per_report == 0 or it == args.iters: stop = time.perf_counter() - train_loss = np.mean(losses) + train_loss = mx.distributed.all_sum(losses).item() + train_loss /= steps * mx.distributed.init().size() + n_tokens = mx.distributed.all_sum(n_tokens).item() learning_rate = optimizer.learning_rate.item() it_sec = args.steps_per_report / (stop - start) tokens_sec = float(n_tokens) / (stop - start) trained_tokens += n_tokens peak_mem = mx.metal.get_peak_memory() / 2**30 - print( - f"Iter {it}: Train loss {train_loss:.3f}, " - f"Learning Rate {learning_rate:.3e}, " - f"It/sec {it_sec:.3f}, " - f"Tokens/sec {tokens_sec:.3f}, " - f"Trained Tokens {trained_tokens}, " - f"Peak mem {peak_mem:.3f} GB" - ) + if rank == 0: + print( + f"Iter {it}: Train loss {train_loss:.3f}, " + f"Learning Rate {learning_rate:.3e}, " + f"It/sec {it_sec:.3f}, " + f"Tokens/sec {tokens_sec:.3f}, " + f"Trained Tokens {trained_tokens}, " + f"Peak mem {peak_mem:.3f} GB", + flush=True, + ) if training_callback is not None: train_info = { @@ -281,8 +309,9 @@ def train( } training_callback.on_train_loss_report(train_info) - losses = [] + losses = 0 n_tokens = 0 + steps = 0 start = time.perf_counter() # Save adapter weights diff --git a/llms/tests/test_finetune.py b/llms/tests/test_finetune.py index 107be092..6ba81628 100644 --- a/llms/tests/test_finetune.py +++ b/llms/tests/test_finetune.py @@ -3,6 +3,7 @@ import math import sys import unittest +from contextlib import contextmanager from io import StringIO from unittest.mock import MagicMock @@ -17,6 +18,14 @@ from mlx_lm.tuner.trainer import evaluate from mlx_lm.tuner.utils import build_schedule +@contextmanager +def swapped_with_identity(obj, func): + old_func = getattr(obj, func) + setattr(obj, func, lambda x: x) + yield + setattr(obj, func, old_func) + + class TestLora(unittest.TestCase): def setUp(self): self.capturedOutput = StringIO() @@ -374,16 +383,17 @@ class TestScheduleConfig(unittest.TestCase): (MagicMock(return_value=0.4), MagicMock(return_value=180)), (MagicMock(return_value=0.6), MagicMock(return_value=120)), ] - evaluate( - model=mock_model, - dataset=mock_dataset, - tokenizer=mock_tokenizer, - batch_size=2, - num_batches=2, - max_seq_length=2048, - loss=mock_default_loss, - iterate_batches=mock_iterate_batches, - ) + with swapped_with_identity(mx.distributed, "all_sum"): + evaluate( + model=mock_model, + dataset=mock_dataset, + tokenizer=mock_tokenizer, + batch_size=2, + num_batches=2, + max_seq_length=2048, + loss=mock_default_loss, + iterate_batches=mock_iterate_batches, + ) mock_iterate_batches.assert_called_once_with( dataset=mock_dataset, @@ -412,16 +422,17 @@ class TestScheduleConfig(unittest.TestCase): (MagicMock(return_value=0.2), MagicMock(return_value=150)), ] - evaluate( - model=mock_model, - dataset=mock_dataset, - tokenizer=mock_tokenizer, - batch_size=2, - num_batches=-1, - max_seq_length=2048, - loss=mock_default_loss, - iterate_batches=mock_iterate_batches, - ) + with swapped_with_identity(mx.distributed, "all_sum"): + evaluate( + model=mock_model, + dataset=mock_dataset, + tokenizer=mock_tokenizer, + batch_size=2, + num_batches=-1, + max_seq_length=2048, + loss=mock_default_loss, + iterate_batches=mock_iterate_batches, + ) mock_iterate_batches.assert_called_once_with( dataset=mock_dataset, From 82e333898707eb57235f408aa6907beca095f759 Mon Sep 17 00:00:00 2001 From: Anchen Date: Mon, 4 Nov 2024 22:06:34 +0800 Subject: [PATCH 072/188] chore(mlx-lm): add max token arg for mlx_lm.chat (#1089) * chore(mlx-lm): add max token arg for mlx_lm.chat * chore: update the default max token value --- llms/mlx_lm/chat.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/llms/mlx_lm/chat.py b/llms/mlx_lm/chat.py index ea1a99c7..85d32d5f 100644 --- a/llms/mlx_lm/chat.py +++ b/llms/mlx_lm/chat.py @@ -11,6 +11,7 @@ from .utils import load, stream_generate DEFAULT_TEMP = 0.0 DEFAULT_TOP_P = 1.0 DEFAULT_SEED = 0 +DEFAULT_MAX_TOKENS = 256 DEFAULT_MODEL = "mlx-community/Llama-3.2-3B-Instruct-4bit" @@ -41,6 +42,13 @@ def setup_arg_parser(): help="Set the maximum key-value cache size", default=None, ) + parser.add_argument( + "--max-tokens", + "-m", + type=int, + default=DEFAULT_MAX_TOKENS, + help="Maximum number of tokens to generate", + ) return parser @@ -70,6 +78,7 @@ def main(): model, tokenizer, prompt, + args.max_tokens, temp=args.temp, top_p=args.top_p, prompt_cache=prompt_cache, From 3b526f0aa1219fae662a86f012dbda82045f4fb0 Mon Sep 17 00:00:00 2001 From: ilyasch2 <104485953+ilyasch2@users.noreply.github.com> Date: Tue, 5 Nov 2024 00:23:30 +0400 Subject: [PATCH 073/188] Add support for falcon-mamba (#1074) * Add support for falcon-mamba * nits * nit --------- Co-authored-by: Awni Hannun --- llms/README.md | 1 + llms/mlx_lm/models/mamba.py | 11 +++++++++++ llms/mlx_lm/utils.py | 1 + 3 files changed, 13 insertions(+) diff --git a/llms/README.md b/llms/README.md index f539988a..0e7dc7fb 100644 --- a/llms/README.md +++ b/llms/README.md @@ -221,6 +221,7 @@ Here are a few examples of Hugging Face models that work with this example: - [pfnet/plamo-13b-instruct](https://huggingface.co/pfnet/plamo-13b-instruct) - [stabilityai/stablelm-2-zephyr-1_6b](https://huggingface.co/stabilityai/stablelm-2-zephyr-1_6b) - [internlm/internlm2-7b](https://huggingface.co/internlm/internlm2-7b) +- [tiiuae/falcon-mamba-7b-instruct](https://huggingface.co/tiiuae/falcon-mamba-7b-instruct) Most [Mistral](https://huggingface.co/models?library=transformers,safetensors&other=mistral&sort=trending), diff --git a/llms/mlx_lm/models/mamba.py b/llms/mlx_lm/models/mamba.py index 84f498e9..f2414660 100644 --- a/llms/mlx_lm/models/mamba.py +++ b/llms/mlx_lm/models/mamba.py @@ -23,6 +23,8 @@ class ModelArgs(BaseModelArgs): use_conv_bias: bool time_step_rank: int tie_word_embeddings: bool = True + use_bcdt_rms: bool = False + mixer_rms_eps: float = 1e-6 def __post_init__(self): if not hasattr(self, "hidden_size") and hasattr(self, "d_model"): @@ -44,6 +46,8 @@ class ModelArgs(BaseModelArgs): if self.time_step_rank == "auto": self.time_step_rank = math.ceil(self.hidden_size / 16) + if self.model_type == "falcon_mamba": + self.use_bcdt_rms = True class DepthWiseConv1d(nn.Module): @@ -83,6 +87,11 @@ class MambaBlock(nn.Module): self.intermediate_size = args.intermediate_size self.time_step_rank = int(args.time_step_rank) self.use_conv_bias = args.use_conv_bias + self.use_bcdt_rms = args.use_bcdt_rms + if self.use_bcdt_rms: + self.mixer_norm = lambda x: mx.fast.rms_norm( + x, mx.ones(x.shape[-1], x.dtype), eps=args.mixer_rms_eps + ) self.in_proj = nn.Linear( self.hidden_size, self.intermediate_size * 2, bias=args.use_bias @@ -126,6 +135,8 @@ class MambaBlock(nn.Module): ], axis=-1, ) + if self.use_bcdt_rms: + delta, B, C = map(self.mixer_norm, (delta, B, C)) delta = nn.softplus(self.dt_proj(delta)) new_state = mx.expand_dims(delta * x, -1) * mx.expand_dims(B, 1) if state is not None: diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index b9fc202d..7b440db6 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -29,6 +29,7 @@ from .tuner.utils import load_adapters MODEL_REMAPPING = { "mistral": "llama", # mistral is compatible with llama "phi-msft": "phixtral", + "falcon_mamba": "mamba", } MAX_FILE_SIZE_GB = 5 From 4394633ce0f9d96cbbdf571e077fa4fd78479b9f Mon Sep 17 00:00:00 2001 From: Anthony Wu <462072+anthonywu@users.noreply.github.com> Date: Mon, 4 Nov 2024 14:02:13 -0800 Subject: [PATCH 074/188] mlx_whisper: add support for audio input from stdin (#1012) * add support for audio and input name from stdin * refactored to stdin - arg, and output-name template * fix bugs, add test coverage * fix doc to match arg rename * some nits --------- Co-authored-by: Awni Hannun --- whisper/README.md | 13 +++++++++++-- whisper/mlx_whisper/audio.py | 18 ++++++++++-------- whisper/mlx_whisper/cli.py | 34 +++++++++++++++++++++++++++------- whisper/mlx_whisper/writers.py | 14 +++++--------- 4 files changed, 53 insertions(+), 26 deletions(-) diff --git a/whisper/README.md b/whisper/README.md index ac6e95f6..cd3bc684 100644 --- a/whisper/README.md +++ b/whisper/README.md @@ -25,7 +25,7 @@ pip install mlx-whisper At its simplest: -``` +```sh mlx_whisper audio_file.mp3 ``` @@ -35,6 +35,15 @@ Use `-f` to specify the output format and `--model` to specify the model. There are many other supported command line options. To see them all, run `mlx_whisper -h`. +You can also pipe the audio content of other programs via stdin: + +```sh +some-process | mlx_whisper - +``` + +The default output file name will be `content.*`. You can specify the name with +the `--output-name` flag. + #### API Transcribe audio with: @@ -103,7 +112,7 @@ python convert.py --help ``` By default, the conversion script will make the directory `mlx_models` -and save the converted `weights.npz` and `config.json` there. +and save the converted `weights.npz` and `config.json` there. Each time it is run, `convert.py` will overwrite any model in the provided path. To save different models, make sure to set `--mlx-path` to a unique diff --git a/whisper/mlx_whisper/audio.py b/whisper/mlx_whisper/audio.py index e04309c1..c8cca07c 100644 --- a/whisper/mlx_whisper/audio.py +++ b/whisper/mlx_whisper/audio.py @@ -3,7 +3,7 @@ import os from functools import lru_cache from subprocess import CalledProcessError, run -from typing import Union +from typing import Optional, Union import mlx.core as mx import numpy as np @@ -21,7 +21,7 @@ FRAMES_PER_SECOND = SAMPLE_RATE // HOP_LENGTH # 10ms per audio frame TOKENS_PER_SECOND = SAMPLE_RATE // N_SAMPLES_PER_TOKEN # 20ms per audio token -def load_audio(file: str, sr: int = SAMPLE_RATE): +def load_audio(file: str = Optional[str], sr: int = SAMPLE_RATE, from_stdin=False): """ Open an audio file and read as mono waveform, resampling as necessary @@ -39,19 +39,21 @@ def load_audio(file: str, sr: int = SAMPLE_RATE): """ # This launches a subprocess to decode audio while down-mixing - # and resampling as necessary. Requires the ffmpeg CLI in PATH. + # and resampling as necessary. Requires the ffmpeg CLI in PATH. + if from_stdin: + cmd = ["ffmpeg", "-i", "pipe:0"] + else: + cmd = ["ffmpeg", "-nostdin", "-i", file] + # fmt: off - cmd = [ - "ffmpeg", - "-nostdin", + cmd.extend([ "-threads", "0", - "-i", file, "-f", "s16le", "-ac", "1", "-acodec", "pcm_s16le", "-ar", str(sr), "-" - ] + ]) # fmt: on try: out = run(cmd, capture_output=True, check=True).stdout diff --git a/whisper/mlx_whisper/cli.py b/whisper/mlx_whisper/cli.py index c2813338..7d08a043 100644 --- a/whisper/mlx_whisper/cli.py +++ b/whisper/mlx_whisper/cli.py @@ -2,9 +2,11 @@ import argparse import os +import pathlib import traceback import warnings +from . import audio from .tokenizer import LANGUAGES, TO_LANGUAGE_CODE from .transcribe import transcribe from .writers import get_writer @@ -27,15 +29,24 @@ def build_parser(): parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter ) - parser.add_argument( - "audio", nargs="+", type=str, help="Audio file(s) to transcribe" - ) + + parser.add_argument("audio", nargs="+", help="Audio file(s) to transcribe") + parser.add_argument( "--model", default="mlx-community/whisper-tiny", type=str, help="The model directory or hugging face repo", ) + parser.add_argument( + "--output-name", + type=str, + default=None, + help=( + "The name of transcription/translation output files before " + "--output-format extensions" + ), + ) parser.add_argument( "--output-dir", "-o", @@ -200,6 +211,7 @@ def main(): path_or_hf_repo: str = args.pop("model") output_dir: str = args.pop("output_dir") output_format: str = args.pop("output_format") + output_name: str = args.pop("output_name") os.makedirs(output_dir, exist_ok=True) writer = get_writer(output_format, output_dir) @@ -219,17 +231,25 @@ def main(): warnings.warn("--max-line-count has no effect without --max-line-width") if writer_args["max_words_per_line"] and writer_args["max_line_width"]: warnings.warn("--max-words-per-line has no effect with --max-line-width") - for audio_path in args.pop("audio"): + + for audio_obj in args.pop("audio"): + if audio_obj == "-": + # receive the contents from stdin rather than read a file + audio_obj = audio.load_audio(from_stdin=True) + + output_name = output_name or "content" + else: + output_name = output_name or pathlib.Path(audio_obj).stem try: result = transcribe( - audio_path, + audio_obj, path_or_hf_repo=path_or_hf_repo, **args, ) - writer(result, audio_path, **writer_args) + writer(result, output_name, **writer_args) except Exception as e: traceback.print_exc() - print(f"Skipping {audio_path} due to {type(e).__name__}: {str(e)}") + print(f"Skipping {audio_obj} due to {type(e).__name__}: {str(e)}") if __name__ == "__main__": diff --git a/whisper/mlx_whisper/writers.py b/whisper/mlx_whisper/writers.py index 464ead18..cdb35063 100644 --- a/whisper/mlx_whisper/writers.py +++ b/whisper/mlx_whisper/writers.py @@ -1,10 +1,8 @@ # Copyright © 2024 Apple Inc. import json -import os +import pathlib import re -import sys -import zlib from typing import Callable, List, Optional, TextIO @@ -43,15 +41,13 @@ class ResultWriter: self.output_dir = output_dir def __call__( - self, result: dict, audio_path: str, options: Optional[dict] = None, **kwargs + self, result: dict, output_name: str, options: Optional[dict] = None, **kwargs ): - audio_basename = os.path.basename(audio_path) - audio_basename = os.path.splitext(audio_basename)[0] - output_path = os.path.join( - self.output_dir, audio_basename + "." + self.extension + output_path = (pathlib.Path(self.output_dir) / output_name).with_suffix( + f".{self.extension}" ) - with open(output_path, "w", encoding="utf-8") as f: + with output_path.open("wt", encoding="utf-8") as f: self.write_result(result, file=f, options=options, **kwargs) def write_result( From 6fd1f70f7366a1e55f14e2b4cd885b86875ab56c Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Tue, 5 Nov 2024 06:06:26 -0800 Subject: [PATCH 075/188] fix spm decoder multi-byte (#1092) --- llms/mlx_lm/tokenizer_utils.py | 40 +++++++++++++++------------------- llms/tests/test_tokenizers.py | 3 +++ 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/llms/mlx_lm/tokenizer_utils.py b/llms/mlx_lm/tokenizer_utils.py index 568a672d..9d390733 100644 --- a/llms/mlx_lm/tokenizer_utils.py +++ b/llms/mlx_lm/tokenizer_utils.py @@ -6,12 +6,6 @@ from transformers import AutoTokenizer REPLACEMENT_CHAR = "\ufffd" -def _remove_space(x): - if x and x[0] == " ": - return x[1:] - return x - - class StreamingDetokenizer: """The streaming detokenizer interface so that we can detokenize one token at a time. @@ -123,42 +117,42 @@ class SPMStreamingDetokenizer(StreamingDetokenizer): def __init__(self, tokenizer, trim_space=True): self.trim_space = trim_space + self._sep = "\u2581".encode() # Extract the tokens in a list from id to text self.tokenmap = [""] * (max(tokenizer.vocab.values()) + 1) for value, tokenid in tokenizer.vocab.items(): - self.tokenmap[tokenid] = value - - # Replace bytes with their value - for i in range(len(self.tokenmap)): - if self.tokenmap[i].startswith("<0x"): - self.tokenmap[i] = chr(int(self.tokenmap[i][3:5], 16)) + if value.startswith("<0x"): + # Replace bytes with their value + self.tokenmap[tokenid] = bytes([int(value[3:5], 16)]) + else: + self.tokenmap[tokenid] = value.encode() self.reset() def reset(self): self.offset = 0 - self._unflushed = "" + self._unflushed = b"" self.text = "" self.tokens = [] + def _flush(self): + text = self._unflushed.replace(self._sep, b" ").decode("utf-8") + if not self.text and self.trim_space and text and text[0] == " ": + text = text[1:] + self.text += text + def add_token(self, token): v = self.tokenmap[token] - if v[0] == "\u2581": - if self.text or not self.trim_space: - self.text += self._unflushed.replace("\u2581", " ") - else: - self.text = _remove_space(self._unflushed.replace("\u2581", " ")) + if v.startswith(self._sep): + self._flush() self._unflushed = v else: self._unflushed += v def finalize(self): - if self.text or not self.trim_space: - self.text += self._unflushed.replace("\u2581", " ") - else: - self.text = _remove_space(self._unflushed.replace("\u2581", " ")) - self._unflushed = "" + self._flush() + self._unflushed = b"" class BPEStreamingDetokenizer(StreamingDetokenizer): diff --git a/llms/tests/test_tokenizers.py b/llms/tests/test_tokenizers.py index 3c93fbe2..9c30d51e 100644 --- a/llms/tests/test_tokenizers.py +++ b/llms/tests/test_tokenizers.py @@ -42,6 +42,9 @@ class TestTokenizers(unittest.TestCase): text += detokenizer.last_segment self.assertEqual(text, expected_text) + tokens = tokenizer.encode("こんにちは!私の名前はAI") + check(tokens) + tokens = tokenizer.encode("a ,b") check(tokens) From ed9e81dd581a9505e677e12c025137d5326fe6df Mon Sep 17 00:00:00 2001 From: Angelos Katharopoulos Date: Tue, 5 Nov 2024 10:24:24 -0800 Subject: [PATCH 076/188] Fix rotating kv cache size (#1093) --- llms/mlx_lm/models/base.py | 2 +- llms/mlx_lm/models/cache.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/llms/mlx_lm/models/base.py b/llms/mlx_lm/models/base.py index cda41c79..f02f49b1 100644 --- a/llms/mlx_lm/models/base.py +++ b/llms/mlx_lm/models/base.py @@ -42,7 +42,7 @@ def create_attention_mask(h: mx.array, cache: Optional[Any] = None): if cache is not None and cache[0] is not None: c = cache[0] if hasattr(c, "max_size"): - offset = min(c.max_size - 1, c.offset) + offset = min(c.max_size, c.offset) window_size = c.max_size else: offset = c.offset diff --git a/llms/mlx_lm/models/cache.py b/llms/mlx_lm/models/cache.py index 1cd5289d..14026f0c 100644 --- a/llms/mlx_lm/models/cache.py +++ b/llms/mlx_lm/models/cache.py @@ -325,9 +325,9 @@ class RotatingKVCache(_BaseCache): self.keys = self._temporal_order(self.keys) self.values = self._temporal_order(self.values) - # The largest size is self.max_size + S - 1 to ensure + # The largest size is self.max_size + S to ensure # every token gets at least self.max_size context - trim_size = self._idx - self.max_size + 1 + trim_size = self._idx - self.max_size self.keys = self._trim(trim_size, self.keys, keys) self.values = self._trim(trim_size, self.values, values) self.offset += keys.shape[2] From 657b4cc0aa90af09ac9793168cb81d406db882c6 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Thu, 7 Nov 2024 16:15:24 -0800 Subject: [PATCH 077/188] [MLX LM] Sampler refactor + a few improvements (#1094) * starting * refactor sampler/processor and a few improvements * fix stream * fix stream generate * fix eos handling in stream generate --- llms/README.md | 5 +- llms/mlx_lm/cache_prompt.py | 4 +- llms/mlx_lm/chat.py | 2 +- llms/mlx_lm/generate.py | 14 +++ llms/mlx_lm/sample_utils.py | 106 ++++++++++++++++++ llms/mlx_lm/server.py | 193 ++++++++++++-------------------- llms/mlx_lm/tuner/trainer.py | 2 +- llms/mlx_lm/utils.py | 168 ++++++++++----------------- llms/tests/test_generate.py | 2 +- llms/tests/test_prompt_cache.py | 2 +- 10 files changed, 259 insertions(+), 239 deletions(-) diff --git a/llms/README.md b/llms/README.md index 0e7dc7fb..eeb3ed6a 100644 --- a/llms/README.md +++ b/llms/README.md @@ -101,7 +101,8 @@ To see a description of all the arguments you can do: #### Streaming For streaming generation, use the `stream_generate` function. This returns a -generator object which streams the output text. For example, +generator object which streams the output text, token, and log probabilities. +For example, ```python from mlx_lm import load, stream_generate @@ -116,7 +117,7 @@ prompt = tokenizer.apply_chat_template( messages, tokenize=False, add_generation_prompt=True ) -for t in stream_generate(model, tokenizer, prompt, max_tokens=512): +for text, *_ in stream_generate(model, tokenizer, prompt, max_tokens=512): print(t, end="", flush=True) print() ``` diff --git a/llms/mlx_lm/cache_prompt.py b/llms/mlx_lm/cache_prompt.py index 7bb06411..987b640d 100644 --- a/llms/mlx_lm/cache_prompt.py +++ b/llms/mlx_lm/cache_prompt.py @@ -152,6 +152,7 @@ def main(): model(y[:step_size][None], cache=cache) mx.eval([c.state for c in cache]) + mx.metal.clear_cache() processed += min(y.size, step_size) y = y[step_size:] current = time.time() @@ -165,14 +166,13 @@ def main(): ) print() - print(f"Peak memory: {mx.metal.get_peak_memory() / 2**30:.3f} GB") + print(f"Peak memory: {mx.metal.get_peak_memory() / 1e9:.3f} GB") print("Saving...") metadata = {} metadata["model"] = args.model metadata["chat_template"] = tokenizer.chat_template metadata["tokenizer_config"] = json.dumps(tokenizer_config) - print(f"Peak memory: {mx.metal.get_peak_memory() / 2**30:.3f} GB") save_prompt_cache(args.prompt_cache_file, cache, metadata) diff --git a/llms/mlx_lm/chat.py b/llms/mlx_lm/chat.py index 85d32d5f..c03056a6 100644 --- a/llms/mlx_lm/chat.py +++ b/llms/mlx_lm/chat.py @@ -74,7 +74,7 @@ def main(): prompt = tokenizer.apply_chat_template( messages, tokenize=False, add_generation_prompt=True ) - for response in stream_generate( + for response, *_ in stream_generate( model, tokenizer, prompt, diff --git a/llms/mlx_lm/generate.py b/llms/mlx_lm/generate.py index 29976da2..51169def 100644 --- a/llms/mlx_lm/generate.py +++ b/llms/mlx_lm/generate.py @@ -13,6 +13,8 @@ DEFAULT_PROMPT = "hello" DEFAULT_MAX_TOKENS = 100 DEFAULT_TEMP = 0.0 DEFAULT_TOP_P = 1.0 +DEFAULT_MIN_P = 0.0 +DEFAULT_MIN_TOKENS_TO_KEEP = 1 DEFAULT_SEED = 0 DEFAULT_MODEL = "mlx-community/Llama-3.2-3B-Instruct-4bit" DEFAULT_QUANTIZED_KV_START = 5000 @@ -52,6 +54,7 @@ def setup_arg_parser(): ) parser.add_argument( "--prompt", + "-p", default=DEFAULT_PROMPT, help="Message to be processed by the model ('-' reads from stdin)", ) @@ -68,6 +71,15 @@ def setup_arg_parser(): parser.add_argument( "--top-p", type=float, default=DEFAULT_TOP_P, help="Sampling top-p" ) + parser.add_argument( + "--min-p", type=float, default=DEFAULT_MIN_P, help="Sampling min-p" + ) + parser.add_argument( + "--min-tokens-to-keep", + type=float, + default=DEFAULT_MIN_TOKENS_TO_KEEP, + help="Minimum tokens to keep for min-p sampling.", + ) parser.add_argument("--seed", type=int, default=DEFAULT_SEED, help="PRNG seed") parser.add_argument( "--ignore-chat-template", @@ -247,6 +259,8 @@ def main(): formatter=formatter, temp=args.temp, top_p=args.top_p, + min_p=args.min_p, + min_tokens_to_keep=args.min_tokens_to_keep, max_kv_size=args.max_kv_size, prompt_cache=prompt_cache if using_cache else None, kv_bits=args.kv_bits, diff --git a/llms/mlx_lm/sample_utils.py b/llms/mlx_lm/sample_utils.py index 20b008fa..c27b52d8 100644 --- a/llms/mlx_lm/sample_utils.py +++ b/llms/mlx_lm/sample_utils.py @@ -1,10 +1,83 @@ # Copyright © 2023-2024 Apple Inc. from functools import partial +from typing import Callable, Dict, Optional import mlx.core as mx +def make_sampler( + temp: float = 0.0, + top_p: float = 0.0, + min_p: float = 0.0, + min_tokens_to_keep: int = 1, +) -> Callable[mx.array, mx.array]: + """ + Make a sampler function for use with ``generate_step``. + + Args: + temp (float): The temperature for sampling, if 0 the argmax is used. + Default: ``0``. + top_p (float, optional): Nulceus sampling, higher means model considers + more less likely words. + min_p (float, optional): The minimum value (scaled by the top token's + probability) that a token probability must have to be considered. + min_tokens_to_keep (int, optional): Minimum number of tokens that cannot + be filtered by min_p sampling. + + Returns: + Callable[mx.array, mx.array]: + A sampler which takes log-probabilities and returns tokens. + """ + if temp == 0: + return lambda x: mx.argmax(x, axis=-1) + elif top_p > 0 and top_p < 1.0: + return lambda x: top_p_sampling(x, top_p, temp) + elif min_p != 0.0: + return lambda x: min_p_sampling(x, min_p, min_tokens_to_keep, temp) + else: + return lambda x: categorical_sampling(x, temp) + + +def make_logits_processors( + logit_bias: Optional[Dict[int, float]] = None, + repetition_penalty: Optional[float] = None, + repetition_context_size: Optional[int] = 20, +): + """ + Make logits processors for use with ``generate_step``. + + Args: + repetition_penalty (float, optional): The penalty factor for repeating + tokens. + repetition_context_size (int, optional): The number of tokens to + consider for repetition penalty. Default: ``20``. + logit_bias (dictionary, optional): Additive logit bias. + + Returns: + List[Callable[[mx.array, mx.array], mx.array]]: + A list of logits processors. Each processor in the list is a + callable which takes an array of tokens and an array of logits + and returns the updated logits. + """ + logits_processors = [] + if logit_bias: + indices = mx.array(list(logit_bias.keys())) + values = mx.array(list(logit_bias.values())) + + def logit_bias_processor(_, logits): + logits[:, indices] += values + return logits + + logits_processors.append(logit_bias_processor) + + if repetition_penalty and repetition_penalty != 0.0: + logits_processors.append( + make_repetition_penalty(repetition_penalty, repetition_context_size) + ) + return logits_processors + + @partial(mx.compile, inputs=mx.random.state, outputs=mx.random.state) def min_p_sampling( logits: mx.array, @@ -100,3 +173,36 @@ def top_p_sampling(logits: mx.array, top_p: float, temperature: float) -> mx.arr @partial(mx.compile, inputs=mx.random.state, outputs=mx.random.state) def categorical_sampling(logits, temp): return mx.random.categorical(logits * (1 / temp)) + + +def make_repetition_penalty(penalty: float, context_size: int = 20): + """ + Make repetition penalty processor. + + Paper: https://arxiv.org/abs/1909.05858 + + Args: + penalty (float): The repetition penalty factor to be applied. + context_size (int): The number of previous tokens to use. + Default: ``20``. + + Returns: + Callable[[mx.array, List[int]], mx.array]: + The repetition penalty processor. + """ + if penalty < 0 or not isinstance(penalty, float): + raise ValueError(f"penalty must be a non-negative float, got {penalty}") + + def repetition_penalty_processor(tokens, logits): + if len(tokens) > 0: + tokens = tokens[-context_size:] + selected_logits = logits[:, tokens] + selected_logits = mx.where( + selected_logits < 0, + selected_logits * penalty, + selected_logits / penalty, + ) + logits[:, tokens] = selected_logits + return logits + + return repetition_penalty_processor diff --git a/llms/mlx_lm/server.py b/llms/mlx_lm/server.py index ec659969..c1365b36 100644 --- a/llms/mlx_lm/server.py +++ b/llms/mlx_lm/server.py @@ -27,7 +27,7 @@ from huggingface_hub import scan_cache_dir from ._version import __version__ from .models.cache import make_prompt_cache -from .utils import generate_step, load +from .utils import load, stream_generate def get_system_fingerprint(): @@ -64,7 +64,7 @@ def stopping_criteria( end if it has (`trim_length`). """ if tokens and tokens[-1] == eos_token_id: - return StopCondition(stop_met=True, trim_length=1) + return StopCondition(stop_met=True, trim_length=0) for stop_ids in stop_id_sequences: if len(tokens) >= len(stop_ids): @@ -253,7 +253,7 @@ class APIHandler(BaseHTTPRequestHandler): self.max_tokens = self.body.get("max_completion_tokens", None) if self.max_tokens is None: self.max_tokens = self.body.get("max_tokens", 512) - self.temperature = self.body.get("temperature", 1.0) + self.temperature = self.body.get("temperature", 0.0) self.top_p = self.body.get("top_p", 1.0) self.repetition_penalty = self.body.get("repetition_penalty", 1.0) self.repetition_context_size = self.body.get("repetition_context_size", 20) @@ -290,10 +290,7 @@ class APIHandler(BaseHTTPRequestHandler): # Call endpoint specific method prompt = endpoints[self.path]() - - # Call method based on response type - method = self.handle_stream if self.stream else self.handle_completion - method(prompt, stop_id_sequences) + self.handle_completion(prompt, stop_id_sequences) def validate_model_parameters(self): """ @@ -452,32 +449,40 @@ class APIHandler(BaseHTTPRequestHandler): stop_id_sequences (List[List[int]]): A list of stop words passed to the stopping_criteria function """ - detokenizer = self.tokenizer.detokenizer - detokenizer.reset() tokens = [] finish_reason = "length" stop_sequence_suffix = None - logging.debug(f"Starting completion:") + if self.stream: + self.end_headers() + logging.debug(f"Starting stream:") + else: + logging.debug(f"Starting completion:") token_logprobs = [] top_tokens = [] prompt = self.get_prompt_cache(prompt) - for _, (token, logprobs) in zip( - range(self.max_tokens), - generate_step( - prompt=mx.array(prompt), + text = "" + tic = time.perf_counter() + for n, (segment, token, logprobs) in enumerate( + stream_generate( model=self.model, + tokenizer=self.tokenizer, + prompt=prompt, + max_tokens=self.max_tokens, temp=self.temperature, - top_p=self.top_p, repetition_penalty=self.repetition_penalty, repetition_context_size=self.repetition_context_size, logit_bias=self.logit_bias, prompt_cache=self.prompt_cache.cache, ), ): - detokenizer.add_token(token) - logging.debug(detokenizer.text) + if n == 0: + prompt_time = time.perf_counter() - tic + tic = time.perf_counter() + + text += segment + logging.debug(text) tokens.append(token) if self.logprobs > 0: @@ -498,121 +503,63 @@ class APIHandler(BaseHTTPRequestHandler): stop_sequence_suffix = self.tokenizer.decode( tokens[-stop_condition.trim_length :] ) + text = text[: -len(stop_sequence_suffix)] break - self.prompt_cache.tokens.extend(tokens) - detokenizer.finalize() - text = ( - detokenizer.text - if stop_sequence_suffix is None - else detokenizer.text[: -len(stop_sequence_suffix)] - ) - response = self.generate_response( - text, - finish_reason, - len(prompt), - len(tokens), - token_logprobs=token_logprobs, - top_tokens=top_tokens, - tokens=tokens, - ) - - response_json = json.dumps(response).encode() - indent = "\t" # Backslashes can't be inside of f-strings - logging.debug(f"Outgoing Response: {json.dumps(response, indent=indent)}") - - # Send an additional Content-Length header when it is known - self.send_header("Content-Length", str(len(response_json))) - self.end_headers() - - self.wfile.write(response_json) - self.wfile.flush() - - def handle_stream( - self, - prompt: List[int], - stop_id_sequences: List[List[int]], - ): - """ - Generate response to prompt and foward it to the client using a Server - Sent Events (SSE) stream. - - Args: - prompt (mx.array): The tokenized prompt - stop_id_sequences (List[List[int]]): A list of stop words passed to - the stopping_criteria function - """ - # No additional headers are needed, call end_headers - self.end_headers() - - detokenizer = self.tokenizer.detokenizer - detokenizer.reset() - tokens = [] - - stop_sequence_suffix = None - logging.debug(f"Starting stream:") - - prompt = self.get_prompt_cache(prompt) - - for _, (token, _) in zip( - range(self.max_tokens), - generate_step( - prompt=mx.array(prompt), - model=self.model, - temp=self.temperature, - top_p=self.top_p, - repetition_penalty=self.repetition_penalty, - repetition_context_size=self.repetition_context_size, - prompt_cache=self.prompt_cache.cache, - ), - ): - detokenizer.add_token(token) - logging.debug(detokenizer.text) - tokens.append(token) - - stop_condition = stopping_criteria( - tokens, - stop_id_sequences, - self.tokenizer.eos_token_id, - ) - if stop_condition.stop_met: - if stop_condition.trim_length: - stop_sequence_suffix = self.tokenizer.decode( - tokens[-stop_condition.trim_length :] + if self.stream: + # If the end of tokens overlaps with a stop sequence, generate new + # tokens until we know if the stop sequence is hit or not + if any( + ( + sequence_overlap(tokens, sequence) + for sequence in stop_id_sequences ) - break - - # If the end of tokens overlaps with a stop sequence, generate new - # tokens until we know if the stop sequence is hit or not - if any( - (sequence_overlap(tokens, sequence) for sequence in stop_id_sequences) - ): - continue - - new_text = detokenizer.last_segment - if new_text: - response = self.generate_response(new_text, None) - self.wfile.write(f"data: {json.dumps(response)}\n\n".encode()) - self.wfile.flush() + ): + continue + elif segment: + response = self.generate_response(segment, None) + self.wfile.write(f"data: {json.dumps(response)}\n\n".encode()) + self.wfile.flush() self.prompt_cache.tokens.extend(tokens) - # check is there any remaining text to send - detokenizer.finalize() - last_segment = detokenizer.last_segment - if last_segment: - if stop_sequence_suffix is not None: - last_segment = last_segment[: -len(stop_sequence_suffix)] - response = self.generate_response(last_segment, "length") + gen_time = time.perf_counter() - tic + prompt_tps = len(prompt) / prompt_time + gen_tps = len(tokens) / gen_time + peak_mem = mx.metal.get_peak_memory() / 1e9 + logging.debug(f"Prompt: {prompt_tps:.3f} tokens-per-sec") + logging.debug(f"Generation: {gen_tps:.3f} tokens-per-sec") + logging.debug(f"Peak memory: {peak_mem:.3f} GB") + + if self.stream: + response = self.generate_response(segment, finish_reason) self.wfile.write(f"data: {json.dumps(response)}\n\n".encode()) self.wfile.flush() + if self.stream_options is not None and self.stream_options["include_usage"]: + response = self.completion_usage_response(len(prompt), len(tokens)) + self.wfile.write(f"data: {json.dumps(response)}\n\n".encode()) + self.wfile.flush() + self.wfile.write("data: [DONE]\n\n".encode()) + self.wfile.flush() + else: + response = self.generate_response( + text, + finish_reason, + len(prompt), + len(tokens), + token_logprobs=token_logprobs, + top_tokens=top_tokens, + tokens=tokens, + ) + response_json = json.dumps(response).encode() + indent = "\t" # Backslashes can't be inside of f-strings + logging.debug(f"Outgoing Response: {json.dumps(response, indent=indent)}") - if self.stream_options is not None and self.stream_options["include_usage"]: - response = self.completion_usage_response(len(prompt), len(tokens)) - self.wfile.write(f"data: {json.dumps(response)}\n\n".encode()) - - self.wfile.write("data: [DONE]\n\n".encode()) - self.wfile.flush() + # Send an additional Content-Length header when it is known + self.send_header("Content-Length", str(len(response_json))) + self.end_headers() + self.wfile.write(response_json) + self.wfile.flush() def completion_usage_response( self, diff --git a/llms/mlx_lm/tuner/trainer.py b/llms/mlx_lm/tuner/trainer.py index 38619d95..21b1af18 100644 --- a/llms/mlx_lm/tuner/trainer.py +++ b/llms/mlx_lm/tuner/trainer.py @@ -285,7 +285,7 @@ def train( it_sec = args.steps_per_report / (stop - start) tokens_sec = float(n_tokens) / (stop - start) trained_tokens += n_tokens - peak_mem = mx.metal.get_peak_memory() / 2**30 + peak_mem = mx.metal.get_peak_memory() / 1e9 if rank == 0: print( f"Iter {it}: Train loss {train_loss:.3f}, " diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index 7b440db6..8893b570 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -20,7 +20,7 @@ from transformers import PreTrainedTokenizer # Local imports from .models import cache -from .sample_utils import categorical_sampling, min_p_sampling, top_p_sampling +from .sample_utils import make_logits_processors, make_sampler from .tokenizer_utils import TokenizerWrapper, load_tokenizer from .tuner.utils import dequantize as dequantize_model from .tuner.utils import load_adapters @@ -34,6 +34,9 @@ MODEL_REMAPPING = { MAX_FILE_SIZE_GB = 5 +# A stream on the default device just for generation +generation_stream = mx.new_stream(mx.default_device()) + class ModelNotFoundError(Exception): def __init__(self, message): @@ -137,29 +140,6 @@ def get_model_path(path_or_hf_repo: str, revision: Optional[str] = None) -> Path return model_path -def apply_repetition_penalty(logits: mx.array, tokens: mx.array, penalty: float): - """ - Apply repetition penalty to specific logits based on the given context. - - Paper: https://arxiv.org/abs/1909.05858 - - Args: - logits (mx.array): The logits produced by the language model. - tokens (mx.array): A list of N previous tokens. - penalty (float): The repetition penalty factor to be applied. - - Returns: - logits (mx.array): Logits with repetition penalty applied to generated tokens. - """ - if len(tokens) > 0: - selected_logits = logits[:, tokens] - selected_logits = mx.where( - selected_logits < 0, selected_logits * penalty, selected_logits / penalty - ) - logits[:, tokens] = selected_logits - return logits - - def maybe_quantize_kv_cache(prompt_cache, quantized_kv_start, kv_group_size, kv_bits): if ( kv_bits is not None @@ -185,7 +165,7 @@ def generate_step( max_kv_size: Optional[int] = None, prompt_cache: Optional[Any] = None, logit_bias: Optional[Dict[int, float]] = None, - logits_processor: Optional[List[Callable[[mx.array, mx.array], mx.array]]] = None, + logits_processors: Optional[List[Callable[[mx.array, mx.array], mx.array]]] = None, kv_bits: Optional[int] = None, kv_group_size: int = 64, quantized_kv_start: int = 0, @@ -214,7 +194,7 @@ def generate_step( prompt_cache (List[Any], optional): A pre-computed prompt cache. Note, if provided, the cache will be updated in place. logit_bias (dictionary, optional): Additive logit bias. - logits_processor (List[Callable[[mx.array, mx.array], mx.array]], optional): + logits_processors (List[Callable[[mx.array, mx.array], mx.array]], optional): A list of functions that take tokens and logits and return the processed logits. Default: ``None``. kv_bits (int, optional): Number of bits to use for KV cache quantization. @@ -224,53 +204,9 @@ def generate_step( when ``kv_bits`` is non-None. Default: ``0``. Yields: - Generator[Tuple[mx.array, mx.array], None, None]: A generator producing - one token and a vector of log probabilities. + Tuple[mx.array, mx.array]: One token and a vector of log probabilities. """ - def sample(logits: mx.array) -> Tuple[mx.array, float]: - logprobs = logits - mx.logsumexp(logits) - - if temp == 0: - token = mx.argmax(logits, axis=-1) - else: - if top_p > 0 and top_p < 1.0: - token = top_p_sampling(logits, top_p, temp) - elif min_p != 0.0: - token = min_p_sampling(logits, min_p, min_tokens_to_keep, temp) - else: - token = categorical_sampling(logits, temp) - - return token, logprobs - - if repetition_penalty and ( - repetition_penalty < 0 or not isinstance(repetition_penalty, float) - ): - raise ValueError( - f"repetition_penalty must be a non-negative float, got {repetition_penalty}" - ) - - logits_processor = logits_processor or [] - - if repetition_penalty: - - def repetition_penalty_processor(tokens, logits): - return apply_repetition_penalty( - logits, tokens[-repetition_context_size:], repetition_penalty - ) - - logits_processor.append(repetition_penalty_processor) - - if logit_bias: - indices = mx.array(list(logit_bias.keys())) - values = mx.array(list(logit_bias.values())) - - def logit_bias_processor(_, logits): - logits[:, indices] += values - return logits - - logits_processor.append(logit_bias_processor) - y = prompt tokens = None @@ -283,24 +219,31 @@ def generate_step( elif len(prompt_cache) != len(model.layers): raise ValueError("Wrong number of layers in the prompt cache.") + sampler = make_sampler(temp, top_p, min_p, min_tokens_to_keep) + logits_processors = logits_processors or [] + logits_processors.extend( + make_logits_processors(logit_bias, repetition_penalty, repetition_context_size) + ) + def _step(y): + with mx.stream(generation_stream): + logits = model(y[None], cache=prompt_cache) + logits = logits[:, -1, :] - logits = model(y[None], cache=prompt_cache) - logits = logits[:, -1, :] + if logits_processors: + nonlocal tokens + tokens = mx.concat([tokens, y]) if tokens is not None else y - if logits_processor: - nonlocal tokens - tokens = mx.concat([tokens, y]) if tokens is not None else y + for processor in logits_processors: + logits = processor(tokens, logits) - for processor in logits_processor: - logits = processor(tokens, logits) + maybe_quantize_kv_cache( + prompt_cache, quantized_kv_start, kv_group_size, kv_bits + ) - maybe_quantize_kv_cache( - prompt_cache, quantized_kv_start, kv_group_size, kv_bits - ) - - y, logprobs = sample(logits) - return y, logprobs.squeeze(0) + logprobs = logits - mx.logsumexp(logits, keepdims=True) + y = sampler(logprobs) + return y, logprobs.squeeze(0) while y.size > prefill_step_size: model(y[:prefill_step_size][None], cache=prompt_cache) @@ -325,43 +268,51 @@ def generate_step( def stream_generate( model: nn.Module, tokenizer: Union[PreTrainedTokenizer, TokenizerWrapper], - prompt: str, + prompt: Union[str, List[int]], max_tokens: int = 100, **kwargs, -) -> Union[str, Generator[str, None, None]]: +) -> Generator[Tuple[str, int, mx.array], None, None]: """ A generator producing text based on the given prompt from the model. Args: - prompt (mx.array): The input prompt. model (nn.Module): The model to use for generation. - max_tokens (int): The ma + tokenizer (PreTrainedTokenizer): The tokenizer. + prompt (Union[str, List[int]]): The input prompt string or integer tokens. + max_tokens (int): The maximum number of tokens. Default: ``100``. kwargs: The remaining options get passed to :func:`generate_step`. See :func:`generate_step` for more details. Yields: - Generator[Tuple[mx.array, mx.array]]: A generator producing text. + Tuple[str, int, mx.array]: + The next text segment, token, and vector of log probabilities. """ if not isinstance(tokenizer, TokenizerWrapper): tokenizer = TokenizerWrapper(tokenizer) - prompt_tokens = mx.array(tokenizer.encode(prompt)) + prompt_tokens = mx.array( + prompt if isinstance(prompt, list) else tokenizer.encode(prompt) + ) detokenizer = tokenizer.detokenizer - detokenizer.reset() - for n, (token, _) in zip( - range(max_tokens), - generate_step(prompt_tokens, model, **kwargs), - ): - if token == tokenizer.eos_token_id: - break - detokenizer.add_token(token) + with wired_limit(model, [generation_stream]): + detokenizer.reset() + for n, (token, logits) in zip( + range(max_tokens), + generate_step(prompt_tokens, model, **kwargs), + ): + if token == tokenizer.eos_token_id: + break - # Yield the last segment if streaming - yield detokenizer.last_segment + detokenizer.add_token(token) - detokenizer.finalize() - yield detokenizer.last_segment + if n == (max_tokens - 1): + break + + yield detokenizer.last_segment, token, logits + + detokenizer.finalize() + yield detokenizer.last_segment, token, logits def generate( @@ -372,7 +323,7 @@ def generate( verbose: bool = False, formatter: Optional[Callable] = None, **kwargs, -) -> Union[str, Generator[str, None, None]]: +) -> str: """ Generate a complete response from the model. @@ -398,7 +349,7 @@ def generate( prompt_tokens = mx.array(tokenizer.encode(prompt)) detokenizer = tokenizer.detokenizer - with wired_limit(model): + with wired_limit(model, [generation_stream]): tic = time.perf_counter() detokenizer.reset() for n, (token, logprobs) in zip( @@ -416,8 +367,7 @@ def generate( if formatter: # We have to finalize so that the prob corresponds to the last segment detokenizer.finalize() - with mx.stream(mx.cpu): - prob = mx.exp(logprobs[token]).item() + prob = mx.exp(logprobs[token]).item() formatter(detokenizer.last_segment, prob) else: print(detokenizer.last_segment, end="", flush=True) @@ -438,7 +388,7 @@ def generate( f"Prompt: {prompt_tokens.size} tokens, {prompt_tps:.3f} tokens-per-sec" ) print(f"Generation: {token_count} tokens, {gen_tps:.3f} tokens-per-sec") - peak_mem = mx.metal.get_peak_memory() / 2**30 + peak_mem = mx.metal.get_peak_memory() / 1e9 print(f"Peak memory: {peak_mem:.3f} GB") return detokenizer.text @@ -623,7 +573,9 @@ def upload_to_hub(path: str, upload_repo: str, hf_path: str): f""" # {upload_repo} - The Model [{upload_repo}](https://huggingface.co/{upload_repo}) was converted to MLX format from [{hf_path}](https://huggingface.co/{hf_path}) using mlx-lm version **{__version__}**. + The Model [{upload_repo}](https://huggingface.co/{upload_repo}) was + converted to MLX format from [{hf_path}](https://huggingface.co/{hf_path}) + using mlx-lm version **{__version__}**. ## Use with mlx diff --git a/llms/tests/test_generate.py b/llms/tests/test_generate.py index 68f1670b..e0a372a9 100644 --- a/llms/tests/test_generate.py +++ b/llms/tests/test_generate.py @@ -46,7 +46,7 @@ class TestGenerate(unittest.TestCase): "hello", max_tokens=5, verbose=False, - logits_processor=[logits_processor], + logits_processors=[logits_processor], ) self.assertEqual(len(all_toks), len(init_toks) + 5) diff --git a/llms/tests/test_prompt_cache.py b/llms/tests/test_prompt_cache.py index 1e57bd86..0867ab56 100644 --- a/llms/tests/test_prompt_cache.py +++ b/llms/tests/test_prompt_cache.py @@ -299,7 +299,7 @@ class TestPromptCache(unittest.TestCase): ): i += 1 self.assertEqual(tok, toks[i]) - self.assertTrue(mx.allclose(logits, all_logits[i], rtol=1e-2)) + self.assertTrue(mx.allclose(logits, all_logits[i], rtol=2e-2)) if __name__ == "__main__": From 1e0766018494c46bc6078769278b8e2a360503dc Mon Sep 17 00:00:00 2001 From: madroid Date: Sat, 9 Nov 2024 09:15:19 +0800 Subject: [PATCH 078/188] FLUX: save train config (#1049) --- flux/README.md | 2 +- flux/dreambooth.py | 15 +++++++++++---- flux/flux/__init__.py | 1 + flux/flux/utils.py | 23 ++++++++++++++++++++++- 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/flux/README.md b/flux/README.md index 1a17e386..b00a9621 100644 --- a/flux/README.md +++ b/flux/README.md @@ -188,7 +188,7 @@ The adapters are saved in `mlx_output` and can be used directly by the ```shell python txt2image.py --model dev --save-raw --image-size 512x512 --n-images 1 \ - --adapter mlx_output/0001200_adapters.safetensors \ + --adapter mlx_output/final_adapters.safetensors \ --fuse-adapter \ --no-t5-padding \ 'A photo of an sks dog lying on the sand at a beach in Greece' diff --git a/flux/dreambooth.py b/flux/dreambooth.py index 48dcad47..ffdb02d7 100644 --- a/flux/dreambooth.py +++ b/flux/dreambooth.py @@ -13,7 +13,7 @@ from mlx.nn.utils import average_gradients from mlx.utils import tree_flatten, tree_map, tree_reduce from PIL import Image -from flux import FluxPipeline, Trainer, load_dataset +from flux import FluxPipeline, Trainer, load_dataset, save_config def generate_progress_images(iteration, flux, args): @@ -43,10 +43,10 @@ def generate_progress_images(iteration, flux, args): im.save(out_file) -def save_adapters(iteration, flux, args): +def save_adapters(adapter_name, flux, args): out_dir = Path(args.output_dir) out_dir.mkdir(parents=True, exist_ok=True) - out_file = out_dir / f"{iteration:07d}_adapters.safetensors" + out_file = out_dir / adapter_name print(f"Saving {str(out_file)}") mx.save_safetensors( @@ -157,6 +157,10 @@ if __name__ == "__main__": parser = setup_arg_parser() args = parser.parse_args() + output_path = Path(args.output_dir) + output_path.mkdir(parents=True, exist_ok=True) + save_config(vars(args), output_path / "adapter_config.json") + # Load the model and set it up for LoRA training. We use the same random # state when creating the LoRA layers so all workers will have the same # initial weights. @@ -278,8 +282,11 @@ if __name__ == "__main__": generate_progress_images(i + 1, flux, args) if (i + 1) % args.checkpoint_every == 0: - save_adapters(i + 1, flux, args) + save_adapters(f"{i + 1:07d}_adapters.safetensors", flux, args) if (i + 1) % 10 == 0: losses = [] tic = time.time() + + save_adapters("final_adapters.safetensors", flux, args) + print(f"Training successful. Saved final weights to {args.adapter_file}.") diff --git a/flux/flux/__init__.py b/flux/flux/__init__.py index b1122d75..3dd423b7 100644 --- a/flux/flux/__init__.py +++ b/flux/flux/__init__.py @@ -12,4 +12,5 @@ from .utils import ( load_flow_model, load_t5, load_t5_tokenizer, + save_config, ) diff --git a/flux/flux/utils.py b/flux/flux/utils.py index 21db17d3..2437f21f 100644 --- a/flux/flux/utils.py +++ b/flux/flux/utils.py @@ -3,7 +3,8 @@ import json import os from dataclasses import dataclass -from typing import Optional +from pathlib import Path +from typing import Optional, Union import mlx.core as mx from huggingface_hub import hf_hub_download @@ -207,3 +208,23 @@ def load_clip_tokenizer(name: str): def load_t5_tokenizer(name: str, pad: bool = True): model_file = hf_hub_download(configs[name].repo_id, "tokenizer_2/spiece.model") return T5Tokenizer(model_file, 256 if "schnell" in name else 512) + + +def save_config( + config: dict, + config_path: Union[str, Path], +) -> None: + """Save the model configuration to the ``config_path``. + + The final configuration will be sorted before saving for better readability. + + Args: + config (dict): The model configuration. + config_path (Union[str, Path]): Model configuration file path. + """ + # Sort the config for better readability + config = dict(sorted(config.items())) + + # Write the config to the provided file + with open(config_path, "w") as fid: + json.dump(config, fid, indent=4) From bd6d910ca3744d75bf704e6e7039f97f71014bd5 Mon Sep 17 00:00:00 2001 From: Alban Lecocq Date: Wed, 13 Nov 2024 15:14:03 +0100 Subject: [PATCH 079/188] [MLX LM] Fix f-string formatting in memory warning message (#1105) * Fix missing f-prefix for string interpolation in model size warning * Ensures proper display of memory values in MB for model and max size --- llms/mlx_lm/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index 8893b570..d4afd428 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -61,8 +61,8 @@ def wired_limit(model: nn.Module, streams: Optional[List[mx.Stream]] = None): model_mb = model_bytes // 2**20 max_rec_mb = max_rec_size // 2**20 print( - "[WARNING] Generating with a model that requires {model_mb} MB " - "which is close to the maximum recommended size of {max_rec_mb} " + f"[WARNING] Generating with a model that requires {model_mb} MB " + f"which is close to the maximum recommended size of {max_rec_mb} " "MB. This can be slow. See the documentation for possible work-arounds: " "https://github.com/ml-explore/mlx-examples/tree/main/llms#large-models" ) From 60c7b803500df4dd84ef8b5ed70deace99272bc2 Mon Sep 17 00:00:00 2001 From: Valentin Roussellet Date: Wed, 20 Nov 2024 15:21:52 -0800 Subject: [PATCH 080/188] Pass seed to sd img2img (#1114) --- stable_diffusion/image2image.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/stable_diffusion/image2image.py b/stable_diffusion/image2image.py index e470aa81..a037af6a 100644 --- a/stable_diffusion/image2image.py +++ b/stable_diffusion/image2image.py @@ -30,6 +30,7 @@ if __name__ == "__main__": parser.add_argument("--preload-models", action="store_true") parser.add_argument("--output", default="out.png") parser.add_argument("--verbose", "-v", action="store_true") + parser.add_argument("--seed", type=int) args = parser.parse_args() # Load the models @@ -94,6 +95,7 @@ if __name__ == "__main__": cfg_weight=args.cfg, num_steps=args.steps, negative_text=args.negative_prompt, + seed=args.seed ) for x_t in tqdm(latents, total=int(args.steps * args.strength)): mx.eval(x_t) From 042280ce50645b69d9c322ccf1cb8471384007f1 Mon Sep 17 00:00:00 2001 From: Angelos Katharopoulos Date: Wed, 20 Nov 2024 16:15:53 -0800 Subject: [PATCH 081/188] Fix format (#1115) --- stable_diffusion/image2image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stable_diffusion/image2image.py b/stable_diffusion/image2image.py index a037af6a..4444c488 100644 --- a/stable_diffusion/image2image.py +++ b/stable_diffusion/image2image.py @@ -95,7 +95,7 @@ if __name__ == "__main__": cfg_weight=args.cfg, num_steps=args.steps, negative_text=args.negative_prompt, - seed=args.seed + seed=args.seed, ) for x_t in tqdm(latents, total=int(args.steps * args.strength)): mx.eval(x_t) From 004eb4cc9d3d390dbadb8eb015de7d28a788701b Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Sat, 23 Nov 2024 11:06:26 -0800 Subject: [PATCH 082/188] Tencent HunYuan MOE model (#1100) * hunyuan * fix * format str * default trust remote code for tokenizer, allow system prompt to be configurable --- llms/mlx_lm/generate.py | 24 +-- llms/mlx_lm/models/hunyuan.py | 291 ++++++++++++++++++++++++++++++++++ llms/tests/test_models.py | 32 ++++ 3 files changed, 337 insertions(+), 10 deletions(-) create mode 100644 llms/mlx_lm/models/hunyuan.py diff --git a/llms/mlx_lm/generate.py b/llms/mlx_lm/generate.py index 51169def..de5c5719 100644 --- a/llms/mlx_lm/generate.py +++ b/llms/mlx_lm/generate.py @@ -41,17 +41,17 @@ def setup_arg_parser(): type=str, help="Optional path for the trained adapter weights and config.", ) - parser.add_argument( - "--trust-remote-code", - action="store_true", - help="Enable trusting remote code for tokenizer", - ) parser.add_argument( "--eos-token", type=str, default=None, help="End of sequence token for tokenizer", ) + parser.add_argument( + "--system-prompt", + default=None, + help="System prompt to be used for the chat template", + ) parser.add_argument( "--prompt", "-p", @@ -191,8 +191,7 @@ def main(): tokenizer_config = ( {} if not using_cache else json.loads(metadata["tokenizer_config"]) ) - if args.trust_remote_code: - tokenizer_config["trust_remote_code"] = True + tokenizer_config["trust_remote_code"] = True if args.eos_token is not None: tokenizer_config["eos_token"] = args.eos_token @@ -224,12 +223,16 @@ def main(): hasattr(tokenizer, "apply_chat_template") and tokenizer.chat_template is not None ): - messages = [ + if args.system_prompt is not None: + messages = [{"role": "system", "content": args.system_prompt}] + else: + messages = [] + messages.append( { "role": "user", "content": sys.stdin.read() if args.prompt == "-" else args.prompt, } - ] + ) prompt = tokenizer.apply_chat_template( messages, tokenize=False, add_generation_prompt=True ) @@ -237,8 +240,9 @@ def main(): # Treat the prompt as a suffix assuming that the prefix is in the # stored kv cache. if using_cache: + messages[-1]["content"] = "" test_prompt = tokenizer.apply_chat_template( - [{"role": "user", "content": ""}], + messages, tokenize=False, add_generation_prompt=True, ) diff --git a/llms/mlx_lm/models/hunyuan.py b/llms/mlx_lm/models/hunyuan.py new file mode 100644 index 00000000..b098c20d --- /dev/null +++ b/llms/mlx_lm/models/hunyuan.py @@ -0,0 +1,291 @@ +# Copyright © 2023-2024 Apple Inc. + +import math +from dataclasses import dataclass +from typing import Any, Dict, Optional, Tuple, Union + +import mlx.core as mx +import mlx.nn as nn + +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention +from .switch_layers import SwitchGLU + + +@dataclass +class ModelArgs(BaseModelArgs): + model_type: str + vocab_size: int + hidden_size: int + num_hidden_layers: int + intermediate_size: int + num_attention_heads: int + num_key_value_heads: int + attention_bias: bool + moe_topk: int + num_experts: int + num_shared_expert: int + use_mixed_mlp_moe: bool + use_qk_norm: bool + rms_norm_eps: float + rope_theta: float + use_cla: bool + cla_share_factor: 2 + rope_scaling: Optional[Dict[str, Union[float, str]]] = None + tie_word_embeddings: bool = False + + def __post_init__(self): + + if self.rope_scaling: + required_keys = {"factor", "type"} + if not all(key in self.rope_scaling for key in required_keys): + raise ValueError(f"rope_scaling must contain keys {required_keys}") + + +class DynamicNTKAlphaRoPE(nn.Module): + def __init__( + self, + dims: int, + base: float = 10000, + scaling_alpha: float = 1.0, + ): + super().__init__() + self.dims = dims + base = base * scaling_alpha ** (dims / (dims - 2)) + self._freqs = base ** (mx.arange(0, self.dims, 2) / self.dims) + + def __call__(self, x, offset: int = 0): + return mx.fast.rope( + x, + self.dims, + traditional=False, + base=None, + scale=1.0, + offset=offset, + freqs=self._freqs, + ) + + +class Attention(nn.Module): + def __init__(self, kv_proj: bool, args: ModelArgs): + super().__init__() + + dim = args.hidden_size + self.n_heads = n_heads = args.num_attention_heads + assert args.num_key_value_heads is not None + self.n_kv_heads = n_kv_heads = args.num_key_value_heads + + head_dim = args.hidden_size // n_heads + self.scale = head_dim**-0.5 + + self.q_proj = nn.Linear(dim, n_heads * head_dim, bias=args.attention_bias) + if kv_proj: + self.k_proj = nn.Linear( + dim, n_kv_heads * head_dim, bias=args.attention_bias + ) + self.v_proj = nn.Linear( + dim, n_kv_heads * head_dim, bias=args.attention_bias + ) + self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=args.attention_bias) + self.use_qk_norm = args.use_qk_norm + if self.use_qk_norm: + self.query_layernorm = nn.RMSNorm(head_dim, args.rms_norm_eps) + self.key_layernorm = nn.RMSNorm(head_dim, args.rms_norm_eps) + + self.rope = DynamicNTKAlphaRoPE( + head_dim, + base=args.rope_theta, + scaling_alpha=args.rope_scaling["alpha"], + ) + + def __call__( + self, + x: mx.array, + mask: Optional[mx.array] = None, + cache: Optional[Any] = None, + kv_states=None, + ) -> mx.array: + B, L, D = x.shape + + queries = self.q_proj(x) + + if kv_states is None: + keys, values = self.k_proj(x), self.v_proj(x) + kv_states = keys, values + else: + keys, values = kv_states + + # Prepare the queries, keys and values for the attention computation + queries = queries.reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) + keys = keys.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) + values = values.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) + + offset = cache.offset if cache else 0 + queries = self.rope(queries, offset=offset) + keys = self.rope(keys, offset=offset) + if self.use_qk_norm: + queries = self.query_layernorm(queries) + keys = self.key_layernorm(keys) + + if cache is not None: + keys, values = cache.update_and_fetch(keys, values) + + output = scaled_dot_product_attention( + queries, keys, values, cache=cache, scale=self.scale, mask=mask + ) + output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) + return self.o_proj(output), kv_states + + +class MLP(nn.Module): + def __init__(self, dim, hidden_dim): + super().__init__() + self.gate_proj = nn.Linear(dim, hidden_dim, bias=False) + self.down_proj = nn.Linear(hidden_dim, dim, bias=False) + self.up_proj = nn.Linear(dim, hidden_dim, bias=False) + + def __call__(self, x) -> mx.array: + return self.down_proj(nn.silu(self.gate_proj(x)) * self.up_proj(x)) + + +class Gate(nn.Module): + def __init__(self, dim, num_experts): + super().__init__() + self.wg = nn.Linear(dim, num_experts, bias=False) + + def __call__(self, x) -> mx.array: + return self.wg(x) + + +class MoeBlock(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + dim = args.hidden_size + intermediate_size = args.intermediate_size + self.use_shared_mlp = args.use_mixed_mlp_moe + + if args.use_mixed_mlp_moe: + self.shared_mlp = MLP(dim, intermediate_size * args.num_shared_expert) + + self.num_experts = num_experts = args.num_experts + self.top_k = args.moe_topk + + self.gate = Gate(dim, num_experts) + self.switch_mlp = SwitchGLU(dim, intermediate_size, num_experts) + + def __call__( + self, + x: mx.array, + ): + gates = self.gate(x) + gates = mx.softmax(gates, axis=-1, precise=True) + + k = self.top_k + inds = mx.stop_gradient(mx.argpartition(-gates, kth=k - 1, axis=-1)[..., :k]) + scores = mx.take_along_axis(gates, inds, axis=-1) + + y = self.switch_mlp(x, inds) + y = (y * scores[..., None]).sum(axis=-2) + + if self.use_shared_mlp: + shared_expert_output = self.shared_mlp(x) + y = y + shared_expert_output + + return y + + +class DecoderLayer(nn.Module): + def __init__(self, args: ModelArgs, kv_proj: bool): + super().__init__() + self.hidden_size = args.hidden_size + self.self_attn = Attention(kv_proj, args) + self.mlp = MoeBlock(args) + + self.input_layernorm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) + self.post_attention_layernorm = nn.RMSNorm( + args.hidden_size, eps=args.rms_norm_eps + ) + self.args = args + + def __call__( + self, + x: mx.array, + mask: Optional[mx.array] = None, + cache: Optional[Any] = None, + shared_kv_states: Optional[Tuple[mx.array, mx.array]] = None, + ): + r, shared_kv_states = self.self_attn( + self.input_layernorm(x), mask, cache, shared_kv_states + ) + h = x + r + r = self.mlp(self.post_attention_layernorm(h)) + out = h + r + return out, shared_kv_states + + +class HunYuanModel(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.args = args + self.vocab_size = args.vocab_size + self.num_hidden_layers = args.num_hidden_layers + assert self.vocab_size > 0 + self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size) + self.layers = [ + DecoderLayer(args=args, kv_proj=(i % args.cla_share_factor) == 0) + for i in range(args.num_hidden_layers) + ] + self.norm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) + + def __call__( + self, + inputs: mx.array, + cache=None, + ): + h = self.embed_tokens(inputs) + + mask = create_attention_mask(h, cache) + + if cache is None: + cache = [None] * len(self.layers) + + for i, (layer, c) in enumerate(zip(self.layers, cache)): + if i % self.args.cla_share_factor == 0: + shared_kv_states = None + h, shared_kv_states = layer(h, mask, c, shared_kv_states) + + return self.norm(h) + + +class Model(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.args = args + self.model_type = args.model_type + self.model = HunYuanModel(args) + + def __call__( + self, + inputs: mx.array, + cache=None, + ): + out = self.model(inputs, cache) + return self.model.embed_tokens.as_linear(out) + + def sanitize(self, weights): + if "model.layers.0.mlp.experts.0.up_proj.weight" not in weights: + return weights + for l in range(self.args.num_hidden_layers): + prefix = f"model.layers.{l}" + for n in ["up_proj", "down_proj", "gate_proj"]: + for k in ["weight", "scales", "biases"]: + if f"{prefix}.mlp.experts.0.{n}.{k}" in weights: + to_join = [ + weights.pop(f"{prefix}.mlp.experts.{e}.{n}.{k}") + for e in range(self.args.num_experts) + ] + weights[f"{prefix}.mlp.switch_mlp.{n}.{k}"] = mx.stack(to_join) + return weights + + @property + def layers(self): + return self.model.layers diff --git a/llms/tests/test_models.py b/llms/tests/test_models.py index 1efde5ae..93b881b9 100644 --- a/llms/tests/test_models.py +++ b/llms/tests/test_models.py @@ -760,6 +760,38 @@ class TestModels(unittest.TestCase): model, args.model_type, args.vocab_size, args.num_hidden_layers ) + def test_hunyuan(self): + from mlx_lm.models import hunyuan + + args = hunyuan.ModelArgs( + model_type="hunyuan", + hidden_size=128, + attention_bias=False, + intermediate_size=256, + num_attention_heads=4, + num_hidden_layers=4, + num_key_value_heads=2, + rms_norm_eps=1e-4, + rope_theta=1000, + vocab_size=1000, + moe_topk=2, + num_experts=2, + num_shared_expert=1, + use_mixed_mlp_moe=True, + use_qk_norm=True, + rope_scaling={ + "alpha": 1000.0, + "factor": 1.0, + "type": "dynamic", + }, + use_cla=True, + cla_share_factor=2, + ) + model = hunyuan.Model(args) + self.model_test_runner( + model, args.model_type, args.vocab_size, args.num_hidden_layers + ) + if __name__ == "__main__": unittest.main() From 0f135396ae7fcb2bad407d6a41296ac84c0fb666 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Sat, 23 Nov 2024 11:47:06 -0800 Subject: [PATCH 083/188] Generation refactor: part 2 (#1099) * unify with stream_generate * fixes * nit * some cleanup, warnings, tests * fix test + faster min p + test * version --- llms/README.md | 11 +- llms/mlx_lm/_version.py | 2 +- llms/mlx_lm/chat.py | 10 +- llms/mlx_lm/examples/chat.py | 1 - llms/mlx_lm/examples/generate_response.py | 9 - llms/mlx_lm/generate.py | 46 +---- llms/mlx_lm/sample_utils.py | 22 +-- llms/mlx_lm/server.py | 42 ++--- llms/mlx_lm/tokenizer_utils.py | 11 +- llms/mlx_lm/utils.py | 203 ++++++++++++---------- llms/tests/test_generate.py | 3 +- llms/tests/test_sample_utils.py | 18 +- llms/tests/test_tokenizers.py | 3 +- 13 files changed, 184 insertions(+), 197 deletions(-) diff --git a/llms/README.md b/llms/README.md index eeb3ed6a..60f68353 100644 --- a/llms/README.md +++ b/llms/README.md @@ -61,7 +61,7 @@ prompt = tokenizer.apply_chat_template( messages, tokenize=False, add_generation_prompt=True ) -response = generate(model, tokenizer, prompt=prompt, verbose=True) +text = generate(model, tokenizer, prompt=prompt, verbose=True) ``` To see a description of all the arguments you can do: @@ -100,8 +100,9 @@ To see a description of all the arguments you can do: #### Streaming -For streaming generation, use the `stream_generate` function. This returns a -generator object which streams the output text, token, and log probabilities. +For streaming generation, use the `stream_generate` function. This yields +a generation response object. + For example, ```python @@ -117,8 +118,8 @@ prompt = tokenizer.apply_chat_template( messages, tokenize=False, add_generation_prompt=True ) -for text, *_ in stream_generate(model, tokenizer, prompt, max_tokens=512): - print(t, end="", flush=True) +for response in stream_generate(model, tokenizer, prompt, max_tokens=512): + print(response.text, end="", flush=True) print() ``` diff --git a/llms/mlx_lm/_version.py b/llms/mlx_lm/_version.py index 3811616f..5168eee4 100644 --- a/llms/mlx_lm/_version.py +++ b/llms/mlx_lm/_version.py @@ -1,3 +1,3 @@ # Copyright © 2023-2024 Apple Inc. -__version__ = "0.19.3" +__version__ = "0.20.0" diff --git a/llms/mlx_lm/chat.py b/llms/mlx_lm/chat.py index c03056a6..7795d8d7 100644 --- a/llms/mlx_lm/chat.py +++ b/llms/mlx_lm/chat.py @@ -5,7 +5,8 @@ import json import mlx.core as mx -from .models.cache import load_prompt_cache, make_prompt_cache, save_prompt_cache +from .models.cache import make_prompt_cache +from .sample_utils import make_sampler from .utils import load, stream_generate DEFAULT_TEMP = 0.0 @@ -74,16 +75,15 @@ def main(): prompt = tokenizer.apply_chat_template( messages, tokenize=False, add_generation_prompt=True ) - for response, *_ in stream_generate( + for response in stream_generate( model, tokenizer, prompt, args.max_tokens, - temp=args.temp, - top_p=args.top_p, + sampler=make_sampler(args.temp, args.top_p), prompt_cache=prompt_cache, ): - print(response, flush=True, end="") + print(response.text, flush=True, end="") print() diff --git a/llms/mlx_lm/examples/chat.py b/llms/mlx_lm/examples/chat.py index 3bf01688..c7512b3c 100644 --- a/llms/mlx_lm/examples/chat.py +++ b/llms/mlx_lm/examples/chat.py @@ -42,7 +42,6 @@ response = generate( tokenizer, prompt=prompt, verbose=True, - temp=0.0, prompt_cache=prompt_cache, ) diff --git a/llms/mlx_lm/examples/generate_response.py b/llms/mlx_lm/examples/generate_response.py index 25730617..e6535b47 100644 --- a/llms/mlx_lm/examples/generate_response.py +++ b/llms/mlx_lm/examples/generate_response.py @@ -23,14 +23,6 @@ max_tokens = 1_000 # Specify if tokens and timing information will be printed verbose = True -# Some optional arguments for causal language model generation -generation_args = { - "temp": 0.7, - "repetition_penalty": 1.2, - "repetition_context_size": 20, - "top_p": 0.95, -} - # Generate a response with the specified settings response = generate( model=model, @@ -38,5 +30,4 @@ response = generate( prompt=prompt, max_tokens=max_tokens, verbose=verbose, - **generation_args, ) diff --git a/llms/mlx_lm/generate.py b/llms/mlx_lm/generate.py index de5c5719..9e96fbdc 100644 --- a/llms/mlx_lm/generate.py +++ b/llms/mlx_lm/generate.py @@ -7,6 +7,7 @@ import sys import mlx.core as mx from .models.cache import QuantizedKVCache, load_prompt_cache +from .sample_utils import make_sampler from .utils import generate, load DEFAULT_PROMPT = "hello" @@ -97,11 +98,6 @@ def setup_arg_parser(): default=True, help="Log verbose output when 'True' or 'T' or only print the response when 'False' or 'F'", ) - parser.add_argument( - "--colorize", - action="store_true", - help="Colorize output based on T[0] probability", - ) parser.add_argument( "--max-kv-size", type=int, @@ -137,33 +133,6 @@ def setup_arg_parser(): return parser -def colorprint(color, s): - color_codes = { - "black": 30, - "red": 31, - "green": 32, - "yellow": 33, - "blue": 34, - "magenta": 35, - "cyan": 36, - "white": 39, - } - ccode = color_codes.get(color, 30) - print(f"\033[1m\033[{ccode}m{s}\033[0m", end="", flush=True) - - -def colorprint_by_t0(s, t0): - if t0 > 0.95: - color = "white" - elif t0 > 0.70: - color = "green" - elif t0 > 0.30: - color = "yellow" - else: - color = "red" - colorprint(color, s) - - def main(): parser = setup_arg_parser() args = parser.parse_args() @@ -250,21 +219,14 @@ def main(): else: prompt = args.prompt - if args.colorize and not args.verbose: - raise ValueError("Cannot use --colorize with --verbose=False") - formatter = colorprint_by_t0 if args.colorize else None - + sampler = make_sampler(args.temp, args.top_p, args.min_p, args.min_tokens_to_keep) response = generate( model, tokenizer, prompt, - args.max_tokens, + max_tokens=args.max_tokens, verbose=args.verbose, - formatter=formatter, - temp=args.temp, - top_p=args.top_p, - min_p=args.min_p, - min_tokens_to_keep=args.min_tokens_to_keep, + sampler=sampler, max_kv_size=args.max_kv_size, prompt_cache=prompt_cache if using_cache else None, kv_bits=args.kv_bits, diff --git a/llms/mlx_lm/sample_utils.py b/llms/mlx_lm/sample_utils.py index c27b52d8..f9868422 100644 --- a/llms/mlx_lm/sample_utils.py +++ b/llms/mlx_lm/sample_utils.py @@ -1,5 +1,6 @@ # Copyright © 2023-2024 Apple Inc. +import math from functools import partial from typing import Callable, Dict, Optional @@ -80,7 +81,7 @@ def make_logits_processors( @partial(mx.compile, inputs=mx.random.state, outputs=mx.random.state) def min_p_sampling( - logits: mx.array, + logprobs: mx.array, min_p: float, min_tokens_to_keep: int = 1, temperature=1.0, @@ -93,7 +94,7 @@ def min_p_sampling( aggressive given a very high-probability token. Args: - logits: The logits from the model's output. + logprobs: A vector of log probabilities. min_p (float): Minimum token probability. Typical values are in the 0.01-0.2 range, comparably selective as setting `top_p` in the 0.99-0.8 range. @@ -111,28 +112,27 @@ def min_p_sampling( ) # reference implementation: https://github.com/huggingface/transformers/blob/main/src/transformers/generation/logits_process.py#L531-L605 - # Softmax probabilities - probs = mx.softmax(logits * (1 / temperature), axis=-1) + logprobs = logprobs * (1 / temperature) # Indices sorted in decreasing order - sorted_indices = mx.argsort(-logits).squeeze(0) - sorted_probs = probs[..., sorted_indices] + sorted_indices = mx.argsort(-logprobs).squeeze(0) + sorted_logprobs = logprobs[..., sorted_indices] # Top probability - top_probs = probs[..., sorted_indices[0]] + top_logprobs = logprobs[..., sorted_indices[0]] # Calculate the min_p threshold - scaled_min_p = min_p * top_probs + scaled_min_p = top_logprobs + math.log(min_p) # Mask tokens that have a probability less than the scaled min_p - tokens_to_remove = sorted_probs < scaled_min_p + tokens_to_remove = sorted_logprobs < scaled_min_p tokens_to_remove[..., :min_tokens_to_keep] = False # Create pool of tokens with probability less than scaled min_p - selected_probs = mx.where(tokens_to_remove, 0, sorted_probs) + selected_logprobs = mx.where(tokens_to_remove, -float("inf"), sorted_logprobs) # Return sampled token - sorted_token = mx.random.categorical(mx.log(selected_probs)) + sorted_token = mx.random.categorical(selected_logprobs) return sorted_indices[sorted_token] diff --git a/llms/mlx_lm/server.py b/llms/mlx_lm/server.py index c1365b36..badc6dd3 100644 --- a/llms/mlx_lm/server.py +++ b/llms/mlx_lm/server.py @@ -27,6 +27,7 @@ from huggingface_hub import scan_cache_dir from ._version import __version__ from .models.cache import make_prompt_cache +from .sample_utils import make_logits_processors, make_sampler from .utils import load, stream_generate @@ -464,25 +465,24 @@ class APIHandler(BaseHTTPRequestHandler): text = "" tic = time.perf_counter() - for n, (segment, token, logprobs) in enumerate( - stream_generate( - model=self.model, - tokenizer=self.tokenizer, - prompt=prompt, - max_tokens=self.max_tokens, - temp=self.temperature, - repetition_penalty=self.repetition_penalty, - repetition_context_size=self.repetition_context_size, - logit_bias=self.logit_bias, - prompt_cache=self.prompt_cache.cache, - ), + sampler = make_sampler(self.temperature) + logits_processors = make_logits_processors( + self.logit_bias, self.repetition_penalty, self.repetition_context_size + ) + for gen_response in stream_generate( + model=self.model, + tokenizer=self.tokenizer, + prompt=prompt, + max_tokens=self.max_tokens, + sampler=sampler, + logits_processors=logits_processors, + prompt_cache=self.prompt_cache.cache, ): - if n == 0: - prompt_time = time.perf_counter() - tic - tic = time.perf_counter() - + segment = gen_response.text text += segment logging.debug(text) + token = gen_response.token + logprobs = gen_response.logprobs tokens.append(token) if self.logprobs > 0: @@ -523,13 +523,9 @@ class APIHandler(BaseHTTPRequestHandler): self.prompt_cache.tokens.extend(tokens) - gen_time = time.perf_counter() - tic - prompt_tps = len(prompt) / prompt_time - gen_tps = len(tokens) / gen_time - peak_mem = mx.metal.get_peak_memory() / 1e9 - logging.debug(f"Prompt: {prompt_tps:.3f} tokens-per-sec") - logging.debug(f"Generation: {gen_tps:.3f} tokens-per-sec") - logging.debug(f"Peak memory: {peak_mem:.3f} GB") + logging.debug(f"Prompt: {gen_response.prompt_tps:.3f} tokens-per-sec") + logging.debug(f"Generation: {gen_response.generation_tps:.3f} tokens-per-sec") + logging.debug(f"Peak memory: {gen_response.peak_memory:.3f} GB") if self.stream: response = self.generate_response(segment, finish_reason) diff --git a/llms/mlx_lm/tokenizer_utils.py b/llms/mlx_lm/tokenizer_utils.py index 9d390733..0fa41ac0 100644 --- a/llms/mlx_lm/tokenizer_utils.py +++ b/llms/mlx_lm/tokenizer_utils.py @@ -73,16 +73,16 @@ class NaiveStreamingDetokenizer(StreamingDetokenizer): def reset(self): self.offset = 0 - self._tokens = [] + self.tokens = [] self._text = "" self._current_tokens = [] self._current_text = "" def add_token(self, token): self._current_tokens.append(token) + self.tokens.append(token) def finalize(self): - self._tokens.extend(self._current_tokens) self._text += self._tokenizer.decode(self._current_tokens) self._current_tokens = [] self._current_text = "" @@ -97,16 +97,11 @@ class NaiveStreamingDetokenizer(StreamingDetokenizer): ): self._current_text = self._current_text[:-1] if self._current_text and self._current_text[-1] == "\n": - self._tokens.extend(self._current_tokens) self._text += self._current_text self._current_tokens.clear() self._current_text = "" return self._text + self._current_text - @property - def tokens(self): - return self._tokens - class SPMStreamingDetokenizer(StreamingDetokenizer): """A streaming detokenizer for SPM models. @@ -143,6 +138,7 @@ class SPMStreamingDetokenizer(StreamingDetokenizer): self.text += text def add_token(self, token): + self.tokens.append(token) v = self.tokenmap[token] if v.startswith(self._sep): self._flush() @@ -200,6 +196,7 @@ class BPEStreamingDetokenizer(StreamingDetokenizer): return current_text def add_token(self, token): + self.tokens.append(token) v = self.tokenmap[token] is_added = token in self._added_ids if is_added or self._byte_decoder[v[0]] == 32: diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index d4afd428..496ae4fc 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -8,6 +8,7 @@ import json import logging import shutil import time +from dataclasses import dataclass from pathlib import Path from textwrap import dedent from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Type, Union @@ -44,6 +45,32 @@ class ModelNotFoundError(Exception): super().__init__(self.message) +@dataclass +class GenerationResponse: + """ + The output of :func:`stream_generate`. + + Args: + text (str): The next segment of decoded text. This can be an empty string. + token (int): The next token. + logprobs (mx.array): A vector of log probabilities. + prompt_tokens (int): The number of tokens in the prompt. + prompt_tps (float): The prompt processing tokens-per-second. + generation_tokens (int): The number of generated tokens. + generation_tps (float): The tokens-per-second for generation. + peak_memory (float): The peak memory used so far in GB. + """ + + text: str + token: int + logprobs: mx.array + prompt_tokens: int + prompt_tps: float + generation_tokens: int + generation_tps: float + peak_memory: float + + @contextlib.contextmanager def wired_limit(model: nn.Module, streams: Optional[List[mx.Stream]] = None): """ @@ -155,20 +182,21 @@ def maybe_quantize_kv_cache(prompt_cache, quantized_kv_start, kv_group_size, kv_ def generate_step( prompt: mx.array, model: nn.Module, - temp: float = 0.0, - repetition_penalty: Optional[float] = None, - repetition_context_size: Optional[int] = 20, - top_p: float = 1.0, - min_p: float = 0.0, - min_tokens_to_keep: int = 1, - prefill_step_size: int = 512, + *, + sampler: Optional[Callable[mx.array, mx.array]] = None, + logits_processors: Optional[List[Callable[[mx.array, mx.array], mx.array]]] = None, max_kv_size: Optional[int] = None, prompt_cache: Optional[Any] = None, - logit_bias: Optional[Dict[int, float]] = None, - logits_processors: Optional[List[Callable[[mx.array, mx.array], mx.array]]] = None, + prefill_step_size: int = 512, kv_bits: Optional[int] = None, kv_group_size: int = 64, quantized_kv_start: int = 0, + temp: Optional[float] = None, + repetition_penalty: Optional[float] = None, + repetition_context_size: Optional[int] = None, + top_p: Optional[float] = None, + min_p: Optional[float] = None, + min_tokens_to_keep: Optional[int] = None, ) -> Generator[Tuple[mx.array, mx.array], None, None]: """ A generator producing token ids based on the given prompt from the model. @@ -176,32 +204,21 @@ def generate_step( Args: prompt (mx.array): The input prompt. model (nn.Module): The model to use for generation. - temp (float): The temperature for sampling, if 0 the argmax is used. - Default: ``0``. - repetition_penalty (float, optional): The penalty factor for repeating - tokens. - repetition_context_size (int, optional): The number of tokens to - consider for repetition penalty. Default: ``20``. - top_p (float, optional): Nulceus sampling, higher means model considers - more less likely words. - min_p (float, optional): The minimum value (scaled by the top token's - probability) that a token probability must have to be considered. - min_tokens_to_keep (int, optional): Minimum number of tokens that cannot - be filtered by min_p sampling. prefill_step_size (int): Step size for processing the prompt. max_kv_size (int, optional): Maximum size of the key-value cache. Old entries (except the first 4 tokens) will be overwritten. prompt_cache (List[Any], optional): A pre-computed prompt cache. Note, if provided, the cache will be updated in place. - logit_bias (dictionary, optional): Additive logit bias. + sampler (Callable[mx.array, mx.array], optional): A sampler for sampling a + token from a vector of log probabilities. Default: ``None``. logits_processors (List[Callable[[mx.array, mx.array], mx.array]], optional): - A list of functions that take tokens and logits and return the processed - logits. Default: ``None``. + A list of functions that take tokens and logits and return the processed + logits. Default: ``None``. kv_bits (int, optional): Number of bits to use for KV cache quantization. - None implies no cache quantization. Default: ``None``. + None implies no cache quantization. Default: ``None``. kv_group_size (int): Group size for KV cache quantization. Default: ``64``. quantized_kv_start (int): Step to begin using a quantized KV cache. - when ``kv_bits`` is non-None. Default: ``0``. + when ``kv_bits`` is non-None. Default: ``0``. Yields: Tuple[mx.array, mx.array]: One token and a vector of log probabilities. @@ -219,10 +236,22 @@ def generate_step( elif len(prompt_cache) != len(model.layers): raise ValueError("Wrong number of layers in the prompt cache.") - sampler = make_sampler(temp, top_p, min_p, min_tokens_to_keep) - logits_processors = logits_processors or [] - logits_processors.extend( - make_logits_processors(logit_bias, repetition_penalty, repetition_context_size) + if temp is not None or top_p is not None or min_tokens_to_keep is not None: + print( + "[Warning] Specifying sampling arguments to ``generate_step`` is " + "deprecated. Pass in a ``sampler`` instead." + ) + if repetition_penalty is not None: + print( + "[Warning] Specifying ``repetition_penalty`` is deprecated. " + "Pass in ``logits_processors`` instead." + ) + + sampler = sampler or make_sampler( + temp or 0.0, top_p or 0.0, min_p or 0.0, min_tokens_to_keep or 1 + ) + logits_processors = logits_processors or make_logits_processors( + None, repetition_penalty, repetition_context_size or 20 ) def _step(y): @@ -290,17 +319,20 @@ def stream_generate( if not isinstance(tokenizer, TokenizerWrapper): tokenizer = TokenizerWrapper(tokenizer) - prompt_tokens = mx.array( - prompt if isinstance(prompt, list) else tokenizer.encode(prompt) - ) + prompt = mx.array(prompt if isinstance(prompt, list) else tokenizer.encode(prompt)) detokenizer = tokenizer.detokenizer with wired_limit(model, [generation_stream]): detokenizer.reset() - for n, (token, logits) in zip( + tic = time.perf_counter() + for n, (token, logprobs) in zip( range(max_tokens), - generate_step(prompt_tokens, model, **kwargs), + generate_step(prompt, model, **kwargs), ): + if n == 0: + prompt_time = time.perf_counter() - tic + prompt_tps = prompt.size / prompt_time + tic = time.perf_counter() if token == tokenizer.eos_token_id: break @@ -309,17 +341,34 @@ def stream_generate( if n == (max_tokens - 1): break - yield detokenizer.last_segment, token, logits + yield GenerationResponse( + text=detokenizer.last_segment, + token=token, + logprobs=logprobs, + prompt_tokens=prompt.size, + prompt_tps=prompt_tps, + generation_tokens=n + 1, + generation_tps=(n + 1) / (time.perf_counter() - tic), + peak_memory=mx.metal.get_peak_memory() / 1e9, + ) detokenizer.finalize() - yield detokenizer.last_segment, token, logits + yield GenerationResponse( + text=detokenizer.last_segment, + token=token, + logprobs=logprobs, + prompt_tokens=prompt.size, + prompt_tps=prompt_tps, + generation_tokens=n + 1, + generation_tps=(n + 1) / (time.perf_counter() - tic), + peak_memory=mx.metal.get_peak_memory() / 1e9, + ) def generate( model: nn.Module, tokenizer: Union[PreTrainedTokenizer, TokenizerWrapper], prompt: str, - max_tokens: int = 100, verbose: bool = False, formatter: Optional[Callable] = None, **kwargs, @@ -334,64 +383,40 @@ def generate( max_tokens (int): The maximum number of tokens. Default: ``100``. verbose (bool): If ``True``, print tokens and timing information. Default: ``False``. - formatter (Optional[Callable]): A function which takes a token and a - probability and displays it. - kwargs: The remaining options get passed to :func:`generate_step`. - See :func:`generate_step` for more details. + kwargs: The remaining options get passed to :func:`stream_generate`. + See :func:`stream_generate` for more details. """ - if not isinstance(tokenizer, TokenizerWrapper): - tokenizer = TokenizerWrapper(tokenizer) - + if formatter is not None: + print( + "[Warning] Text formatting is deprecated and no longer used. " + "The argument will be removed in a future version." + ) if verbose: print("=" * 10) print("Prompt:", prompt) - prompt_tokens = mx.array(tokenizer.encode(prompt)) - detokenizer = tokenizer.detokenizer - - with wired_limit(model, [generation_stream]): - tic = time.perf_counter() - detokenizer.reset() - for n, (token, logprobs) in zip( - range(max_tokens), - generate_step(prompt_tokens, model, **kwargs), - ): - if n == 0: - prompt_time = time.perf_counter() - tic - tic = time.perf_counter() - if token == tokenizer.eos_token_id: - break - detokenizer.add_token(token) - - if verbose: - if formatter: - # We have to finalize so that the prob corresponds to the last segment - detokenizer.finalize() - prob = mx.exp(logprobs[token]).item() - formatter(detokenizer.last_segment, prob) - else: - print(detokenizer.last_segment, end="", flush=True) - - token_count = n + 1 - detokenizer.finalize() - + text = "" + for response in stream_generate(model, tokenizer, prompt, **kwargs): if verbose: - gen_time = time.perf_counter() - tic - print(detokenizer.last_segment, flush=True) - print("=" * 10) - if token_count == 0: - print("No tokens generated for this prompt") - return - prompt_tps = prompt_tokens.size / prompt_time - gen_tps = (token_count - 1) / gen_time - print( - f"Prompt: {prompt_tokens.size} tokens, {prompt_tps:.3f} tokens-per-sec" - ) - print(f"Generation: {token_count} tokens, {gen_tps:.3f} tokens-per-sec") - peak_mem = mx.metal.get_peak_memory() / 1e9 - print(f"Peak memory: {peak_mem:.3f} GB") + print(response.text, end="", flush=True) + text += response.text - return detokenizer.text + if verbose: + print() + print("=" * 10) + if len(text) == 0: + print("No text generated for this prompt") + return + print( + f"Prompt: {response.prompt_tokens} tokens, " + f"{response.prompt_tps:.3f} tokens-per-sec" + ) + print( + f"Generation: {response.generation_tokens} tokens, " + f"{response.generation_tps:.3f} tokens-per-sec" + ) + print(f"Peak memory: {response.peak_memory:.3f} GB") + return text def load_config(model_path: Path) -> dict: diff --git a/llms/tests/test_generate.py b/llms/tests/test_generate.py index e0a372a9..f2345394 100644 --- a/llms/tests/test_generate.py +++ b/llms/tests/test_generate.py @@ -2,6 +2,7 @@ import unittest +from mlx_lm.sample_utils import make_logits_processors from mlx_lm.utils import generate, load @@ -25,8 +26,8 @@ class TestGenerate(unittest.TestCase): self.tokenizer, "hello", max_tokens=5, + logits_processors=make_logits_processors(logit_bias), verbose=False, - logit_bias=logit_bias, ) self.assertEqual(text, "!!!!!") diff --git a/llms/tests/test_sample_utils.py b/llms/tests/test_sample_utils.py index ec0e2cb7..ebc90ce8 100644 --- a/llms/tests/test_sample_utils.py +++ b/llms/tests/test_sample_utils.py @@ -1,10 +1,10 @@ import unittest import mlx.core as mx -from mlx_lm.sample_utils import top_p_sampling +from mlx_lm.sample_utils import min_p_sampling, top_p_sampling -class TestSamplingUtils(unittest.TestCase): +class TestSampleUtils(unittest.TestCase): def test_top_p_sampling(self): probs = mx.array([0.9, 0.0, 0.0, 0.1])[None] logits = mx.log(probs) @@ -28,6 +28,20 @@ class TestSamplingUtils(unittest.TestCase): token = top_p_sampling(logits, 0.95, temperature).item() self.assertTrue(token in (1, 2, 3)) + def test_min_p_sampling(self): + probs = mx.array([0.9, 0.0, 0.0, 0.1])[None] + logits = mx.log(probs) + temperature = 1.0 + token = min_p_sampling(logits, 0.8) + self.assertEqual(token, 0) + + probs = mx.array([0.9, 0.0, 0.0, 0.1])[None] + logits = mx.log(probs) + temperature = 1.0 + for _ in range(5): + token = min_p_sampling(logits, 0.05) + self.assertTrue(token in (0, 3)) + if __name__ == "__main__": unittest.main() diff --git a/llms/tests/test_tokenizers.py b/llms/tests/test_tokenizers.py index 9c30d51e..db6b9f9e 100644 --- a/llms/tests/test_tokenizers.py +++ b/llms/tests/test_tokenizers.py @@ -34,10 +34,11 @@ class TestTokenizers(unittest.TestCase): detokenizer = tokenizer.detokenizer detokenizer.reset() text = "" - for t in tokens: + for e, t in enumerate(tokens): detokenizer.add_token(t) seg = detokenizer.last_segment text += seg + self.assertEqual(detokenizer.tokens, tokens[: e + 1]) detokenizer.finalize() text += detokenizer.last_segment self.assertEqual(text, expected_text) From 0ffdb6dd20f3cf45445b69d80aa93f793faf222d Mon Sep 17 00:00:00 2001 From: Kevin Conner Date: Sun, 24 Nov 2024 16:37:37 -0800 Subject: [PATCH 084/188] Fix object property value in mlx_lm.server chat completions response to match OpenAI spec (#1119) These were "chat.completions" and "chat.completions.chunk" but should be "chat.completion" and "chat.completion.chunk" for compatibility with clients expecting an OpenAI API. In particular, this solves a problem in which aider 0.64.1 reports hitting a token limit on any completion request, no matter how small, despite apparently correct counts in the usage property. Refer to: https://platform.openai.com/docs/api-reference/chat/object > object string > The object type, which is always chat.completion. https://platform.openai.com/docs/api-reference/chat/streaming > object string > The object type, which is always chat.completion.chunk. --- llms/mlx_lm/SERVER.md | 2 +- llms/mlx_lm/server.py | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/llms/mlx_lm/SERVER.md b/llms/mlx_lm/SERVER.md index 2976a09f..e544c6fa 100644 --- a/llms/mlx_lm/SERVER.md +++ b/llms/mlx_lm/SERVER.md @@ -92,7 +92,7 @@ curl localhost:8080/v1/chat/completions \ - `system_fingerprint`: A unique identifier for the system. -- `object`: Any of "chat.completions", "chat.completions.chunk" (for +- `object`: Any of "chat.completion", "chat.completion.chunk" (for streaming), or "text.completion". - `model`: The model repo or path (e.g. `"mlx-community/Llama-3.2-3B-Instruct-4bit"`). diff --git a/llms/mlx_lm/server.py b/llms/mlx_lm/server.py index badc6dd3..ce09cf45 100644 --- a/llms/mlx_lm/server.py +++ b/llms/mlx_lm/server.py @@ -589,9 +589,7 @@ class APIHandler(BaseHTTPRequestHandler): # Determine response type self.request_id = f"chatcmpl-{uuid.uuid4()}" - self.object_type = ( - "chat.completions.chunk" if self.stream else "chat.completions" - ) + self.object_type = "chat.completion.chunk" if self.stream else "chat.completion" if ( hasattr(self.tokenizer, "apply_chat_template") and self.tokenizer.chat_template From adaab81029eb5f53d9a40c94968bf143cbc5985c Mon Sep 17 00:00:00 2001 From: Remixer Dec Date: Mon, 25 Nov 2024 04:41:06 +0400 Subject: [PATCH 085/188] Allow converting models from local directories (#1118) --- whisper/convert.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/whisper/convert.py b/whisper/convert.py index 301fd5b4..7369fafa 100644 --- a/whisper/convert.py +++ b/whisper/convert.py @@ -174,11 +174,6 @@ def load_torch_weights_and_config( "*.txt", ], ) - else: - raise RuntimeError( - f"Model {name_or_path} is not found in {available_models()}," - "on Hugging Face or as a local path." - ) if name_or_path.endswith(".pt"): checkpoint = torch.load(name_or_path, map_location="cpu", weights_only=False) From a5e173802ea0da999923d240f65e94ec8ad3c415 Mon Sep 17 00:00:00 2001 From: madroid Date: Tue, 26 Nov 2024 00:10:14 +0800 Subject: [PATCH 086/188] docs: update stream_generate return type annotation (#1121) Improve documentation clarity by: 1. Fix return type annotation to correctly reflect GenerationResponse 2. Simplify docstring by referencing GenerationResponse class 3. Remove redundant field descriptions --- llms/mlx_lm/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index 496ae4fc..5abd396d 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -300,7 +300,7 @@ def stream_generate( prompt: Union[str, List[int]], max_tokens: int = 100, **kwargs, -) -> Generator[Tuple[str, int, mx.array], None, None]: +) -> Generator[GenerationResponse, None, None]: """ A generator producing text based on the given prompt from the model. @@ -313,8 +313,8 @@ def stream_generate( See :func:`generate_step` for more details. Yields: - Tuple[str, int, mx.array]: - The next text segment, token, and vector of log probabilities. + GenerationResponse: An instance containing the generated text segment and + associated metadata. See :class:`GenerationResponse` for details. """ if not isinstance(tokenizer, TokenizerWrapper): tokenizer = TokenizerWrapper(tokenizer) From cfc29c29f45372c78876335a44b0c99ab6565ae0 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Mon, 25 Nov 2024 09:47:00 -0800 Subject: [PATCH 087/188] Put prompt processing in same stream (#1122) * put prompt processing in same stream * patch --- llms/mlx_lm/_version.py | 2 +- llms/mlx_lm/utils.py | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/llms/mlx_lm/_version.py b/llms/mlx_lm/_version.py index 5168eee4..343e0016 100644 --- a/llms/mlx_lm/_version.py +++ b/llms/mlx_lm/_version.py @@ -1,3 +1,3 @@ # Copyright © 2023-2024 Apple Inc. -__version__ = "0.20.0" +__version__ = "0.20.1" diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index 5abd396d..0e2f7af7 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -274,13 +274,14 @@ def generate_step( y = sampler(logprobs) return y, logprobs.squeeze(0) - while y.size > prefill_step_size: - model(y[:prefill_step_size][None], cache=prompt_cache) - mx.eval([c.state for c in prompt_cache]) - y = y[prefill_step_size:] - mx.metal.clear_cache() + with mx.stream(generation_stream): + while y.size > prefill_step_size: + model(y[:prefill_step_size][None], cache=prompt_cache) + mx.eval([c.state for c in prompt_cache]) + y = y[prefill_step_size:] + mx.metal.clear_cache() - y, logprobs = _step(y) + y, logprobs = _step(y) mx.async_eval(y, logprobs) n = 0 From cefe793ae0991b394b89c497d92afc6459490460 Mon Sep 17 00:00:00 2001 From: Neil Mehta Date: Tue, 26 Nov 2024 19:51:55 -0500 Subject: [PATCH 088/188] Accept mx.array type for prompt argument for stream_generate (#1125) * Accept mx.array type for prompt argument for stream_generate * Fix formatting --- llms/mlx_lm/utils.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index 0e2f7af7..f439ca99 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -298,7 +298,7 @@ def generate_step( def stream_generate( model: nn.Module, tokenizer: Union[PreTrainedTokenizer, TokenizerWrapper], - prompt: Union[str, List[int]], + prompt: Union[str, mx.array, List[int]], max_tokens: int = 100, **kwargs, ) -> Generator[GenerationResponse, None, None]: @@ -308,7 +308,7 @@ def stream_generate( Args: model (nn.Module): The model to use for generation. tokenizer (PreTrainedTokenizer): The tokenizer. - prompt (Union[str, List[int]]): The input prompt string or integer tokens. + prompt (Union[str, mx.array, List[int]]): The input prompt string or integer tokens. max_tokens (int): The maximum number of tokens. Default: ``100``. kwargs: The remaining options get passed to :func:`generate_step`. See :func:`generate_step` for more details. @@ -320,7 +320,11 @@ def stream_generate( if not isinstance(tokenizer, TokenizerWrapper): tokenizer = TokenizerWrapper(tokenizer) - prompt = mx.array(prompt if isinstance(prompt, list) else tokenizer.encode(prompt)) + if not isinstance(prompt, mx.array): + prompt = mx.array( + prompt if isinstance(prompt, list) else tokenizer.encode(prompt) + ) + detokenizer = tokenizer.detokenizer with wired_limit(model, [generation_stream]): From 8801beb66f61d16114d4014fec32a266778e4481 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Mon, 2 Dec 2024 11:42:58 -0800 Subject: [PATCH 089/188] Add olmo2 (#1128) * add olmo2 * add olmo2 --- llms/mlx_lm/models/olmo2.py | 312 ++++++++++++++++++++++++++++++++++++ llms/mlx_lm/tuner/utils.py | 1 + llms/tests/test_models.py | 20 +++ 3 files changed, 333 insertions(+) create mode 100644 llms/mlx_lm/models/olmo2.py diff --git a/llms/mlx_lm/models/olmo2.py b/llms/mlx_lm/models/olmo2.py new file mode 100644 index 00000000..a28fdcc1 --- /dev/null +++ b/llms/mlx_lm/models/olmo2.py @@ -0,0 +1,312 @@ +# Copyright © 2023-2024 Apple Inc. + +from dataclasses import dataclass +from typing import Any, Dict, Optional, Union + +import mlx.core as mx +import mlx.nn as nn + +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention + + +@dataclass +class ModelArgs(BaseModelArgs): + model_type: str + hidden_size: int + num_hidden_layers: int + intermediate_size: int + num_attention_heads: int + rms_norm_eps: float + vocab_size: int + head_dim: Optional[int] = None + max_position_embeddings: Optional[int] = None + num_key_value_heads: Optional[int] = None + attention_bias: bool = False + mlp_bias: bool = False + rope_theta: float = 10000 + rope_traditional: bool = False + rope_scaling: Optional[Dict[str, Union[float, str]]] = None + tie_word_embeddings: bool = True + + def __post_init__(self): + if self.num_key_value_heads is None: + self.num_key_value_heads = self.num_attention_heads + + if self.rope_scaling: + if not "factor" in self.rope_scaling: + raise ValueError(f"rope_scaling must contain 'factor'") + rope_type = self.rope_scaling.get("type") or self.rope_scaling.get( + "rope_type" + ) + if rope_type is None: + raise ValueError( + f"rope_scaling must contain either 'type' or 'rope_type'" + ) + if rope_type not in ["linear", "dynamic", "llama3"]: + raise ValueError( + "rope_scaling 'type' currently only supports 'linear', 'dynamic' or 'llama3'" + ) + + +class DynamicNTKScalingRoPE(nn.Module): + """Implements the rotary positional encoding with Dynamic NTK scaling and Llama 3 RoPE.""" + + def __init__( + self, + dims: int, + max_position_embeddings: int = 2048, + traditional: bool = False, + base: float = 10000, + scale: float = 1.0, + rope_type: str = "default", + rope_scaling: dict = None, + ): + super().__init__() + self.dims = dims + self.max_position_embeddings = max_position_embeddings + self.traditional = traditional + self.scale = scale + self.rope_type = rope_type + self.rope_scaling = rope_scaling + self.base = base + self.compute_freqs() + + def compute_freqs(self): + if self.rope_type != "llama3": + self._freqs = None + return + factor = self.rope_scaling["factor"] + low_freq_factor = self.rope_scaling.get("low_freq_factor", 1.0) + high_freq_factor = self.rope_scaling.get("high_freq_factor", 4.0) + old_context_len = self.rope_scaling.get( + "original_max_position_embeddings", + 8192, + ) + + low_freq_wavelen = old_context_len / low_freq_factor + high_freq_wavelen = old_context_len / high_freq_factor + + freqs = self.base ** (mx.arange(0, self.dims, 2) / self.dims) + wavelens = 2 * mx.pi * freqs + + freqs = mx.where(wavelens > low_freq_wavelen, freqs * factor, freqs) + is_medium_freq = (wavelens > high_freq_wavelen) & (wavelens < low_freq_wavelen) + smooth_factors = (old_context_len / wavelens - low_freq_factor) / ( + high_freq_factor - low_freq_factor + ) + smooth_freqs = freqs / ((1 - smooth_factors) / factor + smooth_factors) + self._freqs = mx.where(is_medium_freq, smooth_freqs, freqs) + self.base = None + + def extra_repr(self): + return ( + f"{self.dims}, traditional={self.traditional}, " + f"max_position_embeddings={self.max_position_embeddings}, " + f"scaling_factor={self.scale}, rope_type={self.rope_type}" + ) + + def __call__(self, x, offset: int = 0): + return mx.fast.rope( + x, + self.dims, + traditional=self.traditional, + base=self.base, + scale=self.scale, + offset=offset, + freqs=self._freqs, + ) + + +def initialize_rope(args: ModelArgs): + head_dim = args.head_dim or args.hidden_size // args.num_attention_heads + + rope_scaling = args.rope_scaling + rope_type = "default" + rope_scale = 1.0 + + if rope_scaling is not None: + rope_type = ( + rope_scaling.get("type") or rope_scaling.get("rope_type") or "default" + ) + if rope_type == "linear": + rope_scale = 1 / rope_scaling["factor"] + elif rope_type == "llama3": + rope_scale = 1.0 # The scaling is handled internally for llama3 + + return DynamicNTKScalingRoPE( + dims=head_dim, + max_position_embeddings=args.max_position_embeddings, + traditional=args.rope_traditional, + base=args.rope_theta, + scale=rope_scale, + rope_type=rope_type, + rope_scaling=rope_scaling, + ) + + +class Attention(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + + dim = args.hidden_size + self.n_heads = n_heads = args.num_attention_heads + self.n_kv_heads = n_kv_heads = args.num_key_value_heads + + self.head_dim = head_dim = args.head_dim or args.hidden_size // n_heads + + self.scale = head_dim**-0.5 + if hasattr(args, "attention_bias"): + attention_bias = args.attention_bias + else: + attention_bias = False + + self.q_proj = nn.Linear(dim, n_heads * head_dim, bias=attention_bias) + self.k_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=attention_bias) + self.v_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=attention_bias) + self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=attention_bias) + + self.rope = initialize_rope(args) + self.q_norm = nn.RMSNorm(n_heads * head_dim, args.rms_norm_eps) + self.k_norm = nn.RMSNorm(n_kv_heads * head_dim, args.rms_norm_eps) + + def __call__( + self, + x: mx.array, + mask: Optional[mx.array] = None, + cache: Optional[Any] = None, + ) -> mx.array: + B, L, D = x.shape + + queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x) + queries = self.q_norm(queries) + keys = self.k_norm(keys) + + # Prepare the queries, keys and values for the attention computation + queries = queries.reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) + keys = keys.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) + values = values.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) + + if cache is not None: + queries = self.rope(queries, offset=cache.offset) + keys = self.rope(keys, offset=cache.offset) + keys, values = cache.update_and_fetch(keys, values) + else: + queries = self.rope(queries) + keys = self.rope(keys) + + output = scaled_dot_product_attention( + queries, keys, values, cache=cache, scale=self.scale, mask=mask + ) + + output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) + return self.o_proj(output) + + +class MLP(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + + dim = args.hidden_size + hidden_dim = args.intermediate_size + if hasattr(args, "mlp_bias"): + mlp_bias = args.mlp_bias + else: + mlp_bias = False + + self.gate_proj = nn.Linear(dim, hidden_dim, bias=mlp_bias) + self.down_proj = nn.Linear(hidden_dim, dim, bias=mlp_bias) + self.up_proj = nn.Linear(dim, hidden_dim, bias=mlp_bias) + + def __call__(self, x) -> mx.array: + return self.down_proj(nn.silu(self.gate_proj(x)) * self.up_proj(x)) + + +class TransformerBlock(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.num_attention_heads = args.num_attention_heads + self.hidden_size = args.hidden_size + self.self_attn = Attention(args) + self.mlp = MLP(args) + self.post_attention_layernorm = nn.RMSNorm( + args.hidden_size, eps=args.rms_norm_eps + ) + self.post_feedforward_layernorm = nn.RMSNorm( + args.hidden_size, eps=args.rms_norm_eps + ) + self.args = args + + def __call__( + self, + x: mx.array, + mask: Optional[mx.array] = None, + cache: Optional[Any] = None, + ) -> mx.array: + r = self.post_attention_layernorm(self.self_attn(x, mask, cache)) + h = x + r + r = self.post_feedforward_layernorm(self.mlp(h)) + out = h + r + return out + + +class LlamaModel(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.args = args + self.vocab_size = args.vocab_size + self.num_hidden_layers = args.num_hidden_layers + assert self.vocab_size > 0 + self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size) + self.layers = [ + TransformerBlock(args=args) for _ in range(args.num_hidden_layers) + ] + self.norm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) + + def __call__( + self, + inputs: mx.array, + cache=None, + ): + h = self.embed_tokens(inputs) + + mask = create_attention_mask(h, cache) + + if cache is None: + cache = [None] * len(self.layers) + + for layer, c in zip(self.layers, cache): + h = layer(h, mask, cache=c) + + return self.norm(h) + + +class Model(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.args = args + self.model_type = args.model_type + self.model = LlamaModel(args) + if not args.tie_word_embeddings: + self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=False) + + def __call__( + self, + inputs: mx.array, + cache=None, + ): + out = self.model(inputs, cache) + if self.args.tie_word_embeddings: + out = self.model.embed_tokens.as_linear(out) + else: + out = self.lm_head(out) + return out + + def sanitize(self, weights): + # Remove unused precomputed rotary freqs + return { + k: v for k, v in weights.items() if "self_attn.rotary_emb.inv_freq" not in k + } + + @property + def layers(self): + return self.model.layers diff --git a/llms/mlx_lm/tuner/utils.py b/llms/mlx_lm/tuner/utils.py index 7c78ee91..835cb482 100644 --- a/llms/mlx_lm/tuner/utils.py +++ b/llms/mlx_lm/tuner/utils.py @@ -98,6 +98,7 @@ def linear_to_lora_layers( "cohere", "minicpm", "deepseek", + "olmo2", ]: keys = set(["self_attn.q_proj", "self_attn.v_proj"]) if model.model_type in ["mixtral", "phimoe"]: diff --git a/llms/tests/test_models.py b/llms/tests/test_models.py index 93b881b9..edb594d7 100644 --- a/llms/tests/test_models.py +++ b/llms/tests/test_models.py @@ -792,6 +792,26 @@ class TestModels(unittest.TestCase): model, args.model_type, args.vocab_size, args.num_hidden_layers ) + def test_olmo2(self): + from mlx_lm.models import olmo2 + + args = olmo2.ModelArgs( + model_type="olmo2", + hidden_size=128, + attention_bias=False, + intermediate_size=256, + num_attention_heads=4, + num_hidden_layers=4, + num_key_value_heads=2, + rms_norm_eps=1e-4, + rope_theta=1000, + vocab_size=1000, + ) + model = olmo2.Model(args) + self.model_test_runner( + model, args.model_type, args.vocab_size, args.num_hidden_layers + ) + if __name__ == "__main__": unittest.main() From 2a9294a5f02b4178bf27804d05f6d88581db06e0 Mon Sep 17 00:00:00 2001 From: hehua2008 Date: Tue, 3 Dec 2024 05:15:19 +0800 Subject: [PATCH 090/188] Fix bug in FluxSampler.timesteps method (#1131) --- flux/flux/sampler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flux/flux/sampler.py b/flux/flux/sampler.py index 3bff1ca2..54c4fe35 100644 --- a/flux/flux/sampler.py +++ b/flux/flux/sampler.py @@ -25,7 +25,7 @@ class FluxSampler: ): t = mx.linspace(start, stop, num_steps + 1) - if self._schnell: + if not self._schnell: t = self._time_shift(image_sequence_length, t) return t.tolist() From eb9277f574c6ba9da8494afade631e7a8553402a Mon Sep 17 00:00:00 2001 From: Angelos Katharopoulos Date: Mon, 2 Dec 2024 13:15:50 -0800 Subject: [PATCH 091/188] Allow loading from diffusers ckpt (#1117) --- flux/flux/model.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flux/flux/model.py b/flux/flux/model.py index 18ea70b0..d8ad9d9b 100644 --- a/flux/flux/model.py +++ b/flux/flux/model.py @@ -85,6 +85,8 @@ class Flux(nn.Module): def sanitize(self, weights): new_weights = {} for k, w in weights.items(): + if k.startswith("model.diffusion_model."): + k = k[22:] if k.endswith(".scale"): k = k[:-6] + ".weight" for seq in ["img_mlp", "txt_mlp", "adaLN_modulation"]: From 0ca162cfb2a85164cbec18304913c4220520786c Mon Sep 17 00:00:00 2001 From: sakares saengkaew Date: Tue, 3 Dec 2024 14:56:07 +0700 Subject: [PATCH 092/188] Fix data_iter in prepare_dataset from speechcommands example (#1113) --- speechcommands/main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/speechcommands/main.py b/speechcommands/main.py index 0d8da9fd..ed328f4c 100644 --- a/speechcommands/main.py +++ b/speechcommands/main.py @@ -76,6 +76,7 @@ def train_epoch(model, train_iter, optimizer, epoch): samples_per_sec = [] model.train(True) + train_iter.reset() for batch_counter, batch in enumerate(train_iter): x = mx.array(batch["audio"]) y = mx.array(batch["label"]) @@ -111,6 +112,7 @@ def test_epoch(model, test_iter): model.train(False) accs = [] throughput = [] + test_iter.reset() for batch_counter, batch in enumerate(test_iter): x = mx.array(batch["audio"]) y = mx.array(batch["label"]) From 1963df856529765b1e11beb24ea4542e6e75916d Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Tue, 3 Dec 2024 16:17:14 -0800 Subject: [PATCH 093/188] Allow prompt callback to `generate_step` (#1133) * allow prompt callback and use in cache_prompt * nit * comments * bump version --- llms/mlx_lm/_version.py | 2 +- llms/mlx_lm/cache_prompt.py | 35 +++++++++++--------------- llms/mlx_lm/generate.py | 2 +- llms/mlx_lm/utils.py | 44 +++++++++++++++++++-------------- llms/tests/test_prompt_cache.py | 13 +++++----- 5 files changed, 48 insertions(+), 48 deletions(-) diff --git a/llms/mlx_lm/_version.py b/llms/mlx_lm/_version.py index 343e0016..0f885fba 100644 --- a/llms/mlx_lm/_version.py +++ b/llms/mlx_lm/_version.py @@ -1,3 +1,3 @@ # Copyright © 2023-2024 Apple Inc. -__version__ = "0.20.1" +__version__ = "0.20.2" diff --git a/llms/mlx_lm/cache_prompt.py b/llms/mlx_lm/cache_prompt.py index 987b640d..9d7d1603 100644 --- a/llms/mlx_lm/cache_prompt.py +++ b/llms/mlx_lm/cache_prompt.py @@ -8,7 +8,7 @@ import time import mlx.core as mx from .models.cache import make_prompt_cache, save_prompt_cache -from .utils import load, maybe_quantize_kv_cache +from .utils import generate_step, load DEFAULT_QUANTIZED_KV_START = 5000 @@ -50,12 +50,6 @@ def setup_arg_parser(): action="store_true", help="Use the default chat template", ) - parser.add_argument( - "--cache-limit-gb", - type=int, - default=None, - help="Set the MLX cache limit in GB", - ) parser.add_argument( "--max-kv-size", type=int, @@ -99,9 +93,6 @@ def main(): parser = setup_arg_parser() args = parser.parse_args() - if args.cache_limit_gb is not None: - mx.metal.set_cache_limit(args.cache_limit_gb * 1024 * 1024 * 1024) - # Building tokenizer_config tokenizer_config = {"trust_remote_code": True if args.trust_remote_code else None} if args.eos_token is not None: @@ -144,26 +135,28 @@ def main(): y = mx.array(tokenizer.encode(prompt)) # Process the prompt - processed = 0 - step_size = 512 start = time.time() max_msg_len = 0 - while y.size > 0: - model(y[:step_size][None], cache=cache) - mx.eval([c.state for c in cache]) - mx.metal.clear_cache() - processed += min(y.size, step_size) - y = y[step_size:] + def callback(processed, total_tokens): current = time.time() speed = processed / (current - start) msg = f"\rProcessed {processed:6d} tokens ({speed:6.2f} tok/s)" + nonlocal max_msg_len max_msg_len = max(max_msg_len, len(msg)) print(msg + " " * (max_msg_len - len(msg)), end="", flush=True) - maybe_quantize_kv_cache( - cache, args.quantized_kv_start, args.kv_group_size, args.kv_bits - ) + for _ in generate_step( + y, + model, + max_tokens=0, + prompt_cache=cache, + kv_bits=args.kv_bits, + kv_group_size=args.kv_group_size, + quantized_kv_start=args.quantized_kv_start, + prompt_progress_callback=callback, + ): + pass print() print(f"Peak memory: {mx.metal.get_peak_memory() / 1e9:.3f} GB") diff --git a/llms/mlx_lm/generate.py b/llms/mlx_lm/generate.py index 9e96fbdc..0c1b4acd 100644 --- a/llms/mlx_lm/generate.py +++ b/llms/mlx_lm/generate.py @@ -77,7 +77,7 @@ def setup_arg_parser(): ) parser.add_argument( "--min-tokens-to-keep", - type=float, + type=int, default=DEFAULT_MIN_TOKENS_TO_KEEP, help="Minimum tokens to keep for min-p sampling.", ) diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index f439ca99..86b786ce 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -183,6 +183,7 @@ def generate_step( prompt: mx.array, model: nn.Module, *, + max_tokens: int = 256, sampler: Optional[Callable[mx.array, mx.array]] = None, logits_processors: Optional[List[Callable[[mx.array, mx.array], mx.array]]] = None, max_kv_size: Optional[int] = None, @@ -191,6 +192,7 @@ def generate_step( kv_bits: Optional[int] = None, kv_group_size: int = 64, quantized_kv_start: int = 0, + prompt_progress_callback: Optional[Callable[int, int]] = None, temp: Optional[float] = None, repetition_penalty: Optional[float] = None, repetition_context_size: Optional[int] = None, @@ -204,21 +206,25 @@ def generate_step( Args: prompt (mx.array): The input prompt. model (nn.Module): The model to use for generation. - prefill_step_size (int): Step size for processing the prompt. - max_kv_size (int, optional): Maximum size of the key-value cache. Old - entries (except the first 4 tokens) will be overwritten. - prompt_cache (List[Any], optional): A pre-computed prompt cache. Note, if - provided, the cache will be updated in place. + max_tokens (int): The maximum number of tokens. Use``-1`` for an infinite + generator. Default: ``256``. sampler (Callable[mx.array, mx.array], optional): A sampler for sampling a token from a vector of log probabilities. Default: ``None``. logits_processors (List[Callable[[mx.array, mx.array], mx.array]], optional): A list of functions that take tokens and logits and return the processed logits. Default: ``None``. + max_kv_size (int, optional): Maximum size of the key-value cache. Old + entries (except the first 4 tokens) will be overwritten. + prompt_cache (List[Any], optional): A pre-computed prompt cache. Note, if + provided, the cache will be updated in place. + prefill_step_size (int): Step size for processing the prompt. kv_bits (int, optional): Number of bits to use for KV cache quantization. None implies no cache quantization. Default: ``None``. kv_group_size (int): Group size for KV cache quantization. Default: ``64``. quantized_kv_start (int): Step to begin using a quantized KV cache. when ``kv_bits`` is non-None. Default: ``0``. + prompt_prorgress_callback (Callable[int, int]): A call-back which takes the + prompt tokens processed so far and the total number of prompt tokens. Yields: Tuple[mx.array, mx.array]: One token and a vector of log probabilities. @@ -253,6 +259,7 @@ def generate_step( logits_processors = logits_processors or make_logits_processors( None, repetition_penalty, repetition_context_size or 20 ) + prompt_progress_callback = prompt_progress_callback or (lambda *_: None) def _step(y): with mx.stream(generation_stream): @@ -275,9 +282,13 @@ def generate_step( return y, logprobs.squeeze(0) with mx.stream(generation_stream): + total_prompt_tokens = y.size + prompt_processed_tokens = 0 while y.size > prefill_step_size: model(y[:prefill_step_size][None], cache=prompt_cache) mx.eval([c.state for c in prompt_cache]) + prompt_progress_callback(prompt_processed_tokens, total_prompt_tokens) + prompt_processed_tokens += prefill_step_size y = y[prefill_step_size:] mx.metal.clear_cache() @@ -286,20 +297,25 @@ def generate_step( mx.async_eval(y, logprobs) n = 0 while True: - next_y, next_logprobs = _step(y) - mx.async_eval(next_y, next_logprobs) + if n != max_tokens: + next_y, next_logprobs = _step(y) + mx.async_eval(next_y, next_logprobs) + if n == 0: + mx.eval(y) + prompt_progress_callback(total_prompt_tokens, total_prompt_tokens) + if n == max_tokens: + break yield y.item(), logprobs if n % 256 == 0: mx.metal.clear_cache() - n += 1 y, logprobs = next_y, next_logprobs + n += 1 def stream_generate( model: nn.Module, tokenizer: Union[PreTrainedTokenizer, TokenizerWrapper], prompt: Union[str, mx.array, List[int]], - max_tokens: int = 100, **kwargs, ) -> Generator[GenerationResponse, None, None]: """ @@ -309,7 +325,6 @@ def stream_generate( model (nn.Module): The model to use for generation. tokenizer (PreTrainedTokenizer): The tokenizer. prompt (Union[str, mx.array, List[int]]): The input prompt string or integer tokens. - max_tokens (int): The maximum number of tokens. Default: ``100``. kwargs: The remaining options get passed to :func:`generate_step`. See :func:`generate_step` for more details. @@ -330,10 +345,7 @@ def stream_generate( with wired_limit(model, [generation_stream]): detokenizer.reset() tic = time.perf_counter() - for n, (token, logprobs) in zip( - range(max_tokens), - generate_step(prompt, model, **kwargs), - ): + for n, (token, logprobs) in enumerate(generate_step(prompt, model, **kwargs)): if n == 0: prompt_time = time.perf_counter() - tic prompt_tps = prompt.size / prompt_time @@ -343,9 +355,6 @@ def stream_generate( detokenizer.add_token(token) - if n == (max_tokens - 1): - break - yield GenerationResponse( text=detokenizer.last_segment, token=token, @@ -385,7 +394,6 @@ def generate( model (nn.Module): The language model. tokenizer (PreTrainedTokenizer): The tokenizer. prompt (str): The string prompt. - max_tokens (int): The maximum number of tokens. Default: ``100``. verbose (bool): If ``True``, print tokens and timing information. Default: ``False``. kwargs: The remaining options get passed to :func:`stream_generate`. diff --git a/llms/tests/test_prompt_cache.py b/llms/tests/test_prompt_cache.py index 0867ab56..de5694d5 100644 --- a/llms/tests/test_prompt_cache.py +++ b/llms/tests/test_prompt_cache.py @@ -121,21 +121,20 @@ class TestPromptCache(unittest.TestCase): def test_cache_with_generate(self): model, tokenizer = load(HF_MODEL_PATH) prompt = tokenizer.encode("this is a prompt", return_tensors="mlx")[0] - results = zip(range(4), generate_step(prompt, model)) - toks, all_logits = zip(*(r[1] for r in results)) + results = list(generate_step(prompt, model, max_tokens=4)) + toks, all_logits = zip(*results) prompt_cache = make_prompt_cache(model) i = 0 - for _, (tok, logits) in zip( - range(2), generate_step(prompt, model, prompt_cache=prompt_cache) + for tok, logits in generate_step( + prompt, model, prompt_cache=prompt_cache, max_tokens=2 ): self.assertEqual(tok, toks[i]) self.assertTrue(mx.allclose(logits, all_logits[i])) i += 1 - for _, (tok, logits) in zip( - range(1), - generate_step(mx.array([toks[i]]), model, prompt_cache=prompt_cache), + for tok, logits in generate_step( + mx.array([toks[i]]), model, prompt_cache=prompt_cache, max_tokens=1 ): i += 1 self.assertEqual(tok, toks[i]) From 1727959a27f2fb7b459387084b59f066296757a5 Mon Sep 17 00:00:00 2001 From: vb Date: Wed, 4 Dec 2024 04:21:39 +0100 Subject: [PATCH 094/188] Add mentions of MLX-my-repo. (#1129) * Add mentions of MLX-my-repo. * simplify * move * move --------- Co-authored-by: Awni Hannun --- llms/README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/llms/README.md b/llms/README.md index 60f68353..4fff4207 100644 --- a/llms/README.md +++ b/llms/README.md @@ -77,7 +77,7 @@ to see how to use the API in more detail. The `mlx-lm` package also comes with functionality to quantize and optionally upload models to the Hugging Face Hub. -You can convert models in the Python API with: +You can convert models using the Python API: ```python from mlx_lm import convert @@ -163,6 +163,10 @@ mlx_lm.convert \ --upload-repo mlx-community/my-4bit-mistral ``` +Models can also be converted and quantized directly in the +[mlx-my-repo]https://huggingface.co/spaces/mlx-community/mlx-my-repo) Hugging +Face Space. + ### Long Prompts and Generations `mlx-lm` has some tools to scale efficiently to long prompts and generations: From cd8cf28c395e8f163d0aeb5264c6f605ccbc4009 Mon Sep 17 00:00:00 2001 From: Alex Barron Date: Sun, 8 Dec 2024 12:20:10 -0800 Subject: [PATCH 095/188] `mlx_lm.evaluate` (#1140) * Add evaluation script * only write top level results * add lm eval version * typo * create output dir * relative import * comment --------- Co-authored-by: David Grangier --- llms/mlx_lm/evaluate.py | 355 ++++++++++++++++++++++++++++++++++++++++ llms/setup.py | 2 + 2 files changed, 357 insertions(+) create mode 100644 llms/mlx_lm/evaluate.py diff --git a/llms/mlx_lm/evaluate.py b/llms/mlx_lm/evaluate.py new file mode 100644 index 00000000..423d5823 --- /dev/null +++ b/llms/mlx_lm/evaluate.py @@ -0,0 +1,355 @@ +# Adapted from a PyTorch implementation by David Grangier + +import argparse +import json +import logging +import os +from importlib.metadata import version +from pathlib import Path +from typing import Optional + +import lm_eval +import mlx.core as mx +import mlx.nn as nn +import numpy as np +from lm_eval.api.model import LM +from lm_eval.api.registry import register_model +from tqdm import tqdm + +from .models.cache import make_prompt_cache +from .utils import load, stream_generate + +PAD = 0 + + +def _len_longest_common_prefix(a, b): + l = 0 + for item_a, item_b in zip(a, b): + if item_a != item_b: + break + l += 1 + return l + + +def _rstrip_until(s, untils): + """Limit a string to the first occurence of any substring in untils.""" + l = len(s) + f = [s.find(u) for u in untils] + f = [l if x < 0 else x for x in f] + return s[: min(f)] + + +def _pad_inputs( + inputs, + maxlen, + genlen=0, + pad_left=False, + pad_multiple=32, + truncate=False, +): + # pad the prompts to the left with at least genlen tokens. + actual_maxlen = max(len(p) for p in inputs) + genlen + if actual_maxlen > maxlen: + if not truncate: + raise ValueError("Inputs are too long.") + else: # drop begining + actual_maxlen = maxlen + inputs = [p[max(0, len(p) - maxlen) :] for p in inputs] + if pad_multiple > 0: + maxlen = (actual_maxlen + pad_multiple - 1) // pad_multiple + maxlen *= pad_multiple + assert PAD == 0 + lr = np.array((1, 0) if pad_left else (0, 1)) + return np.stack( + [np.pad(np.array(x, np.int32), lr * (maxlen - len(x))) for x in inputs], + axis=0, + ) + + +@register_model("mlxlm") +class MLXLM(LM): + def __init__( + self, + path_or_hf_repo: str, + batch_size: int = 16, + max_tokens: Optional[int] = None, + ) -> None: + super().__init__() + self._batch_size = batch_size + self._model, self._tokenizer = load(path_or_hf_repo) + self._max_tokens = max_tokens or self._tokenizer.model_max_length + + def _score_fn(self, inputs, tokenize=True, step_size=32): + if tokenize: + inputs = self._tokenizer.encode(inputs) + inputs = _pad_inputs(inputs, self._max_tokens, truncate=False) + inputs = mx.array(inputs) + inputs, targets = inputs[..., :-1], inputs[..., 1:] + + cache = make_prompt_cache(self._model) + + mask = targets != PAD + + scores, is_greedy = [], [] + for i in range(0, inputs.shape[1], step_size): + logits = self._model(inputs[:, i : i + step_size], cache=cache) + + log_probs = nn.log_softmax(logits.astype(mx.float32)) + score = mx.take_along_axis( + log_probs, targets[:, i : i + step_size, mx.newaxis], axis=-1 + )[..., 0] + ig = mask[:, i : i + step_size] * ( + targets[:, i : i + step_size] == mx.argmax(logits, axis=-1) + ) + + mx.eval(score, ig) + mx.metal.clear_cache() + + is_greedy.append(ig) + scores.append(score) + + scores = mx.concatenate(scores, axis=1) + is_greedy = mx.concatenate(is_greedy, axis=1) + + return scores, mask.sum(axis=-1), is_greedy + + def _loglikelihood(self, texts, score_spans=None, tokenize=True): + # sort by length to get batches with little padding. + sorted_indices = sorted(range(len(texts)), key=lambda i: -len(texts[i])) + sorted_inputs = [texts[sorted_indices[i]] for i in range(len(texts))] + sorted_spans = None + if score_spans is not None: + sorted_spans = [score_spans[sorted_indices[i]] for i in range(len(texts))] + + results = [] + for i in tqdm(range(0, len(sorted_inputs), self._batch_size)): + batch = sorted_inputs[i : i + self._batch_size] + scores, length, is_greedy = self._score_fn(batch, tokenize=tokenize) + for j in range(len(batch)): + if sorted_spans is None: # full sequence score + mask = mx.arange(scores[j].shape[-1]) < length + score = (scores[j].astype(mx.float32) * mask).sum(axis=-1) + ig = (is_greedy[j].astype(mx.int32) * mask).sum(axis=-1) + else: # subsequence score + start, end = sorted_spans[i + j] + score = scores[j][start:end].astype(mx.float32).sum() + ig = is_greedy[j][start:end].astype(mx.int32).sum() + length = end - start + + results.append((score.item(), ig.item(), length)) + + # reorder the outputs + inv_sort = np.argsort(sorted_indices) + results = [results[inv_sort[i]] for i in range(len(results))] + + return results + + def _tokenize(self, texts): + return [tuple(self._tokenizer.encode(t)) for t in texts] + + def loglikelihood(self, requests) -> list[tuple[float, bool]]: + """Compute log-likelihood of generating a continuation from a context. + Downstream tasks should attempt to use loglikelihood instead of other + LM calls whenever possible. + :param requests: list[Instance] + A list of Instance objects, with property `args` which returns a tuple (context, continuation). + `context: str` + Context string. Implementations of LM must be able to handle an + empty context string. + `continuation: str` + The continuation over which log likelihood will be calculated. If + there is a word boundary, the space should be in the continuation. + For example, context="hello" continuation=" world" is correct. + :return: list[tuple[float, bool]] + A list of pairs (logprob, isgreedy) + `logprob: float` + The log probability of `continuation`. + `isgreedy`: + Whether `continuation` would be generated by greedy sampling from `context`. + """ + logging.info("Estimating loglikelihood for %d pairs." % len(requests)) + + # tokenize prefix and prefix + completion for all requests. + tokenized = self._tokenize( + [t for r in requests for t in [r.args[0], r.args[0] + r.args[1]]] + ) + + # max length (prefix + completion) and longest common prefix per question. + length_stats = {} + for prefix, completed in zip(tokenized[0::2], tokenized[1::2]): + max_completed_l, min_prefix_l = length_stats.get(prefix, (0, 1e8)) + length_stats[prefix] = ( + max(max_completed_l, len(completed)), + min(min_prefix_l, _len_longest_common_prefix(prefix, completed)), + ) + + # truncate requests for completed sequences longer than model context. + shortened = [] + completion_spans = [] + long_completions = 0 + for prefix, completed in zip(tokenized[0::2], tokenized[1::2]): + max_completed_l, prefix_l = length_stats[prefix] + # compute truncation length + truncation = max(0, max_completed_l - self._max_tokens - 1) + prefix_l = prefix_l - truncation + if prefix_l <= 0: + # completion too long, prefix is eliminated for some requests. + long_completions += 1 + truncation = max(0, len(completed) - self._max_tokens - 1) + prefix_l = 1 + # truncate the completed sequence + completed = completed[truncation:] + shortened.append(completed) + # scores do not include initial bos, substract 1 to span bounds + completion_spans.append((prefix_l - 1, len(completed) - 1)) + + if long_completions > 0: + logging.info( + f"Prefix eliminated for {long_completions} requests with " + + "completion longer than context." + ) + + # model scoring, returns num_requests x (logp, is_greedy, length). + results = self._loglikelihood( + shortened, + score_spans=completion_spans, + tokenize=False, + ) + return [(r[0], r[1] == r[2]) for r in results] + + def loglikelihood_rolling(self, requests) -> list[float]: + """Compute full log-likelihood of a string, with no truncation, for perplexity computation + - We will use the full max context length of the model. + - For inputs that exceed the max context length, we divide the tokenized string into chunks of up to + the max context length. + - IMPORTANT: Each document's loglikelihood/perplexity is computed *separately*, unlike other implementations + which may simply concatenate multiple documents together. + - IMPORTANT: We maximize the amount of context for each prediction. Specifically, for inputs that we break into + multiple chunks, the last input will still a full-sized context. + Example: + Input tokens: [ 0 1 2 3 4 5 6 7 8 9 ] + Prefix: EOT + Max context length: 4 + Resulting input/prediction pairs: + INPUT: EOT 0 1 2 + PRED: 0 1 2 3 + INPUT: 3 4 5 6 + PRED: 4 5 6 7 + INPUT: 5 6 7 8 + PRED: 8 9 + Observe that: + 1. Each token is predicted exactly once + 2. For the last pair, we provide the full context, but only score the last two tokens + :param requests: list[Instance] + A list of Instance objects with property `args` which returns a tuple (context,). + string: str + String for which we are computing overall loglikelihood + :return: list[tuple[float]] + A list of tuples (logprob,) + logprob: float + The log probability of `context` conditioned on the EOT token. + """ + logging.info( + "Estimating loglikelihood rolling for %d sequences." % len(requests) + ) + inputs = [req.args[0] for req in requests] + return [t[0] for t in self._loglikelihood(inputs)] + + def generate_until(self, requests) -> list[str]: + """Generate greedily until a stopping sequence + :param requests: list[Instance] + A list of Instance objects with property `args` which returns a tuple (context, until). + context: str + Context string + until: [str] + The string sequences to generate until. These string sequences + may each span across multiple tokens, or may be part of one token. + :return: list[str] + A list of strings continuation + continuation: str + The generated continuation. + """ + logging.info("Generating continuation for %d sequences." % len(requests)) + contexts, options = zip(*[req.args for req in requests]) + # contrary to the doc the second element of the tuple contains + # {'do_sample': False, 'until': ['\n\n'], 'temperature': 0} + keys = list(options[0].keys()) + assert "until" in keys + untils = [x["until"] for x in options] + completions = [] + for context, until in tqdm(zip(contexts, untils), total=len(contexts)): + if ( + hasattr(self._tokenizer, "apply_chat_template") + and self._tokenizer.chat_template is not None + ): + messages = [{"role": "user", "content": context}] + context = self._tokenizer.apply_chat_template( + messages, tokenize=False, add_generation_prompt=True + ) + + max_tokens = min( + self._max_tokens, + self._tokenizer.model_max_length - len(self._tokenizer.encode(context)), + ) + text = "" + for response in stream_generate( + self._model, self._tokenizer, prompt=context, max_tokens=max_tokens + ): + text += response.text + if any(u in text for u in until): + text = _rstrip_until(text, until) + completions.append(text) + break + else: + completions.append(text) + return completions + + +def main(): + parser = argparse.ArgumentParser( + "Evaluate an MLX model using lm-evaluation-harness." + ) + parser.add_argument("--model", help="Model to evaluate", required=True) + parser.add_argument("--tasks", nargs="+", required=True) + parser.add_argument( + "--output-dir", default=".", help="Output directory for result files." + ) + parser.add_argument("--batch-size", type=int, default=16, help="Batch size") + parser.add_argument("--num-shots", type=int, default=0, help="Number of shots") + parser.add_argument( + "--max-tokens", + type=int, + help="Maximum nunber of tokens to generate. Defaults to the model's max context length.", + ) + parser.add_argument("--seed", type=int, default=123, help="Random seed.") + args = parser.parse_args() + + output_dir = Path(args.output_dir) + output_dir.mkdir(parents=True, exist_ok=True) + + # Silence tokenizer warnings + os.environ["TOKENIZERS_PARALLELISM"] = "false" + + mx.random.seed(args.seed) + + lm = MLXLM(args.model, batch_size=args.batch_size, max_tokens=args.max_tokens) + + results = lm_eval.simple_evaluate( + model=lm, + tasks=args.tasks, + num_fewshot=args.num_shots, + random_seed=args.seed, + numpy_random_seed=args.seed, + torch_random_seed=args.seed, + fewshot_random_seed=args.seed, + ) + + model_name = args.model.replace("/", "_") + task_names = "_".join(args.tasks) + ver = version("lm_eval") + filename = f"eval_{model_name}_{task_names}_{args.num_shots:02d}_v_{ver}.json" + output_path = output_dir / filename + output_path.write_text(json.dumps(results["results"], indent=4)) + print("Results:") + for result in results["results"].values(): + print(json.dumps(result, indent=4)) diff --git a/llms/setup.py b/llms/setup.py index 1c696dc0..b88dcd33 100644 --- a/llms/setup.py +++ b/llms/setup.py @@ -28,12 +28,14 @@ setup( python_requires=">=3.8", extras_require={ "testing": ["datasets"], + "evaluation": ["lm-eval"], }, entry_points={ "console_scripts": [ "mlx_lm.cache_prompt = mlx_lm.cache_prompt:main", "mlx_lm.chat = mlx_lm.chat:main", "mlx_lm.convert = mlx_lm.convert:main", + "mlx_lm.evaluate = mlx_lm.evaluate:main", "mlx_lm.fuse = mlx_lm.fuse:main", "mlx_lm.generate = mlx_lm.generate:main", "mlx_lm.lora = mlx_lm.lora:main", From 2211b27388f8cc5725360e3b14c2b114d61b0e8d Mon Sep 17 00:00:00 2001 From: Alex Barron Date: Sun, 8 Dec 2024 14:21:50 -0800 Subject: [PATCH 096/188] Mixed Quantizations (#1132) * saving/loading mixed quantizations * comment * add bits per weight * more concise bpw * count bias too --- llms/mlx_lm/tuner/utils.py | 12 ++++---- llms/mlx_lm/utils.py | 61 +++++++++++++++++++++++++++++++++----- 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/llms/mlx_lm/tuner/utils.py b/llms/mlx_lm/tuner/utils.py index 835cb482..8351ed1b 100644 --- a/llms/mlx_lm/tuner/utils.py +++ b/llms/mlx_lm/tuner/utils.py @@ -250,12 +250,14 @@ def remove_lora_layers(model: nn.Module) -> nn.Module: return model -def print_trainable_parameters(model): - def nparams(m): - if isinstance(m, (nn.QuantizedLinear, nn.QuantizedEmbedding)): - return m.weight.size * (32 // m.bits) - return sum(v.size for _, v in tree_flatten(m.parameters())) +def nparams(module): + if hasattr(module, "bits"): + n = 0 if not hasattr(module, "bias") else module.bias.size + return n + module.weight.size * 32 // module.bits + return sum(v.size for _, v in tree_flatten(module.parameters())) + +def print_trainable_parameters(model): leaf_modules = tree_flatten( model.leaf_modules(), is_leaf=lambda m: isinstance(m, nn.Module) ) diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index 86b786ce..66a106a1 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -16,7 +16,7 @@ from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Type, import mlx.core as mx import mlx.nn as nn from huggingface_hub import snapshot_download -from mlx.utils import tree_flatten, tree_reduce +from mlx.utils import tree_flatten, tree_map, tree_reduce from transformers import PreTrainedTokenizer # Local imports @@ -24,7 +24,7 @@ from .models import cache from .sample_utils import make_logits_processors, make_sampler from .tokenizer_utils import TokenizerWrapper, load_tokenizer from .tuner.utils import dequantize as dequantize_model -from .tuner.utils import load_adapters +from .tuner.utils import load_adapters, nparams # Constants MODEL_REMAPPING = { @@ -127,6 +127,17 @@ def _get_classes(config: dict): return arch.Model, arch.ModelArgs +def compute_bits_per_weight(model): + model_bytes = tree_reduce( + lambda acc, x: acc + x.nbytes if isinstance(x, mx.array) else acc, model, 0 + ) + leaf_modules = tree_flatten( + model.leaf_modules(), is_leaf=lambda m: isinstance(m, nn.Module) + ) + model_params = sum(nparams(m) for _, m in leaf_modules) + return model_bytes * 8 / model_params + + def get_model_path(path_or_hf_repo: str, revision: Optional[str] = None) -> Path: """ Ensures the model is available locally. If the path does not exist locally, @@ -496,15 +507,20 @@ def load_model( weights = model.sanitize(weights) if (quantization := config.get("quantization", None)) is not None: - # Handle legacy models which may not have everything quantized + def class_predicate(p, m): + # Handle custom per layer quantizations + if p in config["quantization"]: + return config["quantization"][p] if not hasattr(m, "to_quantized"): return False + # Handle legacy models which may not have everything quantized return f"{p}.scales" in weights nn.quantize( model, - **quantization, + group_size=quantization["group_size"], + bits=quantization["bits"], class_predicate=class_predicate, ) @@ -707,7 +723,13 @@ def save_weights( def quantize_model( - model: nn.Module, config: dict, q_group_size: int, q_bits: int + model: nn.Module, + config: dict, + q_group_size: int, + q_bits: int, + quant_predicate: Optional[ + Callable[[str, nn.Module, dict], Union[bool, dict]] + ] = None, ) -> Tuple: """ Applies quantization to the model weights. @@ -717,17 +739,37 @@ def quantize_model( config (dict): Model configuration. q_group_size (int): Group size for quantization. q_bits (int): Bits per weight for quantization. + quant_predicate (Callable): A callable that decides how + to quantize each layer based on the path. + Accepts the layer `path`, the `module` and the model `config`. + Returns either a bool to signify quantize/no quantize or + a dict of quantization parameters to pass to `to_quantized`. Returns: Tuple: Tuple containing quantized weights and config. """ quantized_config = copy.deepcopy(config) - nn.quantize(model, q_group_size, q_bits) quantized_config["quantization"] = {"group_size": q_group_size, "bits": q_bits} + + # Add any custom quantization parameters to the config as we go + def _class_predicate(p, m): + bool_or_params = quant_predicate(p, m, config) + quantized_config["quantization"][p] = bool_or_params + return bool_or_params + + nn.quantize( + model, + q_group_size, + q_bits, + class_predicate=_class_predicate if quant_predicate else None, + ) # support hf model tree #957 quantized_config["quantization_config"] = quantized_config["quantization"] quantized_weights = dict(tree_flatten(model.parameters())) + bpw = compute_bits_per_weight(model) + print(f"[INFO] Quantized model with {bpw:.3f} bits per weight.") + return quantized_weights, quantized_config @@ -764,6 +806,9 @@ def convert( upload_repo: str = None, revision: Optional[str] = None, dequantize: bool = False, + quant_predicate: Optional[ + Callable[[str, nn.Module, dict], Union[bool, dict]] + ] = None, ): # Check the save path is empty if isinstance(mlx_path, str): @@ -789,7 +834,9 @@ def convert( if quantize: print("[INFO] Quantizing") model.load_weights(list(weights.items())) - weights, config = quantize_model(model, config, q_group_size, q_bits) + weights, config = quantize_model( + model, config, q_group_size, q_bits, quant_predicate=quant_predicate + ) if dequantize: print("[INFO] Dequantizing") From 1fd6aae871e9e21613ae90624cb4a72bdf709cc6 Mon Sep 17 00:00:00 2001 From: hehua2008 Date: Mon, 9 Dec 2024 14:09:04 +0800 Subject: [PATCH 097/188] Fix flux training with batch size (#1135) Co-authored-by: Angelos Katharopoulos --- flux/flux/sampler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/flux/flux/sampler.py b/flux/flux/sampler.py index 54c4fe35..e7a1080d 100644 --- a/flux/flux/sampler.py +++ b/flux/flux/sampler.py @@ -50,6 +50,7 @@ class FluxSampler: if noise is not None else mx.random.normal(x.shape, dtype=x.dtype, key=key) ) + t = t.reshape([-1] + [1] * (x.ndim - 1)) return x * (1 - t) + t * noise def step(self, pred, x_t, t, t_prev): From ed91bbc4dcf2734203c5302e2cfd1c5a10daa2e2 Mon Sep 17 00:00:00 2001 From: Peter Sibley Date: Mon, 9 Dec 2024 02:01:53 -0500 Subject: [PATCH 098/188] Fix final message at end of flux training (#1143) --- flux/dreambooth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flux/dreambooth.py b/flux/dreambooth.py index ffdb02d7..f82178b9 100644 --- a/flux/dreambooth.py +++ b/flux/dreambooth.py @@ -289,4 +289,4 @@ if __name__ == "__main__": tic = time.time() save_adapters("final_adapters.safetensors", flux, args) - print(f"Training successful. Saved final weights to {args.adapter_file}.") + print("Training successful.") From 893b3f085e01dc1db224d9f983a7a82fb4f4d584 Mon Sep 17 00:00:00 2001 From: hehua2008 Date: Mon, 9 Dec 2024 15:29:48 +0800 Subject: [PATCH 099/188] Change Flux default max_shift to 1.15 to match the official one (#1137) --- flux/flux/sampler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flux/flux/sampler.py b/flux/flux/sampler.py index e7a1080d..6f293edc 100644 --- a/flux/flux/sampler.py +++ b/flux/flux/sampler.py @@ -7,7 +7,7 @@ import mlx.core as mx class FluxSampler: - def __init__(self, name: str, base_shift: float = 0.5, max_shift: float = 1.5): + def __init__(self, name: str, base_shift: float = 0.5, max_shift: float = 1.15): self._base_shift = base_shift self._max_shift = max_shift self._schnell = "schnell" in name From 5687d5b99b66171a234705d0fa30721076f7446e Mon Sep 17 00:00:00 2001 From: n8programs <43304488+N8python@users.noreply.github.com> Date: Mon, 9 Dec 2024 10:58:25 -0500 Subject: [PATCH 100/188] Adds EXAONE architecture. (#1145) * Adds EXAONE architecture. * nits + format * format * clean up and fix rope * clean up and fix rope --------- Co-authored-by: Awni Hannun --- llms/mlx_lm/models/exaone.py | 163 +++++++++++++++++++++++++++++++ llms/mlx_lm/models/llama.py | 120 ++--------------------- llms/mlx_lm/models/olmo2.py | 121 ++--------------------- llms/mlx_lm/models/rope_utils.py | 91 +++++++++++++++++ llms/mlx_lm/tuner/utils.py | 2 + llms/tests/test_models.py | 39 ++++++++ 6 files changed, 312 insertions(+), 224 deletions(-) create mode 100644 llms/mlx_lm/models/exaone.py create mode 100644 llms/mlx_lm/models/rope_utils.py diff --git a/llms/mlx_lm/models/exaone.py b/llms/mlx_lm/models/exaone.py new file mode 100644 index 00000000..eaed5dd8 --- /dev/null +++ b/llms/mlx_lm/models/exaone.py @@ -0,0 +1,163 @@ +# Copyright © 2024 Apple Inc. + +from dataclasses import dataclass +from typing import Any, Dict, Optional, Union + +import mlx.core as mx +import mlx.nn as nn + +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention +from .rope_utils import initialize_rope + + +@dataclass +class ModelArgs(BaseModelArgs): + model_type: str + hidden_size: int + num_layers: int + intermediate_size: int + num_attention_heads: int + vocab_size: int + rope_theta: float + layer_norm_epsilon: float + num_key_value_heads: int + head_dim: Optional[int] = None + max_position_embeddings: Optional[int] = None + rope_traditional: bool = False + rope_scaling: Optional[Dict[str, Union[float, str]]] = None + tie_word_embeddings: bool = True + attention_bias: bool = False + mlp_bias: bool = False + + +class AttentionModule(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + dim = args.hidden_size + self.n_heads = n_heads = args.num_attention_heads + self.n_kv_heads = n_kv_heads = args.num_key_value_heads + self.head_dim = head_dim = args.head_dim or (dim // n_heads) + self.scale = head_dim**-0.5 + + self.q_proj = nn.Linear(dim, n_heads * head_dim, bias=args.attention_bias) + self.k_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=args.attention_bias) + self.v_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=args.attention_bias) + self.out_proj = nn.Linear(n_heads * head_dim, dim, bias=args.attention_bias) + + self.rope = initialize_rope( + self.head_dim, + args.rope_theta, + args.rope_traditional, + args.rope_scaling, + args.max_position_embeddings, + ) + + def __call__( + self, x: mx.array, mask: Optional[mx.array] = None, cache: Optional[Any] = None + ) -> mx.array: + B, L, D = x.shape + q = self.q_proj(x).reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) + k = self.k_proj(x).reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) + v = self.v_proj(x).reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) + + if cache is not None: + q = self.rope(q, offset=cache.offset) + k = self.rope(k, offset=cache.offset) + k, v = cache.update_and_fetch(k, v) + else: + q = self.rope(q) + k = self.rope(k) + + out = scaled_dot_product_attention( + q, k, v, cache=cache, scale=self.scale, mask=mask + ) + out = out.transpose(0, 2, 1, 3).reshape(B, L, D) + return self.out_proj(out) + + +class Attention(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.attention = AttentionModule(args) + + +class MLP(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + dim = args.hidden_size + hidden_dim = args.intermediate_size + self.c_fc_0 = nn.Linear(dim, hidden_dim, bias=args.mlp_bias) + self.c_fc_1 = nn.Linear(dim, hidden_dim, bias=args.mlp_bias) + self.c_proj = nn.Linear(hidden_dim, dim, bias=args.mlp_bias) + + def __call__(self, x: mx.array) -> mx.array: + return self.c_proj(nn.silu(self.c_fc_0(x)) * self.c_fc_1(x)) + + +class TransformerBlock(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.ln_1 = nn.RMSNorm(args.hidden_size, eps=args.layer_norm_epsilon) + self.attn = Attention(args) + self.ln_2 = nn.RMSNorm(args.hidden_size, eps=args.layer_norm_epsilon) + self.mlp = MLP(args) + + def __call__( + self, + x: mx.array, + mask: Optional[mx.array] = None, + cache: Optional[Any] = None, + ) -> mx.array: + h = x + self.attn.attention(self.ln_1(x), mask, cache) + out = h + self.mlp(self.ln_2(h)) + return out + + +class ExaoneModel(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.wte = nn.Embedding(args.vocab_size, args.hidden_size) + self.h = [TransformerBlock(args) for _ in range(args.num_layers)] + self.ln_f = nn.RMSNorm(args.hidden_size, eps=args.layer_norm_epsilon) + + def __call__( + self, + inputs: mx.array, + cache=None, + ): + h = self.wte(inputs) + mask = create_attention_mask(h, cache) + + if cache is None: + cache = [None] * len(self.h) + + for layer, c in zip(self.h, cache): + h = layer(h, mask, cache=c) + + return self.ln_f(h) + + +class Model(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.args = args + self.model_type = args.model_type + self.transformer = ExaoneModel(args) + if not args.tie_word_embeddings: + self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=False) + + def __call__( + self, + inputs: mx.array, + cache=None, + ): + out = self.transformer(inputs, cache) + if self.args.tie_word_embeddings: + out = self.transformer.wte.as_linear(out) + else: + out = self.lm_head(out) + return out + + @property + def layers(self): + return self.transformer.h diff --git a/llms/mlx_lm/models/llama.py b/llms/mlx_lm/models/llama.py index 438278e5..290cb83e 100644 --- a/llms/mlx_lm/models/llama.py +++ b/llms/mlx_lm/models/llama.py @@ -7,6 +7,7 @@ import mlx.core as mx import mlx.nn as nn from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention +from .rope_utils import initialize_rope @dataclass @@ -32,117 +33,6 @@ class ModelArgs(BaseModelArgs): if self.num_key_value_heads is None: self.num_key_value_heads = self.num_attention_heads - if self.rope_scaling: - if not "factor" in self.rope_scaling: - raise ValueError(f"rope_scaling must contain 'factor'") - rope_type = self.rope_scaling.get("type") or self.rope_scaling.get( - "rope_type" - ) - if rope_type is None: - raise ValueError( - f"rope_scaling must contain either 'type' or 'rope_type'" - ) - if rope_type not in ["linear", "dynamic", "llama3"]: - raise ValueError( - "rope_scaling 'type' currently only supports 'linear', 'dynamic' or 'llama3'" - ) - - -class DynamicNTKScalingRoPE(nn.Module): - """Implements the rotary positional encoding with Dynamic NTK scaling and Llama 3 RoPE.""" - - def __init__( - self, - dims: int, - max_position_embeddings: int = 2048, - traditional: bool = False, - base: float = 10000, - scale: float = 1.0, - rope_type: str = "default", - rope_scaling: dict = None, - ): - super().__init__() - self.dims = dims - self.max_position_embeddings = max_position_embeddings - self.traditional = traditional - self.scale = scale - self.rope_type = rope_type - self.rope_scaling = rope_scaling - self.base = base - self.compute_freqs() - - def compute_freqs(self): - if self.rope_type != "llama3": - self._freqs = None - return - factor = self.rope_scaling["factor"] - low_freq_factor = self.rope_scaling.get("low_freq_factor", 1.0) - high_freq_factor = self.rope_scaling.get("high_freq_factor", 4.0) - old_context_len = self.rope_scaling.get( - "original_max_position_embeddings", - 8192, - ) - - low_freq_wavelen = old_context_len / low_freq_factor - high_freq_wavelen = old_context_len / high_freq_factor - - freqs = self.base ** (mx.arange(0, self.dims, 2) / self.dims) - wavelens = 2 * mx.pi * freqs - - freqs = mx.where(wavelens > low_freq_wavelen, freqs * factor, freqs) - is_medium_freq = (wavelens > high_freq_wavelen) & (wavelens < low_freq_wavelen) - smooth_factors = (old_context_len / wavelens - low_freq_factor) / ( - high_freq_factor - low_freq_factor - ) - smooth_freqs = freqs / ((1 - smooth_factors) / factor + smooth_factors) - self._freqs = mx.where(is_medium_freq, smooth_freqs, freqs) - self.base = None - - def extra_repr(self): - return ( - f"{self.dims}, traditional={self.traditional}, " - f"max_position_embeddings={self.max_position_embeddings}, " - f"scaling_factor={self.scale}, rope_type={self.rope_type}" - ) - - def __call__(self, x, offset: int = 0): - return mx.fast.rope( - x, - self.dims, - traditional=self.traditional, - base=self.base, - scale=self.scale, - offset=offset, - freqs=self._freqs, - ) - - -def initialize_rope(args: ModelArgs): - head_dim = args.head_dim or args.hidden_size // args.num_attention_heads - - rope_scaling = args.rope_scaling - rope_type = "default" - rope_scale = 1.0 - - if rope_scaling is not None: - rope_type = ( - rope_scaling.get("type") or rope_scaling.get("rope_type") or "default" - ) - if rope_type == "linear": - rope_scale = 1 / rope_scaling["factor"] - elif rope_type == "llama3": - rope_scale = 1.0 # The scaling is handled internally for llama3 - - return DynamicNTKScalingRoPE( - dims=head_dim, - max_position_embeddings=args.max_position_embeddings, - traditional=args.rope_traditional, - base=args.rope_theta, - scale=rope_scale, - rope_type=rope_type, - rope_scaling=rope_scaling, - ) - class Attention(nn.Module): def __init__(self, args: ModelArgs): @@ -165,7 +55,13 @@ class Attention(nn.Module): self.v_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=attention_bias) self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=attention_bias) - self.rope = initialize_rope(args) + self.rope = initialize_rope( + self.head_dim, + args.rope_theta, + args.rope_traditional, + args.rope_scaling, + args.max_position_embeddings, + ) def __call__( self, diff --git a/llms/mlx_lm/models/olmo2.py b/llms/mlx_lm/models/olmo2.py index a28fdcc1..64d7e116 100644 --- a/llms/mlx_lm/models/olmo2.py +++ b/llms/mlx_lm/models/olmo2.py @@ -7,6 +7,7 @@ import mlx.core as mx import mlx.nn as nn from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention +from .rope_utils import initialize_rope @dataclass @@ -32,117 +33,6 @@ class ModelArgs(BaseModelArgs): if self.num_key_value_heads is None: self.num_key_value_heads = self.num_attention_heads - if self.rope_scaling: - if not "factor" in self.rope_scaling: - raise ValueError(f"rope_scaling must contain 'factor'") - rope_type = self.rope_scaling.get("type") or self.rope_scaling.get( - "rope_type" - ) - if rope_type is None: - raise ValueError( - f"rope_scaling must contain either 'type' or 'rope_type'" - ) - if rope_type not in ["linear", "dynamic", "llama3"]: - raise ValueError( - "rope_scaling 'type' currently only supports 'linear', 'dynamic' or 'llama3'" - ) - - -class DynamicNTKScalingRoPE(nn.Module): - """Implements the rotary positional encoding with Dynamic NTK scaling and Llama 3 RoPE.""" - - def __init__( - self, - dims: int, - max_position_embeddings: int = 2048, - traditional: bool = False, - base: float = 10000, - scale: float = 1.0, - rope_type: str = "default", - rope_scaling: dict = None, - ): - super().__init__() - self.dims = dims - self.max_position_embeddings = max_position_embeddings - self.traditional = traditional - self.scale = scale - self.rope_type = rope_type - self.rope_scaling = rope_scaling - self.base = base - self.compute_freqs() - - def compute_freqs(self): - if self.rope_type != "llama3": - self._freqs = None - return - factor = self.rope_scaling["factor"] - low_freq_factor = self.rope_scaling.get("low_freq_factor", 1.0) - high_freq_factor = self.rope_scaling.get("high_freq_factor", 4.0) - old_context_len = self.rope_scaling.get( - "original_max_position_embeddings", - 8192, - ) - - low_freq_wavelen = old_context_len / low_freq_factor - high_freq_wavelen = old_context_len / high_freq_factor - - freqs = self.base ** (mx.arange(0, self.dims, 2) / self.dims) - wavelens = 2 * mx.pi * freqs - - freqs = mx.where(wavelens > low_freq_wavelen, freqs * factor, freqs) - is_medium_freq = (wavelens > high_freq_wavelen) & (wavelens < low_freq_wavelen) - smooth_factors = (old_context_len / wavelens - low_freq_factor) / ( - high_freq_factor - low_freq_factor - ) - smooth_freqs = freqs / ((1 - smooth_factors) / factor + smooth_factors) - self._freqs = mx.where(is_medium_freq, smooth_freqs, freqs) - self.base = None - - def extra_repr(self): - return ( - f"{self.dims}, traditional={self.traditional}, " - f"max_position_embeddings={self.max_position_embeddings}, " - f"scaling_factor={self.scale}, rope_type={self.rope_type}" - ) - - def __call__(self, x, offset: int = 0): - return mx.fast.rope( - x, - self.dims, - traditional=self.traditional, - base=self.base, - scale=self.scale, - offset=offset, - freqs=self._freqs, - ) - - -def initialize_rope(args: ModelArgs): - head_dim = args.head_dim or args.hidden_size // args.num_attention_heads - - rope_scaling = args.rope_scaling - rope_type = "default" - rope_scale = 1.0 - - if rope_scaling is not None: - rope_type = ( - rope_scaling.get("type") or rope_scaling.get("rope_type") or "default" - ) - if rope_type == "linear": - rope_scale = 1 / rope_scaling["factor"] - elif rope_type == "llama3": - rope_scale = 1.0 # The scaling is handled internally for llama3 - - return DynamicNTKScalingRoPE( - dims=head_dim, - max_position_embeddings=args.max_position_embeddings, - traditional=args.rope_traditional, - base=args.rope_theta, - scale=rope_scale, - rope_type=rope_type, - rope_scaling=rope_scaling, - ) - class Attention(nn.Module): def __init__(self, args: ModelArgs): @@ -165,7 +55,14 @@ class Attention(nn.Module): self.v_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=attention_bias) self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=attention_bias) - self.rope = initialize_rope(args) + self.rope = initialize_rope( + self.head_dim, + args.rope_theta, + args.rope_traditional, + args.rope_scaling, + args.max_position_embeddings, + ) + self.q_norm = nn.RMSNorm(n_heads * head_dim, args.rms_norm_eps) self.k_norm = nn.RMSNorm(n_kv_heads * head_dim, args.rms_norm_eps) diff --git a/llms/mlx_lm/models/rope_utils.py b/llms/mlx_lm/models/rope_utils.py new file mode 100644 index 00000000..d30b432d --- /dev/null +++ b/llms/mlx_lm/models/rope_utils.py @@ -0,0 +1,91 @@ +# Copyright © 2023-2024 Apple Inc. + +from typing import Optional + +import mlx.core as mx +import mlx.nn as nn + + +class Llama3RoPE(nn.Module): + + def __init__( + self, + dims: int, + max_position_embeddings: int = 2048, + traditional: bool = False, + base: float = 10000, + scaling_config: dict = None, + ): + super().__init__() + self.dims = dims + self.max_position_embeddings = max_position_embeddings + self.traditional = traditional + + factor = scaling_config["factor"] + low_freq_factor = scaling_config.get("low_freq_factor", 1.0) + high_freq_factor = scaling_config.get("high_freq_factor", 4.0) + old_context_len = scaling_config.get( + "original_max_position_embeddings", + 8192, + ) + + low_freq_wavelen = old_context_len / low_freq_factor + high_freq_wavelen = old_context_len / high_freq_factor + + freqs = base ** (mx.arange(0, dims, 2) / dims) + wavelens = 2 * mx.pi * freqs + + freqs = mx.where(wavelens > low_freq_wavelen, freqs * factor, freqs) + is_medium_freq = (wavelens > high_freq_wavelen) & (wavelens < low_freq_wavelen) + smooth_factors = (old_context_len / wavelens - low_freq_factor) / ( + high_freq_factor - low_freq_factor + ) + smooth_freqs = freqs / ((1 - smooth_factors) / factor + smooth_factors) + self._freqs = mx.where(is_medium_freq, smooth_freqs, freqs) + + def extra_repr(self): + return ( + f"{self.dims}, traditional={self.traditional}, " + f"max_position_embeddings={self.max_position_embeddings}" + ) + + def __call__(self, x, offset: int = 0): + return mx.fast.rope( + x, + self.dims, + traditional=self.traditional, + base=None, + scale=1.0, + offset=offset, + freqs=self._freqs, + ) + + +def initialize_rope( + dims, + base, + traditional, + scaling_config: Optional[dict] = None, + max_position_embeddings: Optional[int] = None, +): + if scaling_config is not None: + rope_type = scaling_config.get("type") or scaling_config.get( + "rope_type", "default" + ) + else: + rope_type = "default" + + if rope_type in ["default", "linear"]: + scale = 1 / scaling_config["factor"] if rope_type == "linear" else 1.0 + return nn.RoPE(dims, traditional=traditional, base=base, scale=scale) + + elif rope_type == "llama3": + return Llama3RoPE( + dims=dims, + max_position_embeddings=max_position_embeddings, + traditional=traditional, + base=base, + scaling_config=scaling_config, + ) + else: + raise ValueError(f"Unsupported RoPE type {rope_type}") diff --git a/llms/mlx_lm/tuner/utils.py b/llms/mlx_lm/tuner/utils.py index 8351ed1b..6821f434 100644 --- a/llms/mlx_lm/tuner/utils.py +++ b/llms/mlx_lm/tuner/utils.py @@ -144,6 +144,8 @@ def linear_to_lora_layers( "mixer.out_proj", ] ) + elif model.model_type == "exaone": + keys = set(["attn.attention.q_proj", "attn.attention.v_proj"]) else: raise ValueError(f"Lora does not support {model.model_type}") diff --git a/llms/tests/test_models.py b/llms/tests/test_models.py index edb594d7..374a5113 100644 --- a/llms/tests/test_models.py +++ b/llms/tests/test_models.py @@ -2,7 +2,9 @@ import unittest import mlx.core as mx +import mlx.nn as nn from mlx.utils import tree_map +from mlx_lm.models import rope_utils from mlx_lm.models.cache import KVCache, RotatingKVCache, make_prompt_cache @@ -126,6 +128,26 @@ class TestModels(unittest.TestCase): self.assertEqual(cache.offset, 22) self.assertTrue(mx.allclose(x, k[..., -2:, :])) + def test_rope(self): + rope = rope_utils.initialize_rope(32, base=100, traditional=False) + self.assertTrue(isinstance(rope, nn.RoPE)) + + rope = rope_utils.initialize_rope( + 32, + base=100, + traditional=False, + scaling_config={"rope_type": "linear", "factor": 10.0}, + ) + self.assertTrue(isinstance(rope, nn.RoPE)) + + rope = rope_utils.initialize_rope( + 32, + base=100, + traditional=False, + scaling_config={"rope_type": "llama3", "factor": 2.0}, + ) + self.assertTrue(isinstance(rope, rope_utils.Llama3RoPE)) + def model_test_runner(self, model, model_type, vocab_size, num_layers): self.assertEqual(len(model.layers), num_layers) @@ -812,6 +834,23 @@ class TestModels(unittest.TestCase): model, args.model_type, args.vocab_size, args.num_hidden_layers ) + def test_exaone(self): + from mlx_lm.models import exaone + + args = exaone.ModelArgs( + model_type="exaone", + hidden_size=128, + num_layers=4, + intermediate_size=256, + num_attention_heads=8, + num_key_value_heads=2, + vocab_size=1000, + layer_norm_epsilon=1e-4, + rope_theta=10000, + ) + model = exaone.Model(args) + self.model_test_runner(model, args.model_type, args.vocab_size, args.num_layers) + if __name__ == "__main__": unittest.main() From 12083c4b7ed041fcb733ac7821eb726a0169ff76 Mon Sep 17 00:00:00 2001 From: madroid Date: Tue, 10 Dec 2024 00:53:58 +0800 Subject: [PATCH 101/188] Support for multiple EOS tokens (#1141) * Support for multiple EOS tokens * Change _eos_token_ids type from list to set * Remove model_config & add eos_token_id * nits --------- Co-authored-by: Awni Hannun --- llms/mlx_lm/tokenizer_utils.py | 23 +++++++++++++++++++---- llms/mlx_lm/utils.py | 24 +++++++++++++----------- llms/tests/test_utils_load_model.py | 4 ++-- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/llms/mlx_lm/tokenizer_utils.py b/llms/mlx_lm/tokenizer_utils.py index 0fa41ac0..10a257f6 100644 --- a/llms/mlx_lm/tokenizer_utils.py +++ b/llms/mlx_lm/tokenizer_utils.py @@ -254,21 +254,33 @@ class TokenizerWrapper: huggingface tokenizer. """ - def __init__(self, tokenizer, detokenizer_class=NaiveStreamingDetokenizer): + def __init__( + self, tokenizer, detokenizer_class=NaiveStreamingDetokenizer, eos_token_ids=None + ): self._tokenizer = tokenizer self._detokenizer = detokenizer_class(tokenizer) + self._eos_token_ids = ( + set(eos_token_ids) + if eos_token_ids is not None + else {tokenizer.eos_token_id} + ) def __getattr__(self, attr): if attr == "detokenizer": return self._detokenizer + elif attr == "eos_token_ids": + return self._eos_token_ids elif attr.startswith("_"): return self.__getattribute__(attr) else: return getattr(self._tokenizer, attr) def __setattr__(self, attr, value): - if attr == "detokenizer": - raise AttributeError("Cannot set the detokenizer.") + if attr in {"detokenizer", "eos_token_ids"}: + if attr == "detokenizer": + raise AttributeError("Cannot set the detokenizer.") + elif attr == "eos_token_ids": + self._eos_token_ids = set(value) if value is not None else set() elif attr.startswith("_"): super().__setattr__(attr, value) else: @@ -315,7 +327,7 @@ def _is_bpe_decoder(decoder): return isinstance(decoder, dict) and decoder.get("type", None) == "ByteLevel" -def load_tokenizer(model_path, tokenizer_config_extra={}): +def load_tokenizer(model_path, tokenizer_config_extra={}, eos_token_ids=None): """Load a huggingface tokenizer and try to infer the type of streaming detokenizer to use. @@ -336,7 +348,10 @@ def load_tokenizer(model_path, tokenizer_config_extra={}): elif _is_bpe_decoder(tokenizer_content["decoder"]): detokenizer_class = BPEStreamingDetokenizer + if isinstance(eos_token_ids, int): + eos_token_ids = [eos_token_ids] return TokenizerWrapper( AutoTokenizer.from_pretrained(model_path, **tokenizer_config_extra), detokenizer_class, + eos_token_ids=eos_token_ids, ) diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index 66a106a1..d81bb66a 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -361,7 +361,7 @@ def stream_generate( prompt_time = time.perf_counter() - tic prompt_tps = prompt.size / prompt_time tic = time.perf_counter() - if token == tokenizer.eos_token_id: + if token in tokenizer.eos_token_ids: break detokenizer.add_token(token) @@ -467,11 +467,11 @@ def load_model( lazy (bool): If False eval the model parameters to make sure they are loaded in memory before returning, otherwise they will be loaded when needed. Default: ``False`` - model_config (dict, optional): Configuration parameters for the model. - Defaults to an empty dictionary. + model_config (dict, optional): Optional configuration parameters for the + model. Defaults to an empty dictionary. get_model_classes (Callable[[dict], Tuple[Type[nn.Module], Type]], optional): A function that returns the model class and model args class given a config. - Defaults to the _get_classes function. + Defaults to the ``_get_classes`` function. Returns: nn.Module: The loaded and initialized model. @@ -480,7 +480,6 @@ def load_model( FileNotFoundError: If the weight files (.safetensors) are not found. ValueError: If the model class or args class are not found or cannot be instantiated. """ - config = load_config(model_path) config.update(model_config) @@ -530,7 +529,7 @@ def load_model( mx.eval(model.parameters()) model.eval() - return model + return model, config def load( @@ -563,11 +562,13 @@ def load( """ model_path = get_model_path(path_or_hf_repo) - model = load_model(model_path, lazy, model_config) + model, config = load_model(model_path, lazy) if adapter_path is not None: model = load_adapters(model, adapter_path) model.eval() - tokenizer = load_tokenizer(model_path, tokenizer_config) + tokenizer = load_tokenizer( + model_path, tokenizer_config, eos_token_ids=config.get("eos_token_id", None) + ) return model, tokenizer @@ -575,9 +576,10 @@ def load( def fetch_from_hub( model_path: Path, lazy: bool = False ) -> Tuple[nn.Module, dict, PreTrainedTokenizer]: - model = load_model(model_path, lazy) - config = load_config(model_path) - tokenizer = load_tokenizer(model_path) + model, config = load_model(model_path, lazy) + tokenizer = load_tokenizer( + model_path, eos_token_ids=config.get("eos_token_id", None) + ) return model, config, tokenizer diff --git a/llms/tests/test_utils_load_model.py b/llms/tests/test_utils_load_model.py index 73ee1352..5821f9e9 100644 --- a/llms/tests/test_utils_load_model.py +++ b/llms/tests/test_utils_load_model.py @@ -32,7 +32,7 @@ class TestLoadModelCustomGetClasses(unittest.TestCase): return CustomQwenModel, CustomQwenConfig model_path = get_model_path(HF_MODEL_PATH) - model = load_model(model_path, get_model_classes=custom_get_classes) + model, _ = load_model(model_path, get_model_classes=custom_get_classes) self.assertIsInstance(model, CustomQwenModel) self.assertTrue(hasattr(model, "custom_attribute")) @@ -41,7 +41,7 @@ class TestLoadModelCustomGetClasses(unittest.TestCase): def test_load_model_with_default_get_classes(self): model_path = get_model_path(HF_MODEL_PATH) - model = load_model(model_path) + model, _ = load_model(model_path) self.assertIsInstance(model, Qwen2Model) From 135c5818c1b2fbea5970b41a10172518dfa8a73a Mon Sep 17 00:00:00 2001 From: Alex Barron Date: Tue, 10 Dec 2024 11:26:04 -0800 Subject: [PATCH 102/188] Fix max_tokens (#1148) --- llms/mlx_lm/chat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llms/mlx_lm/chat.py b/llms/mlx_lm/chat.py index 7795d8d7..5a8245ef 100644 --- a/llms/mlx_lm/chat.py +++ b/llms/mlx_lm/chat.py @@ -79,7 +79,7 @@ def main(): model, tokenizer, prompt, - args.max_tokens, + max_tokens=args.max_tokens, sampler=make_sampler(args.temp, args.top_p), prompt_cache=prompt_cache, ): From 77b42b7c8bf27272f0263cd04ce962d20295504f Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Thu, 12 Dec 2024 10:37:26 -0800 Subject: [PATCH 103/188] fix llava (#1149) --- llava/generate.py | 7 +++---- llava/llava.py | 26 ++++++++------------------ llms/mlx_lm/generate.py | 7 ++++--- 3 files changed, 15 insertions(+), 25 deletions(-) diff --git a/llava/generate.py b/llava/generate.py index 8067839e..64313858 100644 --- a/llava/generate.py +++ b/llava/generate.py @@ -79,10 +79,10 @@ def load_image(image_source): def prepare_inputs(processor, image, prompt): if isinstance(image, str): image = load_image(image) - inputs = processor(prompt, image, return_tensors="np") + inputs = processor(image, prompt, return_tensors="np") pixel_values = mx.array(inputs["pixel_values"]) input_ids = mx.array(inputs["input_ids"]) - return input_ids, pixel_values + return pixel_values, input_ids def load_model(model_path, tokenizer_config={}): @@ -126,8 +126,7 @@ def main(): processor, model = load_model(args.model, tokenizer_config) prompt = codecs.decode(args.prompt, "unicode_escape") - - input_ids, pixel_values = prepare_inputs(processor, args.image, prompt) + pixel_values, input_ids = prepare_inputs(processor, args.image, prompt) print(prompt) generated_text = generate_text( diff --git a/llava/llava.py b/llava/llava.py index 9e6b7511..c5f190f8 100644 --- a/llava/llava.py +++ b/llava/llava.py @@ -104,31 +104,21 @@ class LlavaModel(nn.Module): self, image_features, inputs_embeds, input_ids ): image_token_index = self.config.image_token_index - num_images, num_image_patches, embed_dim = image_features.shape + batch_size, num_image_patches, embed_dim = image_features.shape # Positions of tokens in input_ids, assuming batch size is 1 - image_positions = np.where(input_ids[0] == image_token_index)[0].tolist() + image_positions = mx.array( + np.where(input_ids[0] == image_token_index)[0], mx.uint32 + ) - if len(image_positions) != num_images: + if len(image_positions) != num_image_patches: raise ValueError( f"The number of image tokens ({len(image_positions)}) does not " - f" match the number of image inputs ({num_images})." + f" match the number of image patches ({num_image_patches})." ) - text_segments = [] - start_idx = 0 - - for position in image_positions: - text_segments.append(inputs_embeds[:, start_idx:position]) - start_idx = position + 1 - - image_embeddings = mx.split(image_features, image_features.shape[0]) - final_embeddings = [v for p in zip(text_segments, image_embeddings) for v in p] - final_embeddings += [inputs_embeds[:, start_idx:]] - - # Create a final embedding of shape - # (1, num_image_patches*num_images + sequence_len, embed_dim) - return mx.concatenate(final_embeddings, axis=1) + inputs_embeds[0, image_positions] = image_features + return inputs_embeds def __call__(self, input_ids: mx.array, pixel_values: mx.array, cache=None): input_embddings = self.get_input_embeddings(input_ids, pixel_values) diff --git a/llms/mlx_lm/generate.py b/llms/mlx_lm/generate.py index 0c1b4acd..84dc63ca 100644 --- a/llms/mlx_lm/generate.py +++ b/llms/mlx_lm/generate.py @@ -1,6 +1,7 @@ # Copyright © 2023-2024 Apple Inc. import argparse +import codecs import json import sys @@ -188,6 +189,8 @@ def main(): elif using_cache: tokenizer.chat_template = metadata["chat_template"] + prompt = codecs.decode(args.prompt, "unicode_escape") + if not args.ignore_chat_template and ( hasattr(tokenizer, "apply_chat_template") and tokenizer.chat_template is not None @@ -199,7 +202,7 @@ def main(): messages.append( { "role": "user", - "content": sys.stdin.read() if args.prompt == "-" else args.prompt, + "content": sys.stdin.read() if prompt == "-" else prompt, } ) prompt = tokenizer.apply_chat_template( @@ -216,8 +219,6 @@ def main(): add_generation_prompt=True, ) prompt = prompt[test_prompt.index("") :] - else: - prompt = args.prompt sampler = make_sampler(args.temp, args.top_p, args.min_p, args.min_tokens_to_keep) response = generate( From 06af3c9b0eac1aea927dcbbda66cacd5aab76f4a Mon Sep 17 00:00:00 2001 From: madroid Date: Fri, 13 Dec 2024 02:37:40 +0800 Subject: [PATCH 104/188] Add finish_reason in GenerationResponse (#1153) --- llms/mlx_lm/utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index d81bb66a..493c1c42 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -16,7 +16,7 @@ from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Type, import mlx.core as mx import mlx.nn as nn from huggingface_hub import snapshot_download -from mlx.utils import tree_flatten, tree_map, tree_reduce +from mlx.utils import tree_flatten, tree_reduce from transformers import PreTrainedTokenizer # Local imports @@ -59,6 +59,7 @@ class GenerationResponse: generation_tokens (int): The number of generated tokens. generation_tps (float): The tokens-per-second for generation. peak_memory (float): The peak memory used so far in GB. + finish_reason (str): The reason the response is being sent: "length", "stop" or `None` """ text: str @@ -69,6 +70,7 @@ class GenerationResponse: generation_tokens: int generation_tps: float peak_memory: float + finish_reason: Optional[str] = None @contextlib.contextmanager @@ -375,6 +377,7 @@ def stream_generate( generation_tokens=n + 1, generation_tps=(n + 1) / (time.perf_counter() - tic), peak_memory=mx.metal.get_peak_memory() / 1e9, + finish_reason=None, ) detokenizer.finalize() @@ -387,6 +390,7 @@ def stream_generate( generation_tokens=n + 1, generation_tps=(n + 1) / (time.perf_counter() - tic), peak_memory=mx.metal.get_peak_memory() / 1e9, + finish_reason="stop" if token in tokenizer.eos_token_ids else "length", ) From 19abf3dcaac2809984800680842ae3d859dda6dc Mon Sep 17 00:00:00 2001 From: Angelos Katharopoulos Date: Thu, 12 Dec 2024 11:10:41 -0800 Subject: [PATCH 105/188] Replace unicode errors instead of raising exception (#1146) --- llms/mlx_lm/tokenizer_utils.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/llms/mlx_lm/tokenizer_utils.py b/llms/mlx_lm/tokenizer_utils.py index 10a257f6..114a35e7 100644 --- a/llms/mlx_lm/tokenizer_utils.py +++ b/llms/mlx_lm/tokenizer_utils.py @@ -3,8 +3,6 @@ from functools import partial from transformers import AutoTokenizer -REPLACEMENT_CHAR = "\ufffd" - class StreamingDetokenizer: """The streaming detokenizer interface so that we can detokenize one token at a time. @@ -51,11 +49,9 @@ class StreamingDetokenizer: def last_segment(self): """Return the last segment of readable text since last time this property was accessed.""" text = self.text - if text and text[-1] != REPLACEMENT_CHAR: - segment = text[self.offset :] - self.offset = len(text) - return segment - return "" + segment = text[self.offset :] + self.offset = len(text) + return segment class NaiveStreamingDetokenizer(StreamingDetokenizer): @@ -132,7 +128,7 @@ class SPMStreamingDetokenizer(StreamingDetokenizer): self.tokens = [] def _flush(self): - text = self._unflushed.replace(self._sep, b" ").decode("utf-8") + text = self._unflushed.replace(self._sep, b" ").decode("utf-8", "replace") if not self.text and self.trim_space and text and text[0] == " ": text = text[1:] self.text += text @@ -202,7 +198,7 @@ class BPEStreamingDetokenizer(StreamingDetokenizer): if is_added or self._byte_decoder[v[0]] == 32: current_text = bytearray( self._byte_decoder[c] for c in self._unflushed - ).decode("utf-8") + ).decode("utf-8", "replace") self.text += self._maybe_trim_space(current_text) if is_added: self.text += v @@ -214,7 +210,8 @@ class BPEStreamingDetokenizer(StreamingDetokenizer): def finalize(self): current_text = bytearray(self._byte_decoder[c] for c in self._unflushed).decode( - "utf-8" + "utf-8", + "replace", ) self.text += self._maybe_trim_space(current_text) self._unflushed = "" From 2ba0e3668382d2c18ab6f691e2f662081596269f Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Thu, 12 Dec 2024 11:12:21 -0800 Subject: [PATCH 106/188] [mlx-lm] Use top p in server (#1144) * use top p in server * couple other fixes --- llms/mlx_lm/sample_utils.py | 2 +- llms/mlx_lm/server.py | 2 +- llms/mlx_lm/utils.py | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/llms/mlx_lm/sample_utils.py b/llms/mlx_lm/sample_utils.py index f9868422..c77f056a 100644 --- a/llms/mlx_lm/sample_utils.py +++ b/llms/mlx_lm/sample_utils.py @@ -190,7 +190,7 @@ def make_repetition_penalty(penalty: float, context_size: int = 20): Callable[[mx.array, List[int]], mx.array]: The repetition penalty processor. """ - if penalty < 0 or not isinstance(penalty, float): + if penalty < 0 or not isinstance(penalty, (int, float)): raise ValueError(f"penalty must be a non-negative float, got {penalty}") def repetition_penalty_processor(tokens, logits): diff --git a/llms/mlx_lm/server.py b/llms/mlx_lm/server.py index ce09cf45..c12513ff 100644 --- a/llms/mlx_lm/server.py +++ b/llms/mlx_lm/server.py @@ -465,7 +465,7 @@ class APIHandler(BaseHTTPRequestHandler): text = "" tic = time.perf_counter() - sampler = make_sampler(self.temperature) + sampler = make_sampler(self.temperature, top_p=self.top_p) logits_processors = make_logits_processors( self.logit_bias, self.repetition_penalty, self.repetition_context_size ) diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index 493c1c42..b87f5a24 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -299,6 +299,9 @@ def generate_step( prompt_processed_tokens = 0 while y.size > prefill_step_size: model(y[:prefill_step_size][None], cache=prompt_cache) + maybe_quantize_kv_cache( + prompt_cache, quantized_kv_start, kv_group_size, kv_bits + ) mx.eval([c.state for c in prompt_cache]) prompt_progress_callback(prompt_processed_tokens, total_prompt_tokens) prompt_processed_tokens += prefill_step_size From 9f2ea5892e3a9517853c526a928268250741f623 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Thu, 12 Dec 2024 13:13:50 -0800 Subject: [PATCH 107/188] Bpe stream without space (#1154) * bpe streaming detokenization without space * version bump --- llms/mlx_lm/_version.py | 2 +- llms/mlx_lm/tokenizer_utils.py | 20 +++++++++----------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/llms/mlx_lm/_version.py b/llms/mlx_lm/_version.py index 0f885fba..3af2d5fd 100644 --- a/llms/mlx_lm/_version.py +++ b/llms/mlx_lm/_version.py @@ -1,3 +1,3 @@ # Copyright © 2023-2024 Apple Inc. -__version__ = "0.20.2" +__version__ = "0.20.4" diff --git a/llms/mlx_lm/tokenizer_utils.py b/llms/mlx_lm/tokenizer_utils.py index 114a35e7..8251e62f 100644 --- a/llms/mlx_lm/tokenizer_utils.py +++ b/llms/mlx_lm/tokenizer_utils.py @@ -195,18 +195,16 @@ class BPEStreamingDetokenizer(StreamingDetokenizer): self.tokens.append(token) v = self.tokenmap[token] is_added = token in self._added_ids - if is_added or self._byte_decoder[v[0]] == 32: - current_text = bytearray( - self._byte_decoder[c] for c in self._unflushed - ).decode("utf-8", "replace") - self.text += self._maybe_trim_space(current_text) - if is_added: - self.text += v - self._unflushed = "" - else: - self._unflushed = v - else: + if not is_added: self._unflushed += v + text = bytearray(self._byte_decoder[c] for c in self._unflushed).decode( + "utf-8", "replace" + ) + if is_added: + text += v + if not text.endswith("\ufffd"): + self.text += self._maybe_trim_space(text) + self._unflushed = "" def finalize(self): current_text = bytearray(self._byte_decoder[c] for c in self._unflushed).decode( From fc0674d2d8fa4cd384641f5473d1dc7ffca918df Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Sun, 15 Dec 2024 23:06:29 +0900 Subject: [PATCH 108/188] chore: update evaluate.py (#1159) occurence -> occurrence --- llms/mlx_lm/evaluate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llms/mlx_lm/evaluate.py b/llms/mlx_lm/evaluate.py index 423d5823..c4b15748 100644 --- a/llms/mlx_lm/evaluate.py +++ b/llms/mlx_lm/evaluate.py @@ -32,7 +32,7 @@ def _len_longest_common_prefix(a, b): def _rstrip_until(s, untils): - """Limit a string to the first occurence of any substring in untils.""" + """Limit a string to the first occurrence of any substring in untils.""" l = len(s) f = [s.find(u) for u in untils] f = [l if x < 0 else x for x in f] From dfa4dd6c93c4c2f81bfed6becb8af5cc3a89ae61 Mon Sep 17 00:00:00 2001 From: Prince Canuma Date: Mon, 16 Dec 2024 17:01:03 +0100 Subject: [PATCH 109/188] Add support for cohere2 (#1157) * add support for cohere2 * revert to act_fn to silu * fix tests and sliding window attention * add tests * add to tuner * fix sliding window * add coauthor :) Co-authored-by: n8programs <43304488+N8python@users.noreply.github.com> * Add rotating kvcache to save space * some nits * style * nits --------- Co-authored-by: n8programs <43304488+N8python@users.noreply.github.com> Co-authored-by: N8 Co-authored-by: Awni Hannun --- llms/mlx_lm/models/cohere2.py | 207 ++++++++++++++++++++++++++++++++++ llms/mlx_lm/tuner/utils.py | 1 + llms/mlx_lm/utils.py | 7 +- llms/tests/test_models.py | 16 +++ 4 files changed, 228 insertions(+), 3 deletions(-) create mode 100644 llms/mlx_lm/models/cohere2.py diff --git a/llms/mlx_lm/models/cohere2.py b/llms/mlx_lm/models/cohere2.py new file mode 100644 index 00000000..fcb4061b --- /dev/null +++ b/llms/mlx_lm/models/cohere2.py @@ -0,0 +1,207 @@ +# Copyright © 2023-2024 Apple Inc. + +from dataclasses import dataclass +from typing import Optional, Tuple + +import mlx.core as mx +import mlx.nn as nn + +from .base import BaseModelArgs, create_causal_mask, scaled_dot_product_attention +from .cache import KVCache, RotatingKVCache + + +@dataclass +class ModelArgs(BaseModelArgs): + model_type: str + hidden_size: int = 4096 + head_dim: int = 128 + num_hidden_layers: int = 32 + intermediate_size: int = 14336 + num_attention_heads: int = 32 + num_key_value_heads: int = 8 + rope_theta: float = 50000.0 + vocab_size: int = 256000 + layer_norm_eps: float = 1e-05 + logit_scale: float = 0.0625 + attention_bias: bool = False + layer_norm_bias: bool = False + sliding_window: int = 4096 + sliding_window_pattern: int = 4 + + +class Attention(nn.Module): + def __init__(self, args: ModelArgs, layer_idx: int): + super().__init__() + self.args = args + self.layer_idx = layer_idx + + dim = args.hidden_size + self.n_heads = n_heads = args.num_attention_heads + self.n_kv_heads = n_kv_heads = args.num_key_value_heads + self.head_dim = head_dim = args.head_dim + if (head_dim * n_heads) != dim: + raise ValueError( + f"hidden_size must be divisible by num_heads (got `hidden_size`: {dim}" + f" and `num_heads`: {n_heads})." + ) + self.scale = head_dim**-0.5 + + attetion_bias = args.attention_bias + + self.q_proj = nn.Linear(dim, n_heads * head_dim, bias=attetion_bias) + self.k_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=attetion_bias) + self.v_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=attetion_bias) + self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=attetion_bias) + + self.rope = nn.RoPE(head_dim, traditional=True, base=args.rope_theta) + + self.use_sliding_window = (layer_idx + 1) % args.sliding_window_pattern != 0 + + def __call__( + self, + x: mx.array, + mask: Optional[mx.array] = None, + cache: Optional[Tuple[mx.array, mx.array]] = None, + ) -> mx.array: + B, L, D = x.shape + + queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x) + + queries = queries.reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) + keys = keys.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) + values = values.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) + + # Apply RoPE only if sliding window is enabled + if self.use_sliding_window: + if cache is None: + queries = self.rope(queries) + keys = self.rope(keys) + else: + queries = self.rope(queries, offset=cache.offset) + keys = self.rope(keys, offset=cache.offset) + + if cache is not None: + keys, values = cache.update_and_fetch(keys, values) + + if self.use_sliding_window and mask is not None: + key_len = keys.shape[-2] + if mask.shape[-1] != key_len: + mask = mask[..., -key_len:] + + output = scaled_dot_product_attention( + queries, keys, values, cache=cache, scale=self.scale, mask=mask + ) + + output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) + return self.o_proj(output) + + +class MLP(nn.Module): + def __init__(self, dim, hidden_dim): + super().__init__() + self.gate_proj = nn.Linear(dim, hidden_dim, bias=False) + self.up_proj = nn.Linear(dim, hidden_dim, bias=False) + self.down_proj = nn.Linear(hidden_dim, dim, bias=False) + + def __call__(self, x): + return self.down_proj(nn.silu(self.gate_proj(x)) * self.up_proj(x)) + + +class TransformerBlock(nn.Module): + def __init__(self, args: ModelArgs, layer_idx: int): + super().__init__() + self.hidden_size = args.hidden_size + self.n_heads = args.num_attention_heads + + self.self_attn = Attention(args, layer_idx) + self.mlp = MLP(args.hidden_size, args.intermediate_size) + self.input_layernorm = nn.LayerNorm( + args.hidden_size, eps=args.layer_norm_eps, bias=args.layer_norm_bias + ) + self.args = args + + def __call__( + self, + x: mx.array, + mask: Optional[mx.array] = None, + cache: Optional[Tuple[mx.array, mx.array]] = None, + ) -> mx.array: + h = self.input_layernorm(x) + attn_h = self.self_attn(h, mask, cache) + ff_h = self.mlp(h) + return attn_h + ff_h + x + + +class CohereModel(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.args = args + self.vocab_size = args.vocab_size + self.num_hidden_layers = args.num_hidden_layers + assert self.vocab_size > 0 + self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size) + self.layers = [ + TransformerBlock(args=args, layer_idx=i) + for i in range(args.num_hidden_layers) + ] + self.norm = nn.LayerNorm( + args.hidden_size, eps=args.layer_norm_eps, bias=args.layer_norm_bias + ) + + def __call__( + self, + inputs: mx.array, + cache=None, + ): + h = self.embed_tokens(inputs) + + T = h.shape[1] + if T > 1: + offset = cache[0].offset if cache else 0 + mask = create_causal_mask(T, offset).astype(h.dtype) + else: + mask = None + + if cache is None: + cache = [None] * len(self.layers) + + for layer, c in zip(self.layers, cache): + h = layer(h, mask, c) + + return self.norm(h) + + +class Model(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.model_type = args.model_type + self.model = CohereModel(args) + self.args = args + + def __call__( + self, + inputs: mx.array, + cache=None, + ): + out = self.model(inputs, cache) + out = self.model.embed_tokens.as_linear(out) + out = out * self.model.args.logit_scale + return out + + def make_cache(self): + caches = [] + for i in range(self.args.num_hidden_layers): + if ( + i % self.args.sliding_window_pattern + == self.args.sliding_window_pattern - 1 + ): + caches.append(KVCache()) + else: + caches.append( + RotatingKVCache(max_size=self.args.sliding_window, keep=0) + ) + return caches + + @property + def layers(self): + return self.model.layers diff --git a/llms/mlx_lm/tuner/utils.py b/llms/mlx_lm/tuner/utils.py index 6821f434..3986952a 100644 --- a/llms/mlx_lm/tuner/utils.py +++ b/llms/mlx_lm/tuner/utils.py @@ -96,6 +96,7 @@ def linear_to_lora_layers( "gemma2", "starcoder2", "cohere", + "cohere2", "minicpm", "deepseek", "olmo2", diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index b87f5a24..4d69115e 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -187,9 +187,10 @@ def maybe_quantize_kv_cache(prompt_cache, quantized_kv_start, kv_group_size, kv_ and prompt_cache[0].offset > quantized_kv_start ): for i in range(len(prompt_cache)): - prompt_cache[i] = prompt_cache[i].to_quantized( - group_size=kv_group_size, bits=kv_bits - ) + if isinstance(prompt_cache[i], cache.KVCache): + prompt_cache[i] = prompt_cache[i].to_quantized( + group_size=kv_group_size, bits=kv_bits + ) def generate_step( diff --git a/llms/tests/test_models.py b/llms/tests/test_models.py index 374a5113..3097c522 100644 --- a/llms/tests/test_models.py +++ b/llms/tests/test_models.py @@ -851,6 +851,22 @@ class TestModels(unittest.TestCase): model = exaone.Model(args) self.model_test_runner(model, args.model_type, args.vocab_size, args.num_layers) + def test_cohere2(self): + from mlx_lm.models import cohere2 + + args = cohere2.ModelArgs( + model_type="cohere2", + hidden_size=4096, + head_dim=128, + num_hidden_layers=40, + sliding_window=4096, + sliding_window_pattern=4, + ) + model = cohere2.Model(args) + self.model_test_runner( + model, args.model_type, args.vocab_size, args.num_hidden_layers + ) + if __name__ == "__main__": unittest.main() From 845efddc8cb4578fe008c2ad0c26ec595e7f6b1e Mon Sep 17 00:00:00 2001 From: Billel Mokeddem Date: Tue, 17 Dec 2024 21:54:29 +0400 Subject: [PATCH 110/188] Fix decoding manually added tokens (#1164) * Fix decoding manually added tokens * fix + test * nit * nit * no lag bpe --------- Co-authored-by: Awni Hannun --- llms/mlx_lm/tokenizer_utils.py | 44 +++++++++++++++++++--------------- llms/tests/test_tokenizers.py | 4 ++++ 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/llms/mlx_lm/tokenizer_utils.py b/llms/mlx_lm/tokenizer_utils.py index 8251e62f..ca3d6c06 100644 --- a/llms/mlx_lm/tokenizer_utils.py +++ b/llms/mlx_lm/tokenizer_utils.py @@ -127,23 +127,23 @@ class SPMStreamingDetokenizer(StreamingDetokenizer): self.text = "" self.tokens = [] - def _flush(self): + def _try_flush(self, force=False): text = self._unflushed.replace(self._sep, b" ").decode("utf-8", "replace") + if not force and text.endswith("\ufffd"): + return if not self.text and self.trim_space and text and text[0] == " ": text = text[1:] self.text += text + self._unflushed = b"" def add_token(self, token): self.tokens.append(token) v = self.tokenmap[token] - if v.startswith(self._sep): - self._flush() - self._unflushed = v - else: - self._unflushed += v + self._unflushed += v + self._try_flush() def finalize(self): - self._flush() + self._try_flush(force=True) self._unflushed = b"" @@ -158,7 +158,6 @@ class BPEStreamingDetokenizer(StreamingDetokenizer): _space_matches = (".", "?", "!", ",", "n't", "'m", "'s", "'ve", "'re") def __init__(self, tokenizer): - self.clean_spaces = tokenizer.clean_up_tokenization_spaces # Extract the tokens in a list from id to text @@ -172,14 +171,22 @@ class BPEStreamingDetokenizer(StreamingDetokenizer): # https://github.com/openai/gpt-2/blob/master/src/encoder.py self.make_byte_decoder() - self._added_ids = set(tokenizer.added_tokens_decoder.keys()) - def reset(self): self.offset = 0 self._unflushed = "" self.text = "" self.tokens = [] + def _decode_bytes(self, seq): + barr = bytearray() + for c in seq: + res = self._byte_decoder.get(c, False) + if res: + barr.append(res) + else: + barr.extend(bytes(c, "utf-8")) + return barr.decode("utf-8", "replace") + def _maybe_trim_space(self, current_text): if len(current_text) == 0: return current_text @@ -194,15 +201,14 @@ class BPEStreamingDetokenizer(StreamingDetokenizer): def add_token(self, token): self.tokens.append(token) v = self.tokenmap[token] - is_added = token in self._added_ids - if not is_added: - self._unflushed += v - text = bytearray(self._byte_decoder[c] for c in self._unflushed).decode( - "utf-8", "replace" - ) - if is_added: - text += v - if not text.endswith("\ufffd"): + self._unflushed += v + text = self._decode_bytes(self._unflushed) + + # For multi-byte utf-8 wait until they are complete + # For single spaces wait until the next token to clean it if needed + if not text.endswith("\ufffd") and not ( + len(v) == 1 and self._byte_decoder[v[0]] == 32 + ): self.text += self._maybe_trim_space(text) self._unflushed = "" diff --git a/llms/tests/test_tokenizers.py b/llms/tests/test_tokenizers.py index db6b9f9e..3009d1b1 100644 --- a/llms/tests/test_tokenizers.py +++ b/llms/tests/test_tokenizers.py @@ -58,6 +58,9 @@ class TestTokenizers(unittest.TestCase): tokens = tokenizer.encode("import 'package:flutter/material.dart';") check(tokens) + tokens = tokenizer.encode("hello\nworld") + check(tokens) + def test_tokenizers(self): tokenizer_repos = [ ("mlx-community/Qwen1.5-0.5B-Chat-4bit", BPEStreamingDetokenizer), @@ -65,6 +68,7 @@ class TestTokenizers(unittest.TestCase): ("mlx-community/Phi-3.5-mini-instruct-4bit", SPMStreamingDetokenizer), ("mlx-community/Mistral-7B-Instruct-v0.3", SPMStreamingDetokenizer), ("mlx-community/Llama-3.2-1B-Instruct-4bit", BPEStreamingDetokenizer), + ("mlx-community/Falcon3-7B-Instruct-4bit", BPEStreamingDetokenizer), ] for tokenizer_repo, expected_detokenizer in tokenizer_repos: with self.subTest(tokenizer=tokenizer_repo): From db109184b7f23ce3166c6cfd4682b092b4bdfbb6 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Wed, 18 Dec 2024 18:46:50 -0800 Subject: [PATCH 111/188] Fix no template prompt + top_k sampling (#1166) * fix no template prompt * add top_k sampling * fix chinese --- llms/mlx_lm/generate.py | 12 +++--------- llms/mlx_lm/sample_utils.py | 34 ++++++++++++++++++++++++++++++++- llms/tests/test_sample_utils.py | 23 +++++++++++++++++++++- 3 files changed, 58 insertions(+), 11 deletions(-) diff --git a/llms/mlx_lm/generate.py b/llms/mlx_lm/generate.py index 84dc63ca..afb1394e 100644 --- a/llms/mlx_lm/generate.py +++ b/llms/mlx_lm/generate.py @@ -1,7 +1,6 @@ # Copyright © 2023-2024 Apple Inc. import argparse -import codecs import json import sys @@ -189,8 +188,8 @@ def main(): elif using_cache: tokenizer.chat_template = metadata["chat_template"] - prompt = codecs.decode(args.prompt, "unicode_escape") - + prompt = args.prompt.replace("\\n", "\n").replace("\\t", "\t") + prompt = sys.stdin.read() if prompt == "-" else prompt if not args.ignore_chat_template and ( hasattr(tokenizer, "apply_chat_template") and tokenizer.chat_template is not None @@ -199,12 +198,7 @@ def main(): messages = [{"role": "system", "content": args.system_prompt}] else: messages = [] - messages.append( - { - "role": "user", - "content": sys.stdin.read() if prompt == "-" else prompt, - } - ) + messages.append({"role": "user", "content": prompt}) prompt = tokenizer.apply_chat_template( messages, tokenize=False, add_generation_prompt=True ) diff --git a/llms/mlx_lm/sample_utils.py b/llms/mlx_lm/sample_utils.py index c77f056a..c48a32cf 100644 --- a/llms/mlx_lm/sample_utils.py +++ b/llms/mlx_lm/sample_utils.py @@ -12,6 +12,7 @@ def make_sampler( top_p: float = 0.0, min_p: float = 0.0, min_tokens_to_keep: int = 1, + top_k: int = -1, ) -> Callable[mx.array, mx.array]: """ Make a sampler function for use with ``generate_step``. @@ -25,6 +26,8 @@ def make_sampler( probability) that a token probability must have to be considered. min_tokens_to_keep (int, optional): Minimum number of tokens that cannot be filtered by min_p sampling. + top_k (int, optional): The top k tokens ranked by probability to constrain + the sampling to. Returns: Callable[mx.array, mx.array]: @@ -36,6 +39,8 @@ def make_sampler( return lambda x: top_p_sampling(x, top_p, temp) elif min_p != 0.0: return lambda x: min_p_sampling(x, min_p, min_tokens_to_keep, temp) + elif top_k > 0: + return lambda x: top_k_sampling(x, top_k, temp) else: return lambda x: categorical_sampling(x, temp) @@ -79,6 +84,33 @@ def make_logits_processors( return logits_processors +@partial(mx.compile, inputs=mx.random.state, outputs=mx.random.state) +def top_k_sampling( + logprobs: mx.array, + top_k: int, + temperature=1.0, +) -> mx.array: + """ + Sample from only the top K tokens ranked by probability. + + Args: + logprobs: A vector of log probabilities. + top_k (int): Top k tokens to sample from. + """ + vocab_size = logprobs.shape[-1] + if not isinstance(top_k, int) or not (0 < top_k < vocab_size): + raise ValueError( + f"`top_k` has to be an integer in the (0, {vocab_size}] interval," + f" but is {top_k}." + ) + logprobs = logprobs * (1 / temperature) + mask_idx = mx.argpartition(-logprobs, kth=top_k - 1, axis=-1)[..., top_k:] + masked_logprobs = mx.put_along_axis( + logprobs, mask_idx, mx.array(-float("inf"), logprobs.dtype), axis=-1 + ) + return mx.random.categorical(masked_logprobs, axis=-1) + + @partial(mx.compile, inputs=mx.random.state, outputs=mx.random.state) def min_p_sampling( logprobs: mx.array, @@ -87,7 +119,7 @@ def min_p_sampling( temperature=1.0, ) -> mx.array: """ - Apply min-p sampling to the logits. + Apply min-p sampling to the logprobs. Min-p keeps all tokens that are above a minimum probability, scaled by the probability of the most likely token. As a result, the filter is more diff --git a/llms/tests/test_sample_utils.py b/llms/tests/test_sample_utils.py index ebc90ce8..c45fa443 100644 --- a/llms/tests/test_sample_utils.py +++ b/llms/tests/test_sample_utils.py @@ -1,7 +1,7 @@ import unittest import mlx.core as mx -from mlx_lm.sample_utils import min_p_sampling, top_p_sampling +from mlx_lm.sample_utils import min_p_sampling, top_k_sampling, top_p_sampling class TestSampleUtils(unittest.TestCase): @@ -42,6 +42,27 @@ class TestSampleUtils(unittest.TestCase): token = min_p_sampling(logits, 0.05) self.assertTrue(token in (0, 3)) + def test_top_k_sampling(self): + probs = mx.array([0.9, 0.0, 0.0, 0.1])[None] + logits = mx.log(probs) + + token = top_k_sampling(logits, 1).item() + self.assertEqual(token, 0) + + probs = mx.array([0.5, 0.0, 0.0, 0.5])[None] + tokens = set() + for _ in range(100): + token = top_k_sampling(logits, 2) + tokens.add(token.item()) + self.assertEqual(tokens, {0, 3}) + + # Batch mode works + probs = mx.array([[0.9, 0.0, 0.0, 0.1], [0.0, 0.8, 0.0, 0.1]]) + logits = mx.log(probs) + + tokens = top_k_sampling(logits, 1) + self.assertEqual(tokens.tolist(), [0, 1]) + if __name__ == "__main__": unittest.main() From d4ef909d4ab44d9f8cf89f5baa8a433d76d7d6b1 Mon Sep 17 00:00:00 2001 From: Alex Barron Date: Wed, 18 Dec 2024 19:43:52 -0800 Subject: [PATCH 112/188] Length masking for batch inputs (#1173) * length masking * add mask to mlx_lm model interface * remove lengths * fix test: * comment + fix --- llms/mlx_lm/models/base.py | 10 +++++++++- llms/mlx_lm/models/cohere.py | 7 +++++-- llms/mlx_lm/models/cohere2.py | 14 ++++++-------- llms/mlx_lm/models/dbrx.py | 7 +++++-- llms/mlx_lm/models/deepseek.py | 7 +++++-- llms/mlx_lm/models/deepseek_v2.py | 8 ++++++-- llms/mlx_lm/models/exaone.py | 7 +++++-- llms/mlx_lm/models/gemma.py | 7 +++++-- llms/mlx_lm/models/gemma2.py | 7 +++++-- llms/mlx_lm/models/gpt2.py | 7 +++++-- llms/mlx_lm/models/gpt_bigcode.py | 7 +++++-- llms/mlx_lm/models/gpt_neox.py | 7 +++++-- llms/mlx_lm/models/hunyuan.py | 7 +++++-- llms/mlx_lm/models/internlm2.py | 7 +++++-- llms/mlx_lm/models/llama.py | 7 +++++-- llms/mlx_lm/models/minicpm.py | 7 +++++-- llms/mlx_lm/models/mixtral.py | 7 +++++-- llms/mlx_lm/models/nemotron.py | 7 +++++-- llms/mlx_lm/models/olmo.py | 10 +++++++--- llms/mlx_lm/models/olmo2.py | 7 +++++-- llms/mlx_lm/models/openelm.py | 7 +++++-- llms/mlx_lm/models/phi.py | 8 +++++--- llms/mlx_lm/models/phi3.py | 7 +++++-- llms/mlx_lm/models/phi3small.py | 7 +++++-- llms/mlx_lm/models/phimoe.py | 7 +++++-- llms/mlx_lm/models/phixtral.py | 4 +++- llms/mlx_lm/models/plamo.py | 7 +++++-- llms/mlx_lm/models/qwen.py | 3 ++- llms/mlx_lm/models/qwen2.py | 7 +++++-- llms/mlx_lm/models/qwen2_moe.py | 7 +++++-- llms/mlx_lm/models/recurrent_gemma.py | 8 +++++--- llms/mlx_lm/models/stablelm.py | 5 ++++- llms/mlx_lm/models/starcoder2.py | 7 +++++-- llms/tests/test_models.py | 25 ++++++++++++++++++++++++- 34 files changed, 191 insertions(+), 72 deletions(-) diff --git a/llms/mlx_lm/models/base.py b/llms/mlx_lm/models/base.py index f02f49b1..ad7a4a65 100644 --- a/llms/mlx_lm/models/base.py +++ b/llms/mlx_lm/models/base.py @@ -23,7 +23,12 @@ class BaseModelArgs: ) -def create_causal_mask(N: int, offset: int = 0, window_size: Optional[int] = None): +def create_causal_mask( + N: int, + offset: int = 0, + window_size: Optional[int] = None, + lengths: Optional[mx.array] = None, +): rinds = mx.arange(offset + N) linds = mx.arange(offset, offset + N) if offset else rinds linds = linds[:, None] @@ -31,6 +36,9 @@ def create_causal_mask(N: int, offset: int = 0, window_size: Optional[int] = Non mask = linds < rinds if window_size is not None: mask = mask | (linds > rinds + window_size) + if lengths is not None: + lengths = lengths[:, None, None, None] + mask = mask | (rinds >= lengths) return mask * -1e9 diff --git a/llms/mlx_lm/models/cohere.py b/llms/mlx_lm/models/cohere.py index 7e002b0c..b2d16dd7 100644 --- a/llms/mlx_lm/models/cohere.py +++ b/llms/mlx_lm/models/cohere.py @@ -155,11 +155,13 @@ class CohereModel(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): h = self.embed_tokens(inputs) - mask = create_attention_mask(h, cache) + if mask is None: + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) @@ -180,9 +182,10 @@ class Model(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): - out = self.model(inputs, cache) + out = self.model(inputs, mask, cache) out = self.model.embed_tokens.as_linear(out) out = out * self.model.args.logit_scale return out diff --git a/llms/mlx_lm/models/cohere2.py b/llms/mlx_lm/models/cohere2.py index fcb4061b..ec0e9276 100644 --- a/llms/mlx_lm/models/cohere2.py +++ b/llms/mlx_lm/models/cohere2.py @@ -6,7 +6,7 @@ from typing import Optional, Tuple import mlx.core as mx import mlx.nn as nn -from .base import BaseModelArgs, create_causal_mask, scaled_dot_product_attention +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention from .cache import KVCache, RotatingKVCache @@ -151,16 +151,13 @@ class CohereModel(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): h = self.embed_tokens(inputs) - T = h.shape[1] - if T > 1: - offset = cache[0].offset if cache else 0 - mask = create_causal_mask(T, offset).astype(h.dtype) - else: - mask = None + if mask is None: + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) @@ -181,9 +178,10 @@ class Model(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): - out = self.model(inputs, cache) + out = self.model(inputs, mask, cache) out = self.model.embed_tokens.as_linear(out) out = out * self.model.args.logit_scale return out diff --git a/llms/mlx_lm/models/dbrx.py b/llms/mlx_lm/models/dbrx.py index 7be274cc..886b5630 100644 --- a/llms/mlx_lm/models/dbrx.py +++ b/llms/mlx_lm/models/dbrx.py @@ -197,11 +197,13 @@ class DBRX(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): h = self.wte(inputs) - mask = create_attention_mask(h, cache) + if mask is None: + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.blocks) @@ -223,9 +225,10 @@ class Model(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): - out = self.transformer(inputs, cache) + out = self.transformer(inputs, mask, cache) return self.lm_head(out) @property diff --git a/llms/mlx_lm/models/deepseek.py b/llms/mlx_lm/models/deepseek.py index b7b24dba..ffc30c36 100644 --- a/llms/mlx_lm/models/deepseek.py +++ b/llms/mlx_lm/models/deepseek.py @@ -211,9 +211,11 @@ class DeepseekModel(nn.Module): self, x: mx.array, cache: Optional[Any] = None, + mask: Optional[mx.array] = None, ) -> mx.array: h = self.embed_tokens(x) - mask = create_attention_mask(h, cache) + if mask is None: + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) @@ -236,8 +238,9 @@ class Model(nn.Module): self, inputs: mx.array, cache: Optional[Any] = None, + mask: Optional[mx.array] = None, ): - out = self.model(inputs, cache) + out = self.model(inputs, cache, mask) return self.lm_head(out) def sanitize(self, weights): diff --git a/llms/mlx_lm/models/deepseek_v2.py b/llms/mlx_lm/models/deepseek_v2.py index 444813b9..9027da7e 100644 --- a/llms/mlx_lm/models/deepseek_v2.py +++ b/llms/mlx_lm/models/deepseek_v2.py @@ -370,9 +370,12 @@ class DeepseekV2Model(nn.Module): self, x: mx.array, cache: Optional[Any] = None, + mask: Optional[mx.array] = None, ) -> mx.array: h = self.embed_tokens(x) - mask = create_attention_mask(h, cache) + + if mask is None: + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) @@ -395,8 +398,9 @@ class Model(nn.Module): self, inputs: mx.array, cache: Optional[Any] = None, + mask: Optional[mx.array] = None, ): - out = self.model(inputs, cache) + out = self.model(inputs, cache, mask) return self.lm_head(out) def sanitize(self, weights): diff --git a/llms/mlx_lm/models/exaone.py b/llms/mlx_lm/models/exaone.py index eaed5dd8..ee3ed1e8 100644 --- a/llms/mlx_lm/models/exaone.py +++ b/llms/mlx_lm/models/exaone.py @@ -123,10 +123,12 @@ class ExaoneModel(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): h = self.wte(inputs) - mask = create_attention_mask(h, cache) + if mask is None: + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.h) @@ -149,9 +151,10 @@ class Model(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): - out = self.transformer(inputs, cache) + out = self.transformer(inputs, mask, cache) if self.args.tie_word_embeddings: out = self.transformer.wte.as_linear(out) else: diff --git a/llms/mlx_lm/models/gemma.py b/llms/mlx_lm/models/gemma.py index 3f384c3f..0860ddeb 100644 --- a/llms/mlx_lm/models/gemma.py +++ b/llms/mlx_lm/models/gemma.py @@ -138,12 +138,14 @@ class GemmaModel(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): h = self.embed_tokens(inputs) h = h * (self.args.hidden_size**0.5) - mask = create_attention_mask(h, cache) + if mask is None: + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) @@ -164,9 +166,10 @@ class Model(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): - out = self.model(inputs, cache) + out = self.model(inputs, mask, cache) out = self.model.embed_tokens.as_linear(out) return out diff --git a/llms/mlx_lm/models/gemma2.py b/llms/mlx_lm/models/gemma2.py index 64951ae4..321a58ff 100644 --- a/llms/mlx_lm/models/gemma2.py +++ b/llms/mlx_lm/models/gemma2.py @@ -160,12 +160,14 @@ class GemmaModel(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): h = self.embed_tokens(inputs) h = h * (self.args.hidden_size**0.5) - mask = create_attention_mask(h, cache) + if mask is None: + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) @@ -187,9 +189,10 @@ class Model(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): - out = self.model(inputs, cache) + out = self.model(inputs, mask, cache) out = self.model.embed_tokens.as_linear(out) out = mx.tanh(out / self.final_logit_softcapping) out = out * self.final_logit_softcapping diff --git a/llms/mlx_lm/models/gpt2.py b/llms/mlx_lm/models/gpt2.py index 52076a34..5b277734 100644 --- a/llms/mlx_lm/models/gpt2.py +++ b/llms/mlx_lm/models/gpt2.py @@ -126,6 +126,7 @@ class GPT2Model(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): _, L = inputs.shape @@ -138,7 +139,8 @@ class GPT2Model(nn.Module): position_ids = mx.array(np.arange(L)) hidden_states += self.wpe(position_ids) - mask = create_attention_mask(hidden_states, cache) + if mask is None: + mask = create_attention_mask(hidden_states, cache) if cache is None: cache = [None] * len(self.h) @@ -159,9 +161,10 @@ class Model(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): - out = self.model(inputs, cache) + out = self.model(inputs, mask, cache) out = self.model.wte.as_linear(out) return out diff --git a/llms/mlx_lm/models/gpt_bigcode.py b/llms/mlx_lm/models/gpt_bigcode.py index 23e86e20..8415c59e 100644 --- a/llms/mlx_lm/models/gpt_bigcode.py +++ b/llms/mlx_lm/models/gpt_bigcode.py @@ -137,6 +137,7 @@ class GPTBigCodeModel(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): B, L = inputs.shape @@ -149,7 +150,8 @@ class GPTBigCodeModel(nn.Module): position_ids = mx.array(np.arange(L)) hidden_states += self.wpe(position_ids) - mask = create_attention_mask(hidden_states, cache) + if mask is None: + mask = create_attention_mask(hidden_states, cache) if cache is None: cache = [None] * len(self.h) @@ -172,9 +174,10 @@ class Model(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): - out = self.transformer(inputs, cache) + out = self.transformer(inputs, mask, cache) if self.args.tie_word_embeddings: out = self.transformer.wte.as_linear(out) else: diff --git a/llms/mlx_lm/models/gpt_neox.py b/llms/mlx_lm/models/gpt_neox.py index ccb0b28b..5e124a67 100644 --- a/llms/mlx_lm/models/gpt_neox.py +++ b/llms/mlx_lm/models/gpt_neox.py @@ -146,13 +146,15 @@ class GPTNeoXModel(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): _, L = inputs.shape hidden_states = self.embed_in(inputs) - mask = create_attention_mask(hidden_states, cache) + if mask is None: + mask = create_attention_mask(hidden_states, cache) if cache is None: cache = [None] * len(self.h) @@ -176,9 +178,10 @@ class Model(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): - out = self.model(inputs, cache) + out = self.model(inputs, mask, cache) return out def sanitize(self, weights): diff --git a/llms/mlx_lm/models/hunyuan.py b/llms/mlx_lm/models/hunyuan.py index b098c20d..f9dc5652 100644 --- a/llms/mlx_lm/models/hunyuan.py +++ b/llms/mlx_lm/models/hunyuan.py @@ -239,11 +239,13 @@ class HunYuanModel(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): h = self.embed_tokens(inputs) - mask = create_attention_mask(h, cache) + if mask is None: + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) @@ -266,9 +268,10 @@ class Model(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): - out = self.model(inputs, cache) + out = self.model(inputs, mask, cache) return self.model.embed_tokens.as_linear(out) def sanitize(self, weights): diff --git a/llms/mlx_lm/models/internlm2.py b/llms/mlx_lm/models/internlm2.py index f5ce057e..28a095e1 100644 --- a/llms/mlx_lm/models/internlm2.py +++ b/llms/mlx_lm/models/internlm2.py @@ -193,11 +193,13 @@ class InternLM2Model(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): h = self.tok_embeddings(inputs) - mask = create_attention_mask(h, cache) + if mask is None: + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) @@ -220,9 +222,10 @@ class Model(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): - out = self.model(inputs, cache) + out = self.model(inputs, mask, cache) if self.args.tie_word_embeddings: out = self.model.tok_embeddings.as_linear(out) else: diff --git a/llms/mlx_lm/models/llama.py b/llms/mlx_lm/models/llama.py index 290cb83e..7b452ea4 100644 --- a/llms/mlx_lm/models/llama.py +++ b/llms/mlx_lm/models/llama.py @@ -155,11 +155,13 @@ class LlamaModel(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): h = self.embed_tokens(inputs) - mask = create_attention_mask(h, cache) + if mask is None: + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) @@ -182,9 +184,10 @@ class Model(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): - out = self.model(inputs, cache) + out = self.model(inputs, mask, cache) if self.args.tie_word_embeddings: out = self.model.embed_tokens.as_linear(out) else: diff --git a/llms/mlx_lm/models/minicpm.py b/llms/mlx_lm/models/minicpm.py index 907beb2a..edddd583 100644 --- a/llms/mlx_lm/models/minicpm.py +++ b/llms/mlx_lm/models/minicpm.py @@ -158,11 +158,13 @@ class MiniCPMModel(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): h = self.embed_tokens(inputs) * self.args.scale_emb - mask = create_attention_mask(h, cache) + if mask is None: + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) @@ -186,9 +188,10 @@ class Model(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): - out = self.model(inputs, cache) + out = self.model(inputs, mask, cache) if not self.args.tie_word_embeddings: out = self.lm_head(out / (self.args.hidden_size / self.args.dim_model_base)) diff --git a/llms/mlx_lm/models/mixtral.py b/llms/mlx_lm/models/mixtral.py index dd94d1f4..0afd1235 100644 --- a/llms/mlx_lm/models/mixtral.py +++ b/llms/mlx_lm/models/mixtral.py @@ -162,11 +162,13 @@ class MixtralModel(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): h = self.embed_tokens(inputs) - mask = create_attention_mask(h, cache) + if mask is None: + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) @@ -188,9 +190,10 @@ class Model(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): - out = self.model(inputs, cache) + out = self.model(inputs, mask, cache) return self.lm_head(out) def sanitize(self, weights): diff --git a/llms/mlx_lm/models/nemotron.py b/llms/mlx_lm/models/nemotron.py index f73c0277..eabfac8c 100644 --- a/llms/mlx_lm/models/nemotron.py +++ b/llms/mlx_lm/models/nemotron.py @@ -176,11 +176,13 @@ class NemotronModel(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): h = self.embed_tokens(inputs) - mask = create_attention_mask(h, cache) + if mask is None: + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) @@ -203,9 +205,10 @@ class Model(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): - out = self.model(inputs, cache) + out = self.model(inputs, mask, cache) if self.args.tie_word_embeddings: out = self.model.embed_tokens.as_linear(out) else: diff --git a/llms/mlx_lm/models/olmo.py b/llms/mlx_lm/models/olmo.py index 3627df06..4273b0ec 100644 --- a/llms/mlx_lm/models/olmo.py +++ b/llms/mlx_lm/models/olmo.py @@ -124,11 +124,13 @@ class Transformer(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): h = self.wte(inputs) - mask = create_attention_mask(h, cache) + if mask is None: + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.blocks) @@ -152,9 +154,10 @@ class OlmoModel(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): - return self.transformer(inputs, cache) + return self.transformer(inputs, mask, cache) class Model(nn.Module): @@ -167,9 +170,10 @@ class Model(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): - return self.model(inputs, cache) + return self.model(inputs, mask, cache) @property def layers(self): diff --git a/llms/mlx_lm/models/olmo2.py b/llms/mlx_lm/models/olmo2.py index 64d7e116..510ff882 100644 --- a/llms/mlx_lm/models/olmo2.py +++ b/llms/mlx_lm/models/olmo2.py @@ -163,10 +163,12 @@ class LlamaModel(nn.Module): self, inputs: mx.array, cache=None, + mask=None, ): h = self.embed_tokens(inputs) - mask = create_attention_mask(h, cache) + if mask is None: + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) @@ -190,8 +192,9 @@ class Model(nn.Module): self, inputs: mx.array, cache=None, + mask=None, ): - out = self.model(inputs, cache) + out = self.model(inputs, cache, mask) if self.args.tie_word_embeddings: out = self.model.embed_tokens.as_linear(out) else: diff --git a/llms/mlx_lm/models/openelm.py b/llms/mlx_lm/models/openelm.py index 408802f4..504fe95c 100644 --- a/llms/mlx_lm/models/openelm.py +++ b/llms/mlx_lm/models/openelm.py @@ -178,11 +178,13 @@ class OpenELMModel(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): h = self.token_embeddings(inputs) - mask = create_attention_mask(h, cache) + if mask is None: + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) @@ -205,9 +207,10 @@ class Model(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): - out = self.transformer(inputs, cache) + out = self.transformer(inputs, mask, cache) if self.args.share_input_output_layers: out = self.transformer.token_embeddings.as_linear(out) else: diff --git a/llms/mlx_lm/models/phi.py b/llms/mlx_lm/models/phi.py index 510025ea..e9724691 100644 --- a/llms/mlx_lm/models/phi.py +++ b/llms/mlx_lm/models/phi.py @@ -143,10 +143,11 @@ class PhiModel(nn.Module): config.hidden_size, eps=config.layer_norm_eps ) - def __call__(self, x, cache): + def __call__(self, x, mask, cache): x = self.embed_tokens(x) - mask = create_attention_mask(x, cache) + if mask is None: + mask = create_attention_mask(x, cache) if cache is None: cache = [None] * len(self.layers) @@ -167,9 +168,10 @@ class Model(nn.Module): def __call__( self, x: mx.array, + mask: mx.array = None, cache=None, ) -> mx.array: - y = self.model(x, cache) + y = self.model(x, mask, cache) return self.lm_head(y) @property diff --git a/llms/mlx_lm/models/phi3.py b/llms/mlx_lm/models/phi3.py index ee6efc49..d1c21e25 100644 --- a/llms/mlx_lm/models/phi3.py +++ b/llms/mlx_lm/models/phi3.py @@ -168,11 +168,13 @@ class Phi3Model(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): h = self.embed_tokens(inputs) - mask = create_attention_mask(h, cache) + if mask is None: + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) @@ -194,9 +196,10 @@ class Model(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): - out = self.model(inputs, cache) + out = self.model(inputs, mask, cache) return self.lm_head(out) @property diff --git a/llms/mlx_lm/models/phi3small.py b/llms/mlx_lm/models/phi3small.py index 53e1a638..cd566eec 100644 --- a/llms/mlx_lm/models/phi3small.py +++ b/llms/mlx_lm/models/phi3small.py @@ -258,13 +258,15 @@ class Phi3Model(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): h = self.embed_tokens(inputs) if self.mup_embedding_multiplier: h = self.mup_embedding_multiplier * h - mask = create_attention_mask(h, cache) + if mask is None: + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) @@ -290,9 +292,10 @@ class Model(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): - out = self.model(inputs, cache) + out = self.model(inputs, mask, cache) out = self.model.embed_tokens.as_linear(out) if self.mup_width_multiplier: out = out / self.mup_width_multiplier diff --git a/llms/mlx_lm/models/phimoe.py b/llms/mlx_lm/models/phimoe.py index f42a6dd0..bddcb128 100644 --- a/llms/mlx_lm/models/phimoe.py +++ b/llms/mlx_lm/models/phimoe.py @@ -155,11 +155,13 @@ class PhiMoEModel(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ) -> mx.array: h = self.embed_tokens(inputs) - mask = create_attention_mask(h, cache) + if mask is None: + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) @@ -181,9 +183,10 @@ class Model(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): - out = self.model(inputs, cache) + out = self.model(inputs, mask, cache) return self.lm_head(out) def sanitize(self, weights): diff --git a/llms/mlx_lm/models/phixtral.py b/llms/mlx_lm/models/phixtral.py index 42d647b0..5477c2c0 100644 --- a/llms/mlx_lm/models/phixtral.py +++ b/llms/mlx_lm/models/phixtral.py @@ -175,7 +175,9 @@ class Model(nn.Module): mask: mx.array = None, cache=None, ) -> mx.array: - mask = create_attention_mask(x, cache) + + if mask is None: + mask = create_attention_mask(x, cache) y = self.transformer(x, mask, cache) return self.lm_head(y) diff --git a/llms/mlx_lm/models/plamo.py b/llms/mlx_lm/models/plamo.py index c8e5bf50..9107daad 100644 --- a/llms/mlx_lm/models/plamo.py +++ b/llms/mlx_lm/models/plamo.py @@ -174,10 +174,12 @@ class PlamoModel(nn.Module): self, inputs: mx.array, cache: Optional[Any] = None, + mask: Optional[mx.array] = None, ) -> mx.array: h = self.embed_tokens(inputs) - mask = create_attention_mask(h, cache) + if mask is None: + mask = create_attention_mask(h, cache) if cache is None: cache = [None for _ in range(len(self.layers.layers))] @@ -202,8 +204,9 @@ class Model(nn.Module): self, inputs: mx.array, cache: Optional[Any] = None, + mask: Optional[mx.array] = None, ) -> mx.array: - out = self.model(inputs, cache) + out = self.model(inputs, cache, mask) return self.lm_head(out) @property diff --git a/llms/mlx_lm/models/qwen.py b/llms/mlx_lm/models/qwen.py index 8145a890..ec8a0199 100644 --- a/llms/mlx_lm/models/qwen.py +++ b/llms/mlx_lm/models/qwen.py @@ -123,7 +123,8 @@ class QwenModel(nn.Module): def __call__(self, inputs, mask=None, cache=None): x = self.wte(inputs) - mask = create_attention_mask(x, cache) + if mask is None: + mask = create_attention_mask(x, cache) if cache is None: cache = [None] * len(self.h) diff --git a/llms/mlx_lm/models/qwen2.py b/llms/mlx_lm/models/qwen2.py index fac59d78..381767c4 100644 --- a/llms/mlx_lm/models/qwen2.py +++ b/llms/mlx_lm/models/qwen2.py @@ -149,11 +149,13 @@ class Qwen2Model(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): h = self.embed_tokens(inputs) - mask = create_attention_mask(h, cache) + if mask is None: + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) @@ -176,9 +178,10 @@ class Model(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): - out = self.model(inputs, cache) + out = self.model(inputs, mask, cache) if self.args.tie_word_embeddings: out = self.model.embed_tokens.as_linear(out) else: diff --git a/llms/mlx_lm/models/qwen2_moe.py b/llms/mlx_lm/models/qwen2_moe.py index 167fc5dd..c6aba622 100644 --- a/llms/mlx_lm/models/qwen2_moe.py +++ b/llms/mlx_lm/models/qwen2_moe.py @@ -187,11 +187,13 @@ class Qwen2MoeModel(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): h = self.embed_tokens(inputs) - mask = create_attention_mask(h, cache) + if mask is None: + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) @@ -213,9 +215,10 @@ class Model(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): - out = self.model(inputs, cache) + out = self.model(inputs, mask, cache) return self.lm_head(out) def sanitize(self, weights): diff --git a/llms/mlx_lm/models/recurrent_gemma.py b/llms/mlx_lm/models/recurrent_gemma.py index 49e4bb8f..ad07d925 100644 --- a/llms/mlx_lm/models/recurrent_gemma.py +++ b/llms/mlx_lm/models/recurrent_gemma.py @@ -389,6 +389,7 @@ class Griffin(nn.Module): def __call__( self, tokens, + mask: mx.array = None, cache=None, ): x = self.embed_tokens(tokens) @@ -402,7 +403,8 @@ class Griffin(nn.Module): if block.temporal_block_type != "recurrent": mask_cache = [cache[i]] - mask = create_attention_mask(x, mask_cache) + if mask is None: + mask = create_attention_mask(x, mask_cache) for i, block in enumerate(self.layers): x = block(x, mask=mask, cache=cache[i]) @@ -418,12 +420,12 @@ class Model(nn.Module): self.model_type = config.model_type self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False) - def __call__(self, tokens: mx.array, cache=None) -> mx.array: + def __call__(self, tokens: mx.array, mask: mx.array = None, cache=None) -> mx.array: """ Args: tokens: Sequence of input tokens. """ - logits = self.model(tokens, cache=cache) + logits = self.model(tokens, mask=mask, cache=cache) if "lm_head" in self: logits = self.lm_head(logits) else: diff --git a/llms/mlx_lm/models/stablelm.py b/llms/mlx_lm/models/stablelm.py index 482bb324..0bbc2ca4 100644 --- a/llms/mlx_lm/models/stablelm.py +++ b/llms/mlx_lm/models/stablelm.py @@ -199,7 +199,10 @@ class Model(nn.Module): mask: mx.array = None, cache=None, ) -> mx.array: - mask = create_attention_mask(x, cache) + + if mask is None: + mask = create_attention_mask(x, cache) + y = self.model(x, mask, cache) return self.lm_head(y) diff --git a/llms/mlx_lm/models/starcoder2.py b/llms/mlx_lm/models/starcoder2.py index d7e626f2..71c397f6 100644 --- a/llms/mlx_lm/models/starcoder2.py +++ b/llms/mlx_lm/models/starcoder2.py @@ -125,11 +125,13 @@ class Starcoder2Model(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): h = self.embed_tokens(inputs) - mask = create_attention_mask(h, cache) + if mask is None: + mask = create_attention_mask(h, cache) if cache is None: cache = [None] * len(self.layers) @@ -152,9 +154,10 @@ class Model(nn.Module): def __call__( self, inputs: mx.array, + mask: mx.array = None, cache=None, ): - out = self.model(inputs, cache) + out = self.model(inputs, mask, cache) if self.args.tie_word_embeddings: out = self.model.embed_tokens.as_linear(out) else: diff --git a/llms/tests/test_models.py b/llms/tests/test_models.py index 3097c522..7b4376bb 100644 --- a/llms/tests/test_models.py +++ b/llms/tests/test_models.py @@ -5,6 +5,7 @@ import mlx.core as mx import mlx.nn as nn from mlx.utils import tree_map from mlx_lm.models import rope_utils +from mlx_lm.models.base import create_causal_mask from mlx_lm.models.cache import KVCache, RotatingKVCache, make_prompt_cache @@ -128,6 +129,22 @@ class TestModels(unittest.TestCase): self.assertEqual(cache.offset, 22) self.assertTrue(mx.allclose(x, k[..., -2:, :])) + def test_causal_mask_lengths(self): + mx.random.seed(8) + B, N_q, T_q, N_kv, T_kv, D = (4, 8, 3, 2, 3, 2) + lengths = mx.array([1, 2, 3, 1]) + q = mx.random.uniform(shape=(B, N_q, T_q, D)) + k = mx.random.uniform(shape=(B, N_kv, T_kv, D)) + v = k + mask = create_causal_mask(T_q, 0, lengths=lengths) + + out1 = mx.fast.scaled_dot_product_attention(q, k, v, scale=1.0, mask=mask) + q[1, :, 2:] = mx.ones_like(q[1, :, 2:]) + k[1, :, 2:] = mx.ones_like(k[1, :, 2:]) + v[1, :, 2:] = mx.ones_like(v[1, :, 2:]) + out2 = mx.fast.scaled_dot_product_attention(q, k, v, scale=1.0, mask=mask) + self.assertTrue(mx.allclose(out1[1, :, :2], out2[1, :, :2])) + def test_rope(self): rope = rope_utils.initialize_rope(32, base=100, traditional=False) self.assertTrue(isinstance(rope, nn.RoPE)) @@ -162,10 +179,16 @@ class TestModels(unittest.TestCase): self.assertEqual(outputs.dtype, t) cache = make_prompt_cache(model) - outputs = model(inputs, cache) + outputs = model(inputs, cache=cache) self.assertEqual(outputs.shape, (1, 2, vocab_size)) self.assertEqual(outputs.dtype, t) + if model_type != "mamba": + mask = create_causal_mask(inputs.shape[1], 0).astype(t) + outputs = model(inputs, mask=mask) + self.assertEqual(outputs.shape, (1, 2, vocab_size)) + self.assertEqual(outputs.dtype, t) + outputs = model(mx.argmax(outputs[0, -1:, :], keepdims=True), cache=cache) self.assertEqual(outputs.shape, (1, 1, vocab_size)) self.assertEqual(outputs.dtype, t) From 3a58c361096e5be7a927e7719c5ef66bace9a8ab Mon Sep 17 00:00:00 2001 From: Ivan Fioravanti Date: Wed, 1 Jan 2025 16:25:57 +0100 Subject: [PATCH 113/188] Improvements to mlx_lm.manage (#1178) * improvements to manage. Default value is N and size added to deletion confirmation. * Fixing case for no case * nits --------- Co-authored-by: Awni Hannun --- llms/mlx_lm/manage.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/llms/mlx_lm/manage.py b/llms/mlx_lm/manage.py index bb5c3a09..9827f3dc 100644 --- a/llms/mlx_lm/manage.py +++ b/llms/mlx_lm/manage.py @@ -6,19 +6,18 @@ from transformers.commands.user import tabulate def ask_for_confirmation(message: str) -> bool: + """Ask user for confirmation with Y/N prompt. + Returns True for Y/yes, False for N/no/empty.""" y = ("y", "yes", "1") - n = ("n", "no", "0") - all_values = y + n + ("",) - full_message = f"{message} (Y/n) " + n = ("n", "no", "0", "") + full_message = f"{message} (y/n) " while True: answer = input(full_message).lower() - if answer == "": - return False if answer in y: return True if answer in n: return False - print(f"Invalid input. Must be one of {all_values}") + print(f"Invalid input. Must be one of: yes/no/y/n or empty for no") def main(): @@ -43,9 +42,7 @@ def main(): args = parser.parse_args() if args.scan: - print( - "Scanning Hugging Face cache for models with" f'pattern "{args.pattern}".' - ) + print(f'Scanning Hugging Face cache for models with pattern "{args.pattern}".') hf_cache_info = scan_cache_dir() print( tabulate( @@ -86,35 +83,41 @@ def main(): if args.pattern in repo.repo_id ] if repos: + print("\nFound the following models:") print( tabulate( rows=[ [ repo.repo_id, + repo.size_on_disk_str, # Added size information str(repo.repo_path), ] for repo in repos ], headers=[ "REPO ID", + "SIZE", # Added size header "LOCAL PATH", ], ) ) - confirmed = ask_for_confirmation(f"Confirm deletion ?") + confirmed = ask_for_confirmation( + "\nAre you sure you want to delete these models?" + ) if confirmed: for model_info in repos: + print(f"\nDeleting {model_info.repo_id}...") for revision in sorted( model_info.revisions, key=lambda revision: revision.commit_hash ): strategy = hf_cache_info.delete_revisions(revision.commit_hash) strategy.execute() - print("Model(s) deleted.") + print("\nModel(s) deleted successfully.") else: - print("Deletion is cancelled. Do nothing.") + print("\nDeletion cancelled - no changes made.") else: - print(f"No models found.") + print(f'No models found matching pattern "{args.pattern}"') if __name__ == "__main__": From c4833a2f55c4553f71b16a412a6eb6d2f1427380 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Fri, 3 Jan 2025 10:50:59 -0800 Subject: [PATCH 114/188] fix encoding with special tokens + chat template (#1189) --- llms/README.md | 4 +- llms/mlx_lm/cache_prompt.py | 20 ++---- llms/mlx_lm/chat.py | 4 +- llms/mlx_lm/evaluate.py | 28 ++++++--- llms/mlx_lm/examples/chat.py | 8 +-- llms/mlx_lm/examples/generate_response.py | 2 +- llms/mlx_lm/generate.py | 9 +-- llms/mlx_lm/lora.py | 2 + llms/mlx_lm/server.py | 6 +- llms/mlx_lm/tuner/datasets.py | 77 ++++++++++++----------- llms/mlx_lm/tuner/trainer.py | 8 +-- llms/mlx_lm/utils.py | 19 +++--- llms/tests/test_datsets.py | 5 +- 13 files changed, 95 insertions(+), 97 deletions(-) diff --git a/llms/README.md b/llms/README.md index 4fff4207..e943ed69 100644 --- a/llms/README.md +++ b/llms/README.md @@ -58,7 +58,7 @@ prompt = "Write a story about Einstein" messages = [{"role": "user", "content": prompt}] prompt = tokenizer.apply_chat_template( - messages, tokenize=False, add_generation_prompt=True + messages, add_generation_prompt=True ) text = generate(model, tokenizer, prompt=prompt, verbose=True) @@ -115,7 +115,7 @@ prompt = "Write a story about Einstein" messages = [{"role": "user", "content": prompt}] prompt = tokenizer.apply_chat_template( - messages, tokenize=False, add_generation_prompt=True + messages, add_generation_prompt=True ) for response in stream_generate(model, tokenizer, prompt, max_tokens=512): diff --git a/llms/mlx_lm/cache_prompt.py b/llms/mlx_lm/cache_prompt.py index 9d7d1603..c18f1bae 100644 --- a/llms/mlx_lm/cache_prompt.py +++ b/llms/mlx_lm/cache_prompt.py @@ -110,29 +110,17 @@ def main(): if tokenizer.chat_template is None: tokenizer.chat_template = tokenizer.default_chat_template - if not args.ignore_chat_template and ( - hasattr(tokenizer, "apply_chat_template") - and tokenizer.chat_template is not None - ): + if not args.ignore_chat_template and tokenizer.chat_template is not None: messages = [{"role": "user", "content": args.prompt}] prompt = tokenizer.apply_chat_template( - messages, tokenize=False, add_generation_prompt=True + messages, add_generation_prompt=False, continue_final_message=True ) - # Treat the prompt as a prefix assuming that the suffix will be - # provided at generation time. - test_prompt = tokenizer.apply_chat_template( - [{"role": "user", "content": ""}], - tokenize=False, - add_generation_prompt=True, - ) - n = len(test_prompt) - test_prompt.index("") - len("") - prompt = prompt[:-n] else: - prompt = args.prompt + prompt = tokenizer.encode(args.prompt) cache = make_prompt_cache(model, args.max_kv_size) - y = mx.array(tokenizer.encode(prompt)) + y = mx.array(prompt) # Process the prompt start = time.time() diff --git a/llms/mlx_lm/chat.py b/llms/mlx_lm/chat.py index 5a8245ef..e52ad10d 100644 --- a/llms/mlx_lm/chat.py +++ b/llms/mlx_lm/chat.py @@ -72,9 +72,7 @@ def main(): if query == "q": break messages = [{"role": "user", "content": query}] - prompt = tokenizer.apply_chat_template( - messages, tokenize=False, add_generation_prompt=True - ) + prompt = tokenizer.apply_chat_template(messages, add_generation_prompt=True) for response in stream_generate( model, tokenizer, diff --git a/llms/mlx_lm/evaluate.py b/llms/mlx_lm/evaluate.py index c4b15748..bf7bf4d4 100644 --- a/llms/mlx_lm/evaluate.py +++ b/llms/mlx_lm/evaluate.py @@ -1,4 +1,8 @@ -# Adapted from a PyTorch implementation by David Grangier +# Copyright © 2024 Apple Inc. + +""" +Adapted from a PyTorch implementation by David Grangier +""" import argparse import json @@ -6,7 +10,7 @@ import logging import os from importlib.metadata import version from pathlib import Path -from typing import Optional +from typing import Optional, Union import lm_eval import mlx.core as mx @@ -277,19 +281,19 @@ class MLXLM(LM): assert "until" in keys untils = [x["until"] for x in options] completions = [] + for context, until in tqdm(zip(contexts, untils), total=len(contexts)): - if ( - hasattr(self._tokenizer, "apply_chat_template") - and self._tokenizer.chat_template is not None - ): + if self._tokenizer.chat_template is not None: messages = [{"role": "user", "content": context}] context = self._tokenizer.apply_chat_template( - messages, tokenize=False, add_generation_prompt=True + messages, add_generation_prompt=True ) + else: + context = self._tokenizer.encode(context) max_tokens = min( self._max_tokens, - self._tokenizer.model_max_length - len(self._tokenizer.encode(context)), + self._tokenizer.model_max_length - len(context), ) text = "" for response in stream_generate( @@ -321,6 +325,12 @@ def main(): type=int, help="Maximum nunber of tokens to generate. Defaults to the model's max context length.", ) + parser.add_argument( + "--limit", + default=1.0, + help="Limit the number of examples per task.", + type=float, + ) parser.add_argument("--seed", type=int, default=123, help="Random seed.") args = parser.parse_args() @@ -338,10 +348,12 @@ def main(): model=lm, tasks=args.tasks, num_fewshot=args.num_shots, + limit=args.limit, random_seed=args.seed, numpy_random_seed=args.seed, torch_random_seed=args.seed, fewshot_random_seed=args.seed, + apply_chat_template=True, ) model_name = args.model.replace("/", "_") diff --git a/llms/mlx_lm/examples/chat.py b/llms/mlx_lm/examples/chat.py index c7512b3c..4a7020f1 100644 --- a/llms/mlx_lm/examples/chat.py +++ b/llms/mlx_lm/examples/chat.py @@ -15,9 +15,7 @@ prompt_cache = make_prompt_cache(model) # User turn prompt = "Hi my name is ." messages = [{"role": "user", "content": prompt}] -prompt = tokenizer.apply_chat_template( - messages, tokenize=False, add_generation_prompt=True -) +prompt = tokenizer.apply_chat_template(messages, add_generation_prompt=True) # Assistant response response = generate( @@ -32,9 +30,7 @@ response = generate( # User turn prompt = "What's my name?" messages = [{"role": "user", "content": prompt}] -prompt = tokenizer.apply_chat_template( - messages, tokenize=False, add_generation_prompt=True -) +prompt = tokenizer.apply_chat_template(messages, add_generation_prompt=True) # Assistant response response = generate( diff --git a/llms/mlx_lm/examples/generate_response.py b/llms/mlx_lm/examples/generate_response.py index e6535b47..41eaf1da 100644 --- a/llms/mlx_lm/examples/generate_response.py +++ b/llms/mlx_lm/examples/generate_response.py @@ -14,7 +14,7 @@ conversation = [{"role": "user", "content": prompt}] # Transform the prompt into the chat template prompt = tokenizer.apply_chat_template( - conversation=conversation, tokenize=False, add_generation_prompt=True + conversation=conversation, add_generation_prompt=True ) # Specify the maximum number of tokens diff --git a/llms/mlx_lm/generate.py b/llms/mlx_lm/generate.py index afb1394e..1ea66384 100644 --- a/llms/mlx_lm/generate.py +++ b/llms/mlx_lm/generate.py @@ -190,10 +190,7 @@ def main(): prompt = args.prompt.replace("\\n", "\n").replace("\\t", "\t") prompt = sys.stdin.read() if prompt == "-" else prompt - if not args.ignore_chat_template and ( - hasattr(tokenizer, "apply_chat_template") - and tokenizer.chat_template is not None - ): + if not args.ignore_chat_template and tokenizer.chat_template is not None: if args.system_prompt is not None: messages = [{"role": "system", "content": args.system_prompt}] else: @@ -214,6 +211,10 @@ def main(): ) prompt = prompt[test_prompt.index("") :] + prompt = tokenizer.encode(prompt, add_special_tokens=False) + else: + prompt = tokenizer.encode(prompt) + sampler = make_sampler(args.temp, args.top_p, args.min_p, args.min_tokens_to_keep) response = generate( model, diff --git a/llms/mlx_lm/lora.py b/llms/mlx_lm/lora.py index c96e75a7..6fb86917 100644 --- a/llms/mlx_lm/lora.py +++ b/llms/mlx_lm/lora.py @@ -2,6 +2,7 @@ import argparse import math +import os import re import types from pathlib import Path @@ -271,6 +272,7 @@ def run(args, training_callback: TrainingCallback = None): def main(): + os.environ["TOKENIZERS_PARALLELISM"] = "true" parser = build_parser() args = parser.parse_args() config = args.config diff --git a/llms/mlx_lm/server.py b/llms/mlx_lm/server.py index c12513ff..4523e3ae 100644 --- a/llms/mlx_lm/server.py +++ b/llms/mlx_lm/server.py @@ -590,14 +590,10 @@ class APIHandler(BaseHTTPRequestHandler): # Determine response type self.request_id = f"chatcmpl-{uuid.uuid4()}" self.object_type = "chat.completion.chunk" if self.stream else "chat.completion" - if ( - hasattr(self.tokenizer, "apply_chat_template") - and self.tokenizer.chat_template - ): + if self.tokenizer.chat_template: prompt = self.tokenizer.apply_chat_template( body["messages"], body.get("tools", None), - tokenize=True, add_generation_prompt=True, ) else: diff --git a/llms/mlx_lm/tuner/datasets.py b/llms/mlx_lm/tuner/datasets.py index 20b32eff..fa848f47 100644 --- a/llms/mlx_lm/tuner/datasets.py +++ b/llms/mlx_lm/tuner/datasets.py @@ -10,41 +10,47 @@ class Dataset: Light-weight wrapper to hold a dataset. """ - def __init__(self, data: List[Dict[str, str]], text_key: str = "text"): - self._text_key = text_key - self._data = data + def __init__( + self, + data: List[Dict[str, str]], + tokenizer: PreTrainedTokenizer, + text_key: str = "text", + ): + self._data = [tokenizer.encode(d[text_key]) for d in data] + for d in self._data: + if d[-1] != tokenizer.eos_token_id: + d.append(tokenizer.eos_token_id) def __getitem__(self, idx: int): - return self._data[idx][self._text_key] + return self._data[idx] def __len__(self): - if self._data is None: - return 0 return len(self._data) -class ChatDataset(Dataset): +class ChatDataset: """ A dataset for chat data in the format of {"messages": [...]} https://platform.openai.com/docs/guides/fine-tuning/example-format """ def __init__(self, data: List[Dict[str, str]], tokenizer: PreTrainedTokenizer): - super().__init__(data) - self._tokenizer = tokenizer + self._data = [ + tokenizer.apply_chat_template( + d["messages"], + tools=d.get("tools", None), + ) + for d in data + ] def __getitem__(self, idx: int): - messages = self._data[idx]["messages"] - text = self._tokenizer.apply_chat_template( - messages, - tools=self._data[idx].get("tools", None), - tokenize=False, - add_generation_prompt=True, - ) - return text + return self._data[idx] + + def __len__(self): + return len(self._data) -class CompletionsDataset(Dataset): +class CompletionsDataset: """ A dataset for prompt-completion data in the format of {"prompt": ..., "completion": ...} or using user-provided keys for prompt and completion values @@ -58,25 +64,24 @@ class CompletionsDataset(Dataset): prompt_key: str = "prompt", completion_key: str = "completion", ): - super().__init__(data) - self._tokenizer = tokenizer - self._prompt_key = prompt_key - self._completion_key = completion_key + self._data = [ + tokenizer.apply_chat_template( + [ + {"role": "user", "content": d[prompt_key]}, + {"role": "assistant", "content": d[completion_key]}, + ], + ) + for d in data + ] def __getitem__(self, idx: int): - data = self._data[idx] - text = self._tokenizer.apply_chat_template( - [ - {"role": "user", "content": data[self._prompt_key]}, - {"role": "assistant", "content": data[self._completion_key]}, - ], - tokenize=False, - add_generation_prompt=True, - ) - return text + return self._data[idx] + + def __len__(self): + return len(self._data) -def create_dataset(data, tokenizer: PreTrainedTokenizer = None): +def create_dataset(data, tokenizer: PreTrainedTokenizer): sample = data[0] if "messages" in sample: @@ -84,7 +89,7 @@ def create_dataset(data, tokenizer: PreTrainedTokenizer = None): elif "prompt" in sample and "completion" in sample: return CompletionsDataset(data, tokenizer) elif "text" in sample: - return Dataset(data) + return Dataset(data, tokenizer) else: raise ValueError( "Unsupported data format, check the supported formats here:\n" @@ -143,7 +148,7 @@ def load_custom_hf_dataset(args, tokenizer: PreTrainedTokenizer): if prompt_feature and completion_feature: return CompletionsDataset(ds, tokenizer, prompt_feature, completion_feature) elif text_feature: - return Dataset(train_ds, text_key=text_feature) + return Dataset(train_ds, tokenizer, text_key=text_feature) else: raise ValueError( "Specify either a prompt and completion feature or a text " @@ -166,7 +171,7 @@ def load_custom_hf_dataset(args, tokenizer: PreTrainedTokenizer): def load_dataset(args, tokenizer: PreTrainedTokenizer): - if getattr(args, "hf_dataset", None) is not None: + if getattr(args, "hf_dataset", False): train, valid, test = load_custom_hf_dataset(args, tokenizer) else: data_path = Path(args.data) diff --git a/llms/mlx_lm/tuner/trainer.py b/llms/mlx_lm/tuner/trainer.py index 21b1af18..a76b8336 100644 --- a/llms/mlx_lm/tuner/trainer.py +++ b/llms/mlx_lm/tuner/trainer.py @@ -100,14 +100,8 @@ def iterate_batches(dataset, tokenizer, batch_size, max_seq_length, train=False) while True: indices = np.random.permutation(len(batch_idx)) for i in indices: - # Encode batch - batch = [tokenizer.encode(dataset[j]) for j in batch_idx[i]] - for b in batch: - if b[-1] != tokenizer.eos_token_id: - b.append(tokenizer.eos_token_id) - + batch = [dataset[j] for j in batch_idx[i]] lengths = [len(x) for x in batch] - if max(lengths) > max_seq_length: print( f"[WARNING] Some sequences are longer than {max_seq_length} tokens. " diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index 4d69115e..0c35d07f 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -353,9 +353,13 @@ def stream_generate( tokenizer = TokenizerWrapper(tokenizer) if not isinstance(prompt, mx.array): - prompt = mx.array( - prompt if isinstance(prompt, list) else tokenizer.encode(prompt) - ) + if isinstance(prompt, str): + # Try to infer if special tokens are needed + add_special_tokens = tokenizer.bos_token is None or not prompt.startswith( + tokenizer.bos_token + ) + prompt = tokenizer.encode(prompt, add_special_tokens=add_special_tokens) + prompt = mx.array(prompt) detokenizer = tokenizer.detokenizer @@ -401,7 +405,7 @@ def stream_generate( def generate( model: nn.Module, tokenizer: Union[PreTrainedTokenizer, TokenizerWrapper], - prompt: str, + prompt: Union[str, List[int]], verbose: bool = False, formatter: Optional[Callable] = None, **kwargs, @@ -412,7 +416,7 @@ def generate( Args: model (nn.Module): The language model. tokenizer (PreTrainedTokenizer): The tokenizer. - prompt (str): The string prompt. + prompt (Union[str, List[int]]): The input prompt string or integer tokens. verbose (bool): If ``True``, print tokens and timing information. Default: ``False``. kwargs: The remaining options get passed to :func:`stream_generate`. @@ -425,7 +429,6 @@ def generate( ) if verbose: print("=" * 10) - print("Prompt:", prompt) text = "" for response in stream_generate(model, tokenizer, prompt, **kwargs): @@ -654,10 +657,10 @@ def upload_to_hub(path: str, upload_repo: str, hf_path: str): prompt="hello" - if hasattr(tokenizer, "apply_chat_template") and tokenizer.chat_template is not None: + if tokenizer.chat_template is not None: messages = [{{"role": "user", "content": prompt}}] prompt = tokenizer.apply_chat_template( - messages, tokenize=False, add_generation_prompt=True + messages, add_generation_prompt=True ) response = generate(model, tokenizer, prompt=prompt, verbose=True) diff --git a/llms/tests/test_datsets.py b/llms/tests/test_datsets.py index 240bfb4a..dd86d277 100644 --- a/llms/tests/test_datsets.py +++ b/llms/tests/test_datsets.py @@ -36,7 +36,8 @@ class TestDatasets(unittest.TestCase): data = {"text": "This is an example for the model."} self.save_data(4 * [data]) args = types.SimpleNamespace(train=True, test=False, data=self.test_dir) - train, valid, test = datasets.load_dataset(args, None) + tokenizer = AutoTokenizer.from_pretrained(HF_MODEL_PATH) + train, valid, test = datasets.load_dataset(args, tokenizer) self.assertEqual(len(train), 4) self.assertEqual(len(valid), 4) self.assertEqual(len(test), 0) @@ -82,6 +83,8 @@ class TestDatasets(unittest.TestCase): "name": "billsum", "prompt_feature": "text", "completion_feature": "summary", + "train_split": "train[:2%]", + "valid_split": "train[-2%:]", }, test=False, train=True, From 25ec2d8c4496be68acf7e0c9ea1ae4269e1a2a19 Mon Sep 17 00:00:00 2001 From: Angelos Katharopoulos Date: Sun, 5 Jan 2025 22:26:05 -0800 Subject: [PATCH 115/188] Change the eos-token argument for mlx_lm.generate (#1176) --- llms/mlx_lm/generate.py | 9 +++++---- llms/mlx_lm/tokenizer_utils.py | 12 ++++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/llms/mlx_lm/generate.py b/llms/mlx_lm/generate.py index 1ea66384..3301edae 100644 --- a/llms/mlx_lm/generate.py +++ b/llms/mlx_lm/generate.py @@ -43,10 +43,11 @@ def setup_arg_parser(): help="Optional path for the trained adapter weights and config.", ) parser.add_argument( - "--eos-token", + "--extra-eos-token", type=str, default=None, - help="End of sequence token for tokenizer", + nargs="+", + help="Add tokens in the list of eos tokens that stop generation.", ) parser.add_argument( "--system-prompt", @@ -161,8 +162,6 @@ def main(): {} if not using_cache else json.loads(metadata["tokenizer_config"]) ) tokenizer_config["trust_remote_code"] = True - if args.eos_token is not None: - tokenizer_config["eos_token"] = args.eos_token model_path = args.model if using_cache: @@ -181,6 +180,8 @@ def main(): adapter_path=args.adapter_path, tokenizer_config=tokenizer_config, ) + for eos_token in args.extra_eos_token: + tokenizer.add_eos_token(eos_token) if args.use_default_chat_template: if tokenizer.chat_template is None: diff --git a/llms/mlx_lm/tokenizer_utils.py b/llms/mlx_lm/tokenizer_utils.py index ca3d6c06..1b5bdd77 100644 --- a/llms/mlx_lm/tokenizer_utils.py +++ b/llms/mlx_lm/tokenizer_utils.py @@ -266,6 +266,18 @@ class TokenizerWrapper: else {tokenizer.eos_token_id} ) + def add_eos_token(self, token: str): + token_id = None + try: + token_id = int(token) + except ValueError: + token_id = self._tokenizer.convert_tokens_to_ids(token) + + if token_id is None: + raise ValueError(f"'{token}' is not a token for this tokenizer") + + self._eos_token_ids.add(token_id) + def __getattr__(self, attr): if attr == "detokenizer": return self._detokenizer From f2619f507c7dcde70410cc2cbb1d4715476d79ee Mon Sep 17 00:00:00 2001 From: Chime Ogbuji Date: Mon, 6 Jan 2025 10:58:43 -0500 Subject: [PATCH 116/188] Add support for fewshot and apply chat template lm_eval functionality (#1180) * Add support for multiturn fewshot examples and chat templates Added two new arguments to the evaluation script: `--fewshot-as-multiturn` and `--apply-chat-template` which correspond to lm_eval options of similar names and are very often used to ensure apples-to-apples comparisons of lm_evaluation results * Add HF overrides for methods needed by added options * don't add duplicate bos --------- Co-authored-by: Awni Hannun --- .circleci/config.yml | 2 +- llms/mlx_lm/evaluate.py | 59 +++++++++++++++++++++++++++++------------ llms/setup.py | 4 +-- 3 files changed, 45 insertions(+), 20 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index cecd2d57..8367281e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -32,7 +32,7 @@ jobs: pip install --upgrade pip pip install unittest-xml-reporting cd llms/ - pip install -e ".[testing]" + pip install -e ".[test]" - run: name: Run Python tests command: | diff --git a/llms/mlx_lm/evaluate.py b/llms/mlx_lm/evaluate.py index bf7bf4d4..ca5e83bb 100644 --- a/llms/mlx_lm/evaluate.py +++ b/llms/mlx_lm/evaluate.py @@ -77,15 +77,19 @@ class MLXLM(LM): path_or_hf_repo: str, batch_size: int = 16, max_tokens: Optional[int] = None, + use_chat_template: Optional[bool] = None, ) -> None: super().__init__() self._batch_size = batch_size - self._model, self._tokenizer = load(path_or_hf_repo) - self._max_tokens = max_tokens or self._tokenizer.model_max_length + self._model, self.tokenizer = load(path_or_hf_repo) + self._max_tokens = max_tokens or self.tokenizer.model_max_length + self.use_chat_template = use_chat_template or ( + self.tokenizer.chat_template is not None + ) def _score_fn(self, inputs, tokenize=True, step_size=32): if tokenize: - inputs = self._tokenizer.encode(inputs) + inputs = self._tokenize(inputs) inputs = _pad_inputs(inputs, self._max_tokens, truncate=False) inputs = mx.array(inputs) inputs, targets = inputs[..., :-1], inputs[..., 1:] @@ -149,7 +153,12 @@ class MLXLM(LM): return results def _tokenize(self, texts): - return [tuple(self._tokenizer.encode(t)) for t in texts] + return [ + tuple( + self.tokenizer.encode(t, add_special_tokens=not self.use_chat_template) + ) + for t in texts + ] def loglikelihood(self, requests) -> list[tuple[float, bool]]: """Compute log-likelihood of generating a continuation from a context. @@ -221,6 +230,9 @@ class MLXLM(LM): ) return [(r[0], r[1] == r[2]) for r in results] + tokenizer_name = lm_eval.models.huggingface.HFLM.tokenizer_name + apply_chat_template = lm_eval.models.huggingface.HFLM.apply_chat_template + def loglikelihood_rolling(self, requests) -> list[float]: """Compute full log-likelihood of a string, with no truncation, for perplexity computation - We will use the full max context length of the model. @@ -283,21 +295,14 @@ class MLXLM(LM): completions = [] for context, until in tqdm(zip(contexts, untils), total=len(contexts)): - if self._tokenizer.chat_template is not None: - messages = [{"role": "user", "content": context}] - context = self._tokenizer.apply_chat_template( - messages, add_generation_prompt=True - ) - else: - context = self._tokenizer.encode(context) - + context = self._tokenize(context) max_tokens = min( self._max_tokens, - self._tokenizer.model_max_length - len(context), + self.tokenizer.model_max_length - len(context), ) text = "" for response in stream_generate( - self._model, self._tokenizer, prompt=context, max_tokens=max_tokens + self._model, self.tokenizer, prompt=context, max_tokens=max_tokens ): text += response.text if any(u in text for u in until): @@ -332,6 +337,21 @@ def main(): type=float, ) parser.add_argument("--seed", type=int, default=123, help="Random seed.") + parser.add_argument( + "--fewshot-as-multiturn", + action="store_true", + help="Whether to provide the fewshot examples as a multiturn " + "conversation or a single user turn.", + default=False, + ) + parser.add_argument( + "--apply-chat-template", + action=argparse.BooleanOptionalAction, + help="Specifies whether to apply a chat template to the prompt. If " + "the model has a chat template, this defaults to `True`, " + "otherwise `False`.", + default=None, + ) args = parser.parse_args() output_dir = Path(args.output_dir) @@ -342,18 +362,23 @@ def main(): mx.random.seed(args.seed) - lm = MLXLM(args.model, batch_size=args.batch_size, max_tokens=args.max_tokens) - + lm = MLXLM( + args.model, + batch_size=args.batch_size, + max_tokens=args.max_tokens, + use_chat_template=args.apply_chat_template, + ) results = lm_eval.simple_evaluate( model=lm, tasks=args.tasks, + fewshot_as_multiturn=args.fewshot_as_multiturn, + apply_chat_template=lm.use_chat_template, num_fewshot=args.num_shots, limit=args.limit, random_seed=args.seed, numpy_random_seed=args.seed, torch_random_seed=args.seed, fewshot_random_seed=args.seed, - apply_chat_template=True, ) model_name = args.model.replace("/", "_") diff --git a/llms/setup.py b/llms/setup.py index b88dcd33..e6fddbae 100644 --- a/llms/setup.py +++ b/llms/setup.py @@ -27,8 +27,8 @@ setup( packages=["mlx_lm", "mlx_lm.models", "mlx_lm.tuner"], python_requires=">=3.8", extras_require={ - "testing": ["datasets"], - "evaluation": ["lm-eval"], + "test": ["datasets"], + "evaluate": ["lm-eval", "tqdm"], }, entry_points={ "console_scripts": [ From 9183fe8b6d6b5e86cac0f47b54675f272c9f3591 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Mon, 6 Jan 2025 10:12:07 -0800 Subject: [PATCH 117/188] fix (#1192) --- llms/mlx_lm/generate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llms/mlx_lm/generate.py b/llms/mlx_lm/generate.py index 3301edae..26481d6b 100644 --- a/llms/mlx_lm/generate.py +++ b/llms/mlx_lm/generate.py @@ -45,7 +45,7 @@ def setup_arg_parser(): parser.add_argument( "--extra-eos-token", type=str, - default=None, + default=(), nargs="+", help="Add tokens in the list of eos tokens that stop generation.", ) From b8f0cacfa8dd08aaca7025351a7afddd481ca490 Mon Sep 17 00:00:00 2001 From: Pedro Cuenca Date: Tue, 7 Jan 2025 18:18:31 +0100 Subject: [PATCH 118/188] Use upload_large_folder (#1193) --- llms/mlx_lm/utils.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index 0c35d07f..ad79349e 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -673,12 +673,10 @@ def upload_to_hub(path: str, upload_repo: str, hf_path: str): api = HfApi() api.create_repo(repo_id=upload_repo, exist_ok=True) - api.upload_folder( + api.upload_large_folder( folder_path=path, repo_id=upload_repo, repo_type="model", - multi_commits=True, - multi_commits_verbose=True, ) print(f"Upload successful, go to https://huggingface.co/{upload_repo} for details.") From 40b88eff488d82b8d8739de6d60f59c1f0789a14 Mon Sep 17 00:00:00 2001 From: Jarrett <2613089+jjaareet@users.noreply.github.com> Date: Thu, 9 Jan 2025 12:33:54 -0700 Subject: [PATCH 119/188] fix(lora): config yaml & arg default merge bug (#1196) --- llms/mlx_lm/lora.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/llms/mlx_lm/lora.py b/llms/mlx_lm/lora.py index 6fb86917..4d050bd5 100644 --- a/llms/mlx_lm/lora.py +++ b/llms/mlx_lm/lora.py @@ -58,6 +58,8 @@ CONFIG_DEFAULTS = { "test": False, "test_batches": 500, "max_seq_length": 2048, + "config": None, + "grad_checkpoint": False, "lr_schedule": None, "lora_parameters": {"rank": 8, "alpha": 16, "dropout": 0.0, "scale": 10.0}, } @@ -67,6 +69,7 @@ def build_parser(): parser = argparse.ArgumentParser(description="LoRA or QLoRA finetuning.") parser.add_argument( "--model", + type=str, help="The path to the local model directory or Hugging Face repo.", ) @@ -75,7 +78,6 @@ def build_parser(): "--train", action="store_true", help="Do training", - default=None, ) parser.add_argument( "--data", @@ -89,7 +91,6 @@ def build_parser(): "--fine-tune-type", type=str, choices=["lora", "dora", "full"], - default="lora", help="Type of fine-tuning to perform: lora, dora, or full.", ) parser.add_argument( @@ -134,7 +135,6 @@ def build_parser(): "--test", action="store_true", help="Evaluate on the test set after training", - default=None, ) parser.add_argument( "--test-batches", @@ -149,16 +149,15 @@ def build_parser(): parser.add_argument( "-c", "--config", - default=None, + type=str, help="A YAML configuration file with the training options", ) parser.add_argument( "--grad-checkpoint", action="store_true", help="Use gradient checkpointing to reduce memory use.", - default=None, ) - parser.add_argument("--seed", type=int, default=None, help="The PRNG seed") + parser.add_argument("--seed", type=int, help="The PRNG seed") return parser From 5cae0a60e6acb3599483a9304aebbc89e0bff1c4 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Thu, 9 Jan 2025 15:55:53 -0800 Subject: [PATCH 120/188] deepseek v3 model with pipeline parallelism (#1191) * deepseekv3 * use upload_large_file instead of deprecated multi comit * add pipeline generation and example * comment * get fp16 working * use mlx==0.22 --- llms/mlx_lm/_version.py | 2 +- llms/mlx_lm/examples/pipeline_generate.py | 75 ++++ llms/mlx_lm/models/deepseek_v3.py | 460 ++++++++++++++++++++++ llms/mlx_lm/requirements.txt | 2 +- llms/mlx_lm/utils.py | 4 +- llms/tests/test_models.py | 37 ++ llms/tests/test_utils_load_model.py | 2 +- 7 files changed, 577 insertions(+), 5 deletions(-) create mode 100644 llms/mlx_lm/examples/pipeline_generate.py create mode 100644 llms/mlx_lm/models/deepseek_v3.py diff --git a/llms/mlx_lm/_version.py b/llms/mlx_lm/_version.py index 3af2d5fd..b2f98e6f 100644 --- a/llms/mlx_lm/_version.py +++ b/llms/mlx_lm/_version.py @@ -1,3 +1,3 @@ # Copyright © 2023-2024 Apple Inc. -__version__ = "0.20.4" +__version__ = "0.21.0" diff --git a/llms/mlx_lm/examples/pipeline_generate.py b/llms/mlx_lm/examples/pipeline_generate.py new file mode 100644 index 00000000..b98e757b --- /dev/null +++ b/llms/mlx_lm/examples/pipeline_generate.py @@ -0,0 +1,75 @@ +# Copyright © 2024 Apple Inc. + +""" +Run with: + +``` +/path/to/mpirun \ + -np 2 \ + --hostfile /path/to/hosts.txt \ + python /path/to/pipeline_generate.py --prompt "hello world" +``` + +Make sure you can run MLX over MPI on two hosts. For more information see the +documentation: + +https://ml-explore.github.io/mlx/build/html/usage/distributed.html). +""" + +import argparse + +import mlx.core as mx +from mlx_lm import load, stream_generate + +parser = argparse.ArgumentParser(description="LLM pipelined inference example") +parser.add_argument( + "--prompt", + "-p", + default="Write a quicksort in C++.", + help="Message to be processed by the model ('-' reads from stdin)", +) +parser.add_argument( + "--max-tokens", + "-m", + type=int, + default=256, + help="Maximum number of tokens to generate", +) +args = parser.parse_args() + +model_repo = "mlx-community/DeepSeek-V3-3bit" + +model, tokenizer = load(model_repo, lazy=True) + +messages = [{"role": "user", "content": args.prompt}] +prompt = tokenizer.apply_chat_template(messages, add_generation_prompt=True) + +group = mx.distributed.init() +rank = group.rank() +model.model.pipeline(group) +mx.eval(model.parameters()) + +# Synchronize processes before generation to avoid timeout if downloading +# model for the first time. +mx.eval(mx.distributed.all_sum(mx.array(1.0), stream=mx.cpu)) + + +def rprint(*args, **kwargs): + if rank == 0: + print(*args, **kwargs) + + +for response in stream_generate(model, tokenizer, prompt, max_tokens=args.max_tokens): + rprint(response.text, end="", flush=True) + +rprint() +rprint("=" * 10) +rprint( + f"Prompt: {response.prompt_tokens} tokens, " + f"{response.prompt_tps:.3f} tokens-per-sec" +) +rprint( + f"Generation: {response.generation_tokens} tokens, " + f"{response.generation_tps:.3f} tokens-per-sec" +) +rprint(f"Peak memory: {response.peak_memory:.3f} GB") diff --git a/llms/mlx_lm/models/deepseek_v3.py b/llms/mlx_lm/models/deepseek_v3.py new file mode 100644 index 00000000..f95949f9 --- /dev/null +++ b/llms/mlx_lm/models/deepseek_v3.py @@ -0,0 +1,460 @@ +# Copyright © 2024 Apple Inc. + +import math +from dataclasses import dataclass +from typing import Any, Dict, Optional, Tuple + +import mlx.core as mx +import mlx.nn as nn + +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention +from .switch_layers import SwitchGLU + + +@dataclass +class ModelArgs(BaseModelArgs): + model_type: str = "deepseek_v3" + vocab_size: int = 102400 + hidden_size: int = 4096 + intermediate_size: int = 11008 + moe_intermediate_size: int = 1407 + num_hidden_layers: int = 30 + num_attention_heads: int = 32 + num_key_value_heads: int = 32 + n_shared_experts: Optional[int] = None + n_routed_experts: Optional[int] = None + routed_scaling_factor: float = 1.0 + kv_lora_rank: int = 512 + q_lora_rank: int = 1536 + qk_rope_head_dim: int = 64 + v_head_dim: int = 128 + qk_nope_head_dim: int = 128 + topk_method: str = "noaux_tc" + scoring_func: str = "sigmoid" + norm_topk_prob: bool = True + n_group: Optional[int] = None + topk_group: Optional[int] = None + num_experts_per_tok: Optional[int] = None + moe_layer_freq: int = 1 + first_k_dense_replace: int = 0 + max_position_embeddings: int = 2048 + rms_norm_eps: float = 1e-6 + rope_theta: float = 10000.0 + rope_scaling: Dict = None + attention_bias: bool = False + + +def yarn_find_correction_dim( + num_rotations, dim, base=10000, max_position_embeddings=2048 +): + return (dim * math.log(max_position_embeddings / (num_rotations * 2 * math.pi))) / ( + 2 * math.log(base) + ) + + +def yarn_find_correction_range( + low_rot, high_rot, dim, base=10000, max_position_embeddings=2048 +): + low = math.floor( + yarn_find_correction_dim(low_rot, dim, base, max_position_embeddings) + ) + high = math.ceil( + yarn_find_correction_dim(high_rot, dim, base, max_position_embeddings) + ) + return max(low, 0), min(high, dim - 1) + + +def yarn_get_mscale(scale=1, mscale=1): + if scale <= 1: + return 1.0 + return 0.1 * mscale * math.log(scale) + 1.0 + + +def yarn_linear_ramp_mask(min_val, max_val, dim): + if min_val == max_val: + max_val += 0.001 # Prevent singularity + + linear_func = (mx.arange(dim, dtype=mx.float32) - min_val) / (max_val - min_val) + return mx.clip(linear_func, 0, 1) + + +class DeepseekV3YarnRotaryEmbedding(nn.Module): + def __init__( + self, + dim, + max_position_embeddings=2048, + base=10000, + scaling_factor=1.0, + original_max_position_embeddings=4096, + beta_fast=32, + beta_slow=1, + mscale=1, + mscale_all_dim=0, + ): + super().__init__() + self.mscale = yarn_get_mscale(scaling_factor, mscale) / yarn_get_mscale( + scaling_factor, mscale_all_dim + ) + freq_extra = base ** (mx.arange(0, dim, 2, dtype=mx.float32) / dim) + freq_inter = scaling_factor * base ** ( + mx.arange(0, dim, 2, dtype=mx.float32) / dim + ) + low, high = yarn_find_correction_range( + beta_fast, + beta_slow, + dim, + base, + original_max_position_embeddings, + ) + freq_mask = 1.0 - yarn_linear_ramp_mask(low, high, dim // 2) + self._freqs = (freq_inter * freq_extra) / ( + freq_inter * freq_mask + freq_extra * (1 - freq_mask) + ) + + def __call__(self, x, offset=0): + if self.mscale != 1.0: + x = self.mscale * x + return mx.fast.rope( + x, + x.shape[-1], + traditional=True, + base=None, + scale=1.0, + offset=offset, + freqs=self._freqs, + ) + + +class DeepseekV3Attention(nn.Module): + def __init__(self, config: ModelArgs): + super().__init__() + self.config = config + self.hidden_size = config.hidden_size + self.num_heads = config.num_attention_heads + self.max_position_embeddings = config.max_position_embeddings + self.rope_theta = config.rope_theta + self.q_lora_rank = config.q_lora_rank + self.qk_rope_head_dim = config.qk_rope_head_dim + self.kv_lora_rank = config.kv_lora_rank + self.v_head_dim = config.v_head_dim + self.qk_nope_head_dim = config.qk_nope_head_dim + self.q_head_dim = config.qk_nope_head_dim + config.qk_rope_head_dim + + self.scale = self.q_head_dim**-0.5 + + if self.q_lora_rank is None: + self.q_proj = nn.Linear( + self.hidden_size, self.num_heads * self.q_head_dim, bias=False + ) + else: + self.q_a_proj = nn.Linear( + self.hidden_size, self.q_lora_rank, bias=config.attention_bias + ) + self.q_a_layernorm = nn.RMSNorm(self.q_lora_rank) + self.q_b_proj = nn.Linear( + self.q_lora_rank, self.num_heads * self.q_head_dim, bias=False + ) + + self.kv_a_proj_with_mqa = nn.Linear( + self.hidden_size, + self.kv_lora_rank + self.qk_rope_head_dim, + bias=config.attention_bias, + ) + self.kv_a_layernorm = nn.RMSNorm(self.kv_lora_rank) + self.kv_b_proj = nn.Linear( + self.kv_lora_rank, + self.num_heads + * (self.q_head_dim - self.qk_rope_head_dim + self.v_head_dim), + bias=False, + ) + + self.o_proj = nn.Linear( + self.num_heads * self.v_head_dim, + self.hidden_size, + bias=config.attention_bias, + ) + + mscale_all_dim = self.config.rope_scaling.get("mscale_all_dim", 0) + scaling_factor = self.config.rope_scaling["factor"] + if mscale_all_dim: + mscale = yarn_get_mscale(scaling_factor, mscale_all_dim) + self.scale = self.scale * mscale * mscale + + rope_kwargs = { + key: self.config.rope_scaling[key] + for key in [ + "original_max_position_embeddings", + "beta_fast", + "beta_slow", + "mscale", + "mscale_all_dim", + ] + if key in self.config.rope_scaling + } + self.rope = DeepseekV3YarnRotaryEmbedding( + dim=self.qk_rope_head_dim, + max_position_embeddings=self.max_position_embeddings, + scaling_factor=scaling_factor, + base=self.rope_theta, + **rope_kwargs, + ) + + def __call__( + self, + x: mx.array, + mask: Optional[mx.array] = None, + cache: Optional[Any] = None, + ) -> mx.array: + B, L, D = x.shape + + if self.q_lora_rank is None: + q = self.q_proj(x) + else: + q = self.q_b_proj(self.q_a_layernorm(self.q_a_proj(x))) + + q = q.reshape(B, L, self.num_heads, self.q_head_dim).transpose(0, 2, 1, 3) + q_nope, q_pe = mx.split(q, [self.qk_nope_head_dim], axis=-1) + compressed_kv = self.kv_a_proj_with_mqa(x) + compressed_kv, k_pe = mx.split(compressed_kv, [self.kv_lora_rank], axis=-1) + k_pe = k_pe.reshape(B, L, 1, self.qk_rope_head_dim).transpose(0, 2, 1, 3) + kv = self.kv_b_proj(self.kv_a_layernorm(compressed_kv)) + kv = kv.reshape(B, L, self.num_heads, -1).transpose(0, 2, 1, 3) + + k_nope, values = mx.split(kv, [self.qk_nope_head_dim], axis=-1) + + if cache is not None: + q_pe = self.rope(q_pe, cache.offset) + k_pe = self.rope(k_pe, cache.offset) + k_pe = mx.repeat(k_pe, self.num_heads, axis=1) + keys, values = cache.update_and_fetch( + mx.concatenate([k_nope, k_pe], axis=-1), values + ) + else: + q_pe = self.rope(q_pe) + k_pe = self.rope(k_pe) + k_pe = mx.repeat(k_pe, self.num_heads, axis=1) + keys = mx.concatenate([k_nope, k_pe], axis=-1) + + queries = mx.concatenate([q_nope, q_pe], axis=-1) + + output = scaled_dot_product_attention( + queries, keys, values, cache=cache, scale=self.scale, mask=mask + ) + output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) + return self.o_proj(output) + + +class DeepseekV3MLP(nn.Module): + def __init__( + self, config: ModelArgs, hidden_size: int = None, intermediate_size: int = None + ): + super().__init__() + self.config = config + self.hidden_size = config.hidden_size if hidden_size is None else hidden_size + self.intermediate_size = ( + config.intermediate_size if intermediate_size is None else intermediate_size + ) + + self.gate_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False) + self.up_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False) + self.down_proj = nn.Linear(self.intermediate_size, self.hidden_size, bias=False) + + def __call__(self, x): + down_proj = self.down_proj(nn.silu(self.gate_proj(x)) * self.up_proj(x)) + return down_proj + + +class MoEGate(nn.Module): + def __init__(self, config: ModelArgs): + super().__init__() + self.config = config + self.top_k = config.num_experts_per_tok + self.norm_topk_prob = config.norm_topk_prob + self.n_routed_experts = config.n_routed_experts + self.routed_scaling_factor = config.routed_scaling_factor + self.topk_method = config.topk_method + self.n_group = config.n_group + self.topk_group = config.topk_group + self.weight = mx.zeros((self.n_routed_experts, config.hidden_size)) + self.e_score_correction_bias = mx.zeros((self.n_routed_experts,)) + + def __call__(self, x): + gates = x @ self.weight.T + + scores = mx.sigmoid(gates.astype(mx.float32)) + + assert self.topk_method == "noaux_tc", "Unsupported topk method." + bsz, seq_len = x.shape[:2] + scores = scores + self.e_score_correction_bias + scores = scores.reshape(bsz, seq_len, self.n_group, -1) + group_scores = mx.topk(scores, 2, axis=-1).sum(axis=-1) + k = self.n_group - self.topk_group + group_idx = mx.argpartition(group_scores, kth=k - 1, axis=-1)[..., :k] + batch_idx = mx.expand_dims(mx.arange(bsz), (1, 2)) + seq_idx = mx.expand_dims(mx.arange(seq_len), (0, 2)) + scores[batch_idx, seq_idx, group_idx] = 0.0 + scores = scores.reshape(bsz, seq_len, -1) + + k = self.top_k + inds = mx.argpartition(-scores, kth=k - 1, axis=-1)[..., :k] + scores = mx.take_along_axis(scores, inds, axis=-1) + if self.top_k > 1 and self.norm_topk_prob: + denominator = scores.sum(axis=-1, keepdims=True) + 1e-20 + scores = scores / denominator + scores = scores * self.routed_scaling_factor + + return inds, scores + + +class DeepseekV3MoE(nn.Module): + def __init__(self, config: ModelArgs): + super().__init__() + self.config = config + self.num_experts_per_tok = config.num_experts_per_tok + self.switch_mlp = SwitchGLU( + config.hidden_size, config.moe_intermediate_size, config.n_routed_experts + ) + + self.gate = MoEGate(config) + if config.n_shared_experts is not None: + intermediate_size = config.moe_intermediate_size * config.n_shared_experts + self.shared_experts = DeepseekV3MLP( + config=config, intermediate_size=intermediate_size + ) + + def __call__(self, x): + inds, scores = self.gate(x) + y = self.switch_mlp(x, inds) + y = (y * scores[..., None]).sum(axis=-2).astype(y.dtype) + if self.config.n_shared_experts is not None: + y = y + self.shared_experts(x) + + return y + + +class DeepseekV3DecoderLayer(nn.Module): + def __init__(self, config: ModelArgs, layer_idx: int): + super().__init__() + self.self_attn = DeepseekV3Attention(config) + self.mlp = ( + DeepseekV3MoE(config) + if ( + config.n_routed_experts is not None + and layer_idx >= config.first_k_dense_replace + and layer_idx % config.moe_layer_freq == 0 + ) + else DeepseekV3MLP(config) + ) + self.input_layernorm = nn.RMSNorm(config.hidden_size, eps=config.rms_norm_eps) + self.post_attention_layernorm = nn.RMSNorm( + config.hidden_size, eps=config.rms_norm_eps + ) + + def __call__( + self, + x: mx.array, + mask: Optional[mx.array] = None, + cache: Optional[Any] = None, + ) -> mx.array: + r = self.self_attn(self.input_layernorm(x), mask, cache) + h = x + r + r = self.mlp(self.post_attention_layernorm(h)) + out = h + r + # Protect against overflow for fp16 + if out.dtype == mx.float16: + out = mx.clip(out, a_min=None, a_max=mx.finfo(mx.float16).max - 1000) + return out + + +class DeepseekV3Model(nn.Module): + def __init__(self, config: ModelArgs): + super().__init__() + self.vocab_size = config.vocab_size + self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size) + self.layers = [ + DeepseekV3DecoderLayer(config, idx) + for idx in range(config.num_hidden_layers) + ] + self.norm = nn.RMSNorm(config.hidden_size, eps=config.rms_norm_eps) + self.pipeline_rank = 0 + self.pipeline_size = 1 + + def pipeline(self, group): + # Split layers in reverse so rank=0 gets the last layers and + # rank=pipeline_size-1 gets the first + self.pipeline_rank = group.rank() + self.pipeline_size = group.size() + layers_per_rank = ( + len(self.layers) + self.pipeline_size - 1 + ) // self.pipeline_size + start = (self.pipeline_size - self.pipeline_rank - 1) * layers_per_rank + self.layers = self.layers[start : start + layers_per_rank] + + def __call__( + self, + x: mx.array, + cache: Optional[Any] = None, + mask: Optional[mx.array] = None, + ) -> mx.array: + h = self.embed_tokens(x) + + pipeline_rank = self.pipeline_rank + pipeline_size = self.pipeline_size + if mask is None: + mask = create_attention_mask(h, cache) + + if cache is None: + cache = [None] * len(self.layers) + + # Receive from the previous process in the pipeline + if pipeline_rank < pipeline_size - 1: + h = mx.distributed.recv_like(h, (pipeline_rank + 1)) + + for layer, c in zip(self.layers, cache): + h = layer(h, mask, c) + + # Send to the next process in the pipeline + if pipeline_rank != 0: + h = mx.distributed.send(h, (pipeline_rank - 1) % pipeline_size) + + # Broadcast h while keeping it in the graph + h = mx.distributed.all_gather(h)[: h.shape[0]] + + return self.norm(h) + + +class Model(nn.Module): + def __init__(self, config: ModelArgs): + super().__init__() + self.args = config + self.model_type = config.model_type + self.model = DeepseekV3Model(config) + self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False) + + def __call__( + self, + inputs: mx.array, + cache: Optional[Any] = None, + mask: Optional[mx.array] = None, + ): + out = self.model(inputs, cache, mask) + return self.lm_head(out) + + def sanitize(self, weights): + for l in range(self.args.num_hidden_layers): + prefix = f"model.layers.{l}" + for n, m in [("w1", "gate_proj"), ("w2", "down_proj"), ("w3", "up_proj")]: + for k in ["weight", "scales", "biases"]: + if f"{prefix}.mlp.experts.0.{m}.{k}" in weights: + to_join = [ + weights.pop(f"{prefix}.mlp.experts.{e}.{m}.{k}") + for e in range(self.args.n_routed_experts) + ] + weights[f"{prefix}.mlp.switch_mlp.{m}.{k}"] = mx.stack(to_join) + + # Remove multi-token prediction layer + return {k: v for k, v in weights.items() if not k.startswith("model.layers.61")} + + @property + def layers(self): + return self.model.layers diff --git a/llms/mlx_lm/requirements.txt b/llms/mlx_lm/requirements.txt index 48012863..72e1ef89 100644 --- a/llms/mlx_lm/requirements.txt +++ b/llms/mlx_lm/requirements.txt @@ -1,4 +1,4 @@ -mlx>=0.19.2 +mlx>=0.22.0 numpy transformers[sentencepiece]>=4.39.3 protobuf diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index ad79349e..0e06b5a0 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -561,7 +561,7 @@ def load( Defaults to an empty dictionary. adapter_path (str, optional): Path to the LoRA adapters. If provided, applies LoRA layers to the model. Default: ``None``. - lazy (bool): If False eval the model parameters to make sure they are + lazy (bool): If ``False`` eval the model parameters to make sure they are loaded in memory before returning, otherwise they will be loaded when needed. Default: ``False`` Returns: @@ -655,7 +655,7 @@ def upload_to_hub(path: str, upload_repo: str, hf_path: str): model, tokenizer = load("{upload_repo}") - prompt="hello" + prompt = "hello" if tokenizer.chat_template is not None: messages = [{{"role": "user", "content": prompt}}] diff --git a/llms/tests/test_models.py b/llms/tests/test_models.py index 7b4376bb..118ec6f2 100644 --- a/llms/tests/test_models.py +++ b/llms/tests/test_models.py @@ -682,6 +682,43 @@ class TestModels(unittest.TestCase): model, args.model_type, args.vocab_size, args.num_hidden_layers ) + def test_deepseek_v3(self): + from mlx_lm.models import deepseek_v3 + + args = deepseek_v3.ModelArgs( + model_type="deepseek_v3", + vocab_size=1024, + hidden_size=128, + intermediate_size=256, + moe_intermediate_size=256, + num_hidden_layers=4, + num_attention_heads=4, + num_key_value_heads=2, + n_routed_experts=4, + n_group=2, + topk_group=1, + num_experts_per_tok=2, + n_shared_experts=1, + kv_lora_rank=4, + q_lora_rank=4, + qk_rope_head_dim=32, + v_head_dim=16, + qk_nope_head_dim=32, + rope_scaling={ + "beta_fast": 32, + "beta_slow": 1, + "factor": 40, + "mscale": 1.0, + "mscale_all_dim": 1.0, + "original_max_position_embeddings": 4096, + "type": "yarn", + }, + ) + model = deepseek_v3.Model(args) + self.model_test_runner( + model, args.model_type, args.vocab_size, args.num_hidden_layers + ) + def test_gemma2(self): from mlx_lm.models import gemma2 diff --git a/llms/tests/test_utils_load_model.py b/llms/tests/test_utils_load_model.py index 5821f9e9..8da19afb 100644 --- a/llms/tests/test_utils_load_model.py +++ b/llms/tests/test_utils_load_model.py @@ -17,7 +17,7 @@ class TestLoadModelCustomGetClasses(unittest.TestCase): self.config = args self.custom_attribute = "This is a custom model" - def load_weights(self, weights): + def load_weights(self, weights, **kwargs): self.qwenWeights = weights class CustomQwenConfig: From 93c5cfd7819cac681bd35f8c928f752d72da8334 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Fri, 10 Jan 2025 15:27:08 -0800 Subject: [PATCH 121/188] Add a speculative decoding generator (#1155) * add a speculative decoding generator * fix * fixes * optional kwarg pop --- llms/mlx_lm/generate.py | 21 +++- llms/mlx_lm/utils.py | 209 ++++++++++++++++++++++++++++++++++------ 2 files changed, 198 insertions(+), 32 deletions(-) diff --git a/llms/mlx_lm/generate.py b/llms/mlx_lm/generate.py index 26481d6b..0d286c75 100644 --- a/llms/mlx_lm/generate.py +++ b/llms/mlx_lm/generate.py @@ -131,6 +131,18 @@ def setup_arg_parser(): type=int, default=DEFAULT_QUANTIZED_KV_START, ) + parser.add_argument( + "--draft-model", + type=str, + help="A model to be used for speculative decoding.", + default=None, + ) + parser.add_argument( + "--num-draft-tokens", + type=int, + help="Number of tokens to draft when using speculative decoding.", + default=2, + ) return parser @@ -211,11 +223,16 @@ def main(): add_generation_prompt=True, ) prompt = prompt[test_prompt.index("") :] - prompt = tokenizer.encode(prompt, add_special_tokens=False) else: prompt = tokenizer.encode(prompt) + if args.draft_model is not None: + draft_model, draft_tokenizer = load(args.draft_model) + if draft_tokenizer.vocab_size != tokenizer.vocab_size: + raise ValueError("Draft model tokenizer does not match model tokenizer.") + else: + draft_model = None sampler = make_sampler(args.temp, args.top_p, args.min_p, args.min_tokens_to_keep) response = generate( model, @@ -229,6 +246,8 @@ def main(): kv_bits=args.kv_bits, kv_group_size=args.kv_group_size, quantized_kv_start=args.quantized_kv_start, + draft_model=draft_model, + num_draft_tokens=args.num_draft_tokens, ) if not args.verbose: print(response) diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index 0e06b5a0..2fc0446b 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -2,6 +2,7 @@ import contextlib import copy +import functools import glob import importlib import json @@ -207,12 +208,6 @@ def generate_step( kv_group_size: int = 64, quantized_kv_start: int = 0, prompt_progress_callback: Optional[Callable[int, int]] = None, - temp: Optional[float] = None, - repetition_penalty: Optional[float] = None, - repetition_context_size: Optional[int] = None, - top_p: Optional[float] = None, - min_p: Optional[float] = None, - min_tokens_to_keep: Optional[int] = None, ) -> Generator[Tuple[mx.array, mx.array], None, None]: """ A generator producing token ids based on the given prompt from the model. @@ -256,25 +251,17 @@ def generate_step( elif len(prompt_cache) != len(model.layers): raise ValueError("Wrong number of layers in the prompt cache.") - if temp is not None or top_p is not None or min_tokens_to_keep is not None: - print( - "[Warning] Specifying sampling arguments to ``generate_step`` is " - "deprecated. Pass in a ``sampler`` instead." - ) - if repetition_penalty is not None: - print( - "[Warning] Specifying ``repetition_penalty`` is deprecated. " - "Pass in ``logits_processors`` instead." - ) - - sampler = sampler or make_sampler( - temp or 0.0, top_p or 0.0, min_p or 0.0, min_tokens_to_keep or 1 - ) - logits_processors = logits_processors or make_logits_processors( - None, repetition_penalty, repetition_context_size or 20 - ) prompt_progress_callback = prompt_progress_callback or (lambda *_: None) + quantize_cache_fn = functools.partial( + maybe_quantize_kv_cache, + quantized_kv_start=quantized_kv_start, + kv_group_size=kv_group_size, + kv_bits=kv_bits, + ) + + sampler = sampler or (lambda x: mx.argmax(x, axis=-1)) + def _step(y): with mx.stream(generation_stream): logits = model(y[None], cache=prompt_cache) @@ -287,9 +274,7 @@ def generate_step( for processor in logits_processors: logits = processor(tokens, logits) - maybe_quantize_kv_cache( - prompt_cache, quantized_kv_start, kv_group_size, kv_bits - ) + quantize_cache_fn(prompt_cache) logprobs = logits - mx.logsumexp(logits, keepdims=True) y = sampler(logprobs) @@ -300,9 +285,7 @@ def generate_step( prompt_processed_tokens = 0 while y.size > prefill_step_size: model(y[:prefill_step_size][None], cache=prompt_cache) - maybe_quantize_kv_cache( - prompt_cache, quantized_kv_start, kv_group_size, kv_bits - ) + quantize_cache_fn(prompt_cache) mx.eval([c.state for c in prompt_cache]) prompt_progress_callback(prompt_processed_tokens, total_prompt_tokens) prompt_processed_tokens += prefill_step_size @@ -329,10 +312,162 @@ def generate_step( n += 1 +def speculative_generate_step( + prompt: mx.array, + model: nn.Module, + draft_model: nn.Module, + *, + num_draft_tokens=2, + max_tokens: int = 256, + sampler: Optional[Callable[mx.array, mx.array]] = None, + logits_processors: Optional[List[Callable[[mx.array, mx.array], mx.array]]] = None, + prompt_cache: Optional[Any] = None, + prefill_step_size: int = 512, + kv_bits: Optional[int] = None, + kv_group_size: int = 64, + quantized_kv_start: int = 0, +) -> Generator[Tuple[mx.array, mx.array], None, None]: + """ + A generator producing token ids based on the given prompt from the model. + + Args: + prompt (mx.array): The input prompt. + model (nn.Module): The model to use for generation. + draft_model (nn.Module): The draft model for speculative decoding. + num_draft_tokens (int, optional): The number of draft tokens for + speculative decoding. Default: ``2``. + max_tokens (int): The maximum number of tokens. Use``-1`` for an infinite + generator. Default: ``256``. + sampler (Callable[mx.array, mx.array], optional): A sampler for sampling a + token from a vector of log probabilities. Default: ``None``. + logits_processors (List[Callable[[mx.array, mx.array], mx.array]], optional): + A list of functions that take tokens and logits and return the processed + logits. Default: ``None``. + prompt_cache (List[Any], optional): A pre-computed prompt cache. Note, if + provided, the cache will be updated in place. The cache must be trimmable. + prefill_step_size (int): Step size for processing the prompt. + kv_bits (int, optional): Number of bits to use for KV cache quantization. + None implies no cache quantization. Default: ``None``. + kv_group_size (int): Group size for KV cache quantization. Default: ``64``. + quantized_kv_start (int): Step to begin using a quantized KV cache. + when ``kv_bits`` is non-None. Default: ``0``. + + Yields: + Tuple[mx.array, mx.array]: One token and a vector of log probabilities. + """ + + y = prompt + tokens = None + + # Create the KV cache for generation + if prompt_cache is None: + model_cache = cache.make_prompt_cache(model) + draft_cache = cache.make_prompt_cache(draft_model) + elif len(prompt_cache) != (len(model.layers) + len(draft_model.layers)): + raise ValueError("Wrong number of layers in the prompt cache.") + else: + model_cache = prompt_cache[: len(model.layers)] + draft_cache = prompt_cache[len(model.layers) :] + + sampler = sampler or (lambda x: mx.argmax(x, axis=-1)) + + quantize_cache_fn = functools.partial( + maybe_quantize_kv_cache, + quantized_kv_start=quantized_kv_start, + kv_group_size=kv_group_size, + kv_bits=kv_bits, + ) + + def _step(model, cache, y, n_predict=1): + with mx.stream(generation_stream): + logits = model(y[None], cache=cache) + logits = logits[:, -n_predict:, :] + + quantize_cache_fn(cache) + + logprobs = logits - mx.logsumexp(logits, keepdims=True) + y = sampler(logprobs).squeeze(0) + return y, logprobs.squeeze(0) + + def _prefill(model, cache, y): + while y.size > prefill_step_size: + model(y[:prefill_step_size][None], cache=cache) + quantize_cache_fn(cache) + mx.eval([c.state for c in cache]) + y = y[prefill_step_size:] + mx.metal.clear_cache() + return y + + def _rewind_cache(num_draft, num_accept): + cache.trim_prompt_cache(model_cache, num_draft - num_accept) + cache.trim_prompt_cache(draft_cache, max(num_draft - num_accept - 1, 0)) + + def _draft_generate(y, num_draft): + if num_draft == 0: + return mx.array([], mx.uint32) + ys = [] + for _ in range(num_draft): + y, _ = _step(draft_model, draft_cache, y) + mx.async_eval(y) + ys.append(y) + return mx.concatenate(ys) + + with mx.stream(generation_stream): + draft_y = _prefill(draft_model, draft_cache, y) + y = _prefill(model, model_cache, y) + + ntoks = 0 + # Set these so the finally block doesn't raise + num_draft = 0 + n = 0 + try: + while True: + num_draft = min(max_tokens - ntoks, num_draft_tokens) + draft_tokens = _draft_generate(draft_y, num_draft) + y = mx.concatenate([y, draft_tokens]) + + tokens, logprobs = _step(model, model_cache, y, num_draft + 1) + mx.eval(tokens, draft_tokens) + draft_tokens = draft_tokens.tolist() + tokens = tokens.tolist() + n = 0 + while n < num_draft: + tn, dtn, lpn = tokens[n], draft_tokens[n], logprobs[n] + if tn != dtn: + break + n += 1 + ntoks += 1 + yield tn, lpn + if ntoks == max_tokens: + break + if ntoks < max_tokens: + ntoks += 1 + yield tokens[n], logprobs[n] + + if ntoks == max_tokens: + break + + y = mx.array([tokens[n]], mx.uint32) + draft_y = y + + # If we accpeted all the draft tokens, include the last + # draft token in the next draft step since it hasn't been + # processed yet by the draft model + if n == num_draft: + draft_y = mx.concatenate( + [mx.array(draft_tokens[-1:], mx.uint32), draft_y] + ) + + _rewind_cache(num_draft, n) + finally: + _rewind_cache(num_draft, n) + + def stream_generate( model: nn.Module, tokenizer: Union[PreTrainedTokenizer, TokenizerWrapper], prompt: Union[str, mx.array, List[int]], + draft_model: Optional[nn.Module] = None, **kwargs, ) -> Generator[GenerationResponse, None, None]: """ @@ -341,7 +476,11 @@ def stream_generate( Args: model (nn.Module): The model to use for generation. tokenizer (PreTrainedTokenizer): The tokenizer. - prompt (Union[str, mx.array, List[int]]): The input prompt string or integer tokens. + prompt (Union[str, mx.array, List[int]]): The input prompt string or + integer tokens. + draft_model (Optional[nn.Module]): An optional draft model. If provided + then speculative decoding is used. The draft model must use the same + tokenizer as the main model. Default: ``None``. kwargs: The remaining options get passed to :func:`generate_step`. See :func:`generate_step` for more details. @@ -363,10 +502,18 @@ def stream_generate( detokenizer = tokenizer.detokenizer + if draft_model is None: + kwargs.pop("num_draft_tokens", None) + token_generator = generate_step(prompt, model, **kwargs) + else: + kwargs.pop("max_kv_size", None) + token_generator = speculative_generate_step( + prompt, model, draft_model, **kwargs + ) with wired_limit(model, [generation_stream]): detokenizer.reset() tic = time.perf_counter() - for n, (token, logprobs) in enumerate(generate_step(prompt, model, **kwargs)): + for n, (token, logprobs) in enumerate(token_generator): if n == 0: prompt_time = time.perf_counter() - tic prompt_tps = prompt.size / prompt_time From 514502da22f0dc4c1ac439bdf78c07d5ec41acf7 Mon Sep 17 00:00:00 2001 From: "Xingjun.Wang" Date: Sat, 11 Jan 2025 07:29:34 +0800 Subject: [PATCH 122/188] Support snapshot_download for ModelScope (#1194) * add MLX_USE_MODELSCOPE env * update * update snapshot_download * update * remove modelscope dependency and add import check * update * nits * fix --------- Co-authored-by: wangxingjun778 Co-authored-by: Awni Hannun --- llms/mlx_lm/utils.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index 2fc0446b..b9037295 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -7,6 +7,7 @@ import glob import importlib import json import logging +import os import shutil import time from dataclasses import dataclass @@ -16,7 +17,17 @@ from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Type, import mlx.core as mx import mlx.nn as nn -from huggingface_hub import snapshot_download + +if os.getenv("MLXLM_USE_MODELSCOPE", "False").lower() == "true": + try: + from modelscope import snapshot_download + except ImportError: + raise ImportError( + "Please run `pip install modelscope` to activate the ModelScope." + ) +else: + from huggingface_hub import snapshot_download + from mlx.utils import tree_flatten, tree_reduce from transformers import PreTrainedTokenizer @@ -154,11 +165,12 @@ def get_model_path(path_or_hf_repo: str, revision: Optional[str] = None) -> Path Path: The path to the model. """ model_path = Path(path_or_hf_repo) + if not model_path.exists(): try: model_path = Path( snapshot_download( - repo_id=path_or_hf_repo, + path_or_hf_repo, revision=revision, allow_patterns=[ "*.json", From bf2da36fc640e6bfab933ac8c10d76c86fcdb288 Mon Sep 17 00:00:00 2001 From: Prince Canuma Date: Sun, 12 Jan 2025 21:58:08 +0100 Subject: [PATCH 123/188] Fix Cohere2: mask shape error (long context) (#1202) * fix mask shape error (long context) * Update llms/mlx_lm/models/cohere2.py Co-authored-by: Awni Hannun * revert layer_idx * black formatting * Update cohere2.py * format --------- Co-authored-by: Awni Hannun Co-authored-by: Awni Hannun --- llms/mlx_lm/models/cohere2.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/llms/mlx_lm/models/cohere2.py b/llms/mlx_lm/models/cohere2.py index ec0e9276..19bfa6b6 100644 --- a/llms/mlx_lm/models/cohere2.py +++ b/llms/mlx_lm/models/cohere2.py @@ -156,12 +156,13 @@ class CohereModel(nn.Module): ): h = self.embed_tokens(inputs) - if mask is None: - mask = create_attention_mask(h, cache) - if cache is None: cache = [None] * len(self.layers) + if mask is None: + j = self.args.sliding_window_pattern + mask = create_attention_mask(h, cache[j - 1 : j]) + for layer, c in zip(self.layers, cache): h = layer(h, mask, c) From 0228c46434157adaa48b44f9a227d2bb93354dc3 Mon Sep 17 00:00:00 2001 From: Chime Ogbuji Date: Mon, 13 Jan 2025 13:01:18 -0500 Subject: [PATCH 124/188] Custom local dataset features (#1085) * Generalize prompt_feature and completion_feature for use in local datasets to facilitate compatibility with many other training dataset formats. * Persist configured prompt/completion key * rebase + nits --------- Co-authored-by: Awni Hannun --- llms/mlx_lm/LORA.md | 17 +++++++++-- llms/mlx_lm/tuner/datasets.py | 55 ++++++++++++++++++++++++++--------- 2 files changed, 56 insertions(+), 16 deletions(-) diff --git a/llms/mlx_lm/LORA.md b/llms/mlx_lm/LORA.md index 15676360..9eac9d7f 100644 --- a/llms/mlx_lm/LORA.md +++ b/llms/mlx_lm/LORA.md @@ -241,14 +241,25 @@ Refer to the documentation for the model you are fine-tuning for more details. {"prompt": "What is the capital of France?", "completion": "Paris."} ``` +For the `completions` data format, a different key can be used for the prompt +and completion by specifying the following in the YAML config: + +```yaml +prompt_feature: "input" +completion_feature: "output" +``` + +Here, `"input"` is the expected key instead of the default `"prompt"`, and +`"output"` is the expected key instead of `"completion"`. + `text`: ```jsonl {"text": "This is an example for the model."} ``` -Note, the format is automatically determined by the dataset. Note also, keys in -each line not expected by the loader will be ignored. +Note, the format is automatically determined by the dataset. Note also, keys +in each line not expected by the loader will be ignored. > [!NOTE] > Each example in the datasets must be on a single line. Do not put more than @@ -270,7 +281,7 @@ Otherwise, provide a mapping of keys in the dataset to the features MLX LM expects. Use a YAML config to specify the Hugging Face dataset arguments. For example: -``` +```yaml hf_dataset: name: "billsum" prompt_feature: "text" diff --git a/llms/mlx_lm/tuner/datasets.py b/llms/mlx_lm/tuner/datasets.py index fa848f47..1b09c7e2 100644 --- a/llms/mlx_lm/tuner/datasets.py +++ b/llms/mlx_lm/tuner/datasets.py @@ -1,6 +1,6 @@ import json from pathlib import Path -from typing import Dict, List +from typing import Dict, List, Optional from transformers import PreTrainedTokenizer @@ -61,8 +61,8 @@ class CompletionsDataset: self, data: List[Dict[str, str]], tokenizer: PreTrainedTokenizer, - prompt_key: str = "prompt", - completion_key: str = "completion", + prompt_key: str, + completion_key: str, ): self._data = [ tokenizer.apply_chat_template( @@ -81,13 +81,19 @@ class CompletionsDataset: return len(self._data) -def create_dataset(data, tokenizer: PreTrainedTokenizer): +def create_dataset( + data, + tokenizer: PreTrainedTokenizer, + prompt_feature: Optional[str] = None, + completion_feature: Optional[str] = None, +): + prompt_feature = prompt_feature or "prompt" + completion_feature = completion_feature or "completion" sample = data[0] - if "messages" in sample: return ChatDataset(data, tokenizer) - elif "prompt" in sample and "completion" in sample: - return CompletionsDataset(data, tokenizer) + elif prompt_feature in sample and completion_feature in sample: + return CompletionsDataset(data, tokenizer, prompt_feature, completion_feature) elif "text" in sample: return Dataset(data, tokenizer) else: @@ -97,20 +103,30 @@ def create_dataset(data, tokenizer: PreTrainedTokenizer): ) -def load_local_dataset(data_path: Path, tokenizer: PreTrainedTokenizer): +def load_local_dataset( + data_path: Path, + tokenizer: PreTrainedTokenizer, + prompt_feature: Optional[str] = None, + completion_feature: Optional[str] = None, +): def load_subset(path): if not path.exists(): return [] with open(path, "r") as fid: data = [json.loads(l) for l in fid] - return create_dataset(data, tokenizer) + return create_dataset(data, tokenizer, prompt_feature, completion_feature) names = ("train", "valid", "test") train, valid, test = [load_subset(data_path / f"{n}.jsonl") for n in names] return train, valid, test -def load_hf_dataset(data_id: str, tokenizer: PreTrainedTokenizer): +def load_hf_dataset( + data_id: str, + tokenizer: PreTrainedTokenizer, + prompt_feature: Optional[str] = None, + completion_feature: Optional[str] = None, +): from datasets import exceptions, load_dataset try: @@ -119,7 +135,13 @@ def load_hf_dataset(data_id: str, tokenizer: PreTrainedTokenizer): names = ("train", "valid", "test") train, valid, test = [ - create_dataset(dataset[n], tokenizer) if n in dataset.keys() else [] + ( + create_dataset( + dataset[n], tokenizer, prompt_feature, completion_feature + ) + if n in dataset.keys() + else [] + ) for n in names ] @@ -175,11 +197,18 @@ def load_dataset(args, tokenizer: PreTrainedTokenizer): train, valid, test = load_custom_hf_dataset(args, tokenizer) else: data_path = Path(args.data) + + prompt_feature = getattr(args, "prompt_feature", None) + completion_feature = getattr(args, "completion_feature", None) if data_path.exists(): - train, valid, test = load_local_dataset(data_path, tokenizer) + train, valid, test = load_local_dataset( + data_path, tokenizer, prompt_feature, completion_feature + ) else: print(f"Loading Hugging Face dataset {args.data}.") - train, valid, test = load_hf_dataset(args.data, tokenizer) + train, valid, test = load_hf_dataset( + args.data, tokenizer, prompt_feature, completion_feature + ) if args.train and len(train) == 0: raise ValueError( From c117af83b8cbec15523bd0d69e7a57f01237ca89 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Mon, 13 Jan 2025 10:22:32 -0800 Subject: [PATCH 125/188] fix gpt bigcode (#1204) --- llms/mlx_lm/models/gpt_bigcode.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/llms/mlx_lm/models/gpt_bigcode.py b/llms/mlx_lm/models/gpt_bigcode.py index 8415c59e..1d9794b6 100644 --- a/llms/mlx_lm/models/gpt_bigcode.py +++ b/llms/mlx_lm/models/gpt_bigcode.py @@ -145,16 +145,16 @@ class GPTBigCodeModel(nn.Module): hidden_states = self.wte(inputs) mask = None - if hidden_states.shape[1] > 1: - - position_ids = mx.array(np.arange(L)) - hidden_states += self.wpe(position_ids) - - if mask is None: - mask = create_attention_mask(hidden_states, cache) + if mask is not None and hidden_states.shape[1] > 1: + mask = create_attention_mask(hidden_states, cache) if cache is None: cache = [None] * len(self.h) + position_ids = mx.array(np.arange(L)) + else: + position_ids = mx.array(np.arange(cache[0].offset, cache[0].offset + L)) + + hidden_states += self.wpe(position_ids) for layer, c in zip(self.h, cache): hidden_states = layer(hidden_states, mask, cache=c) From 6ae6c72c2ec9d4a2adb453712a75099b84e9593a Mon Sep 17 00:00:00 2001 From: Ivan Fioravanti Date: Wed, 15 Jan 2025 02:20:42 +0100 Subject: [PATCH 126/188] reduction moved to CPU in case of distributed training (#1200) --- llms/mlx_lm/tuner/trainer.py | 8 ++++---- llms/tests/test_finetune.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/llms/mlx_lm/tuner/trainer.py b/llms/mlx_lm/tuner/trainer.py index a76b8336..63ca58bb 100644 --- a/llms/mlx_lm/tuner/trainer.py +++ b/llms/mlx_lm/tuner/trainer.py @@ -159,8 +159,8 @@ def evaluate( ntokens += toks mx.eval(all_losses, ntokens) - all_losses = mx.distributed.all_sum(all_losses) - ntokens = mx.distributed.all_sum(ntokens) + all_losses = mx.distributed.all_sum(all_losses, stream=mx.cpu) + ntokens = mx.distributed.all_sum(ntokens, stream=mx.cpu) return (all_losses / ntokens).item() @@ -272,9 +272,9 @@ def train( if it % args.steps_per_report == 0 or it == args.iters: stop = time.perf_counter() - train_loss = mx.distributed.all_sum(losses).item() + train_loss = mx.distributed.all_sum(losses, stream=mx.cpu).item() train_loss /= steps * mx.distributed.init().size() - n_tokens = mx.distributed.all_sum(n_tokens).item() + n_tokens = mx.distributed.all_sum(n_tokens, stream=mx.cpu).item() learning_rate = optimizer.learning_rate.item() it_sec = args.steps_per_report / (stop - start) tokens_sec = float(n_tokens) / (stop - start) diff --git a/llms/tests/test_finetune.py b/llms/tests/test_finetune.py index 6ba81628..a6d53747 100644 --- a/llms/tests/test_finetune.py +++ b/llms/tests/test_finetune.py @@ -21,7 +21,7 @@ from mlx_lm.tuner.utils import build_schedule @contextmanager def swapped_with_identity(obj, func): old_func = getattr(obj, func) - setattr(obj, func, lambda x: x) + setattr(obj, func, lambda x, **kwargs: x) yield setattr(obj, func, old_func) From 50f0a7f6d99839de0b9439d609e136089f141a3c Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Wed, 15 Jan 2025 14:55:41 -0800 Subject: [PATCH 127/188] add internlm3 (#1206) --- llms/mlx_lm/models/internlm3.py | 241 ++++++++++++++++++++++++++++++++ llms/mlx_lm/tuner/utils.py | 1 + llms/tests/test_models.py | 17 +++ 3 files changed, 259 insertions(+) create mode 100644 llms/mlx_lm/models/internlm3.py diff --git a/llms/mlx_lm/models/internlm3.py b/llms/mlx_lm/models/internlm3.py new file mode 100644 index 00000000..3be6f536 --- /dev/null +++ b/llms/mlx_lm/models/internlm3.py @@ -0,0 +1,241 @@ +# Copyright © 2023-2024 Apple Inc. + +from dataclasses import dataclass +from typing import Any, Dict, Optional, Tuple, Union + +import mlx.core as mx +import mlx.nn as nn + +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention + + +@dataclass +class ModelArgs(BaseModelArgs): + model_type: str + hidden_size: int + num_hidden_layers: int + intermediate_size: int + num_attention_heads: int + rms_norm_eps: float + vocab_size: int + bias: bool = False + qkv_bias: bool = False + max_position_embeddings: int = 32768 + num_key_value_heads: int = None + rope_theta: float = 10000 + rope_traditional: bool = False + rope_scaling: Optional[Dict[str, Union[float, str]]] = None + tie_word_embeddings: bool = False + + def __post_init__(self): + if self.num_key_value_heads is None: + self.num_key_value_heads = self.num_attention_heads + + if self.rope_scaling: + required_keys = {"factor", "rope_type"} + if not all(key in self.rope_scaling for key in required_keys): + raise ValueError(f"rope_scaling must contain keys {required_keys}") + + if self.rope_scaling["rope_type"] not in ["linear", "dynamic"]: + raise ValueError( + "rope_scaling 'rope_type' currently only supports 'linear' or 'dynamic" + ) + + +class DynamicNTKScalingRoPE(nn.Module): + """Implements the rotary positional encoding with Dynamic NTK scaling.""" + + def __init__( + self, + dims: int, + max_position_embeddings: int = 2048, + traditional: bool = False, + base: float = 10000, + scale: float = 1.0, + ): + super().__init__() + self.max_position_embeddings = max_position_embeddings + self.original_base = base + self.dims = dims + self.traditional = traditional + self.scale = scale + + def extra_repr(self): + return f"{self.dims}, traditional={self.traditional}, max_position_embeddings={self.max_position_embeddings}, scaling_factor={self.scaling_factor}" + + def __call__(self, x, offset: int = 0): + seq_len = x.shape[1] + offset + if seq_len > self.max_position_embeddings: + base = self.original_base * ( + (self.scale * seq_len / self.max_position_embeddings) - (self.scale - 1) + ) ** (self.dims / (self.dims - 2)) + else: + base = self.original_base + + return mx.fast.rope( + x, + self.dims, + traditional=self.traditional, + base=base, + scale=self.scale, + offset=offset, + ) + + +class Attention(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + + dim = args.hidden_size + qkv_bias = args.qkv_bias + self.n_heads = n_heads = args.num_attention_heads + self.n_kv_heads = n_kv_heads = args.num_key_value_heads + self.n_kv_groups = n_heads // args.num_key_value_heads + + self.head_dim = head_dim = args.hidden_size // n_heads + self.scale = head_dim**-0.5 + + self.q_proj = nn.Linear(dim, n_heads * head_dim, bias=qkv_bias) + self.k_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=qkv_bias) + self.v_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=qkv_bias) + self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=qkv_bias) + + rope_scale = ( + 1 / args.rope_scaling["factor"] + if args.rope_scaling is not None + and args.rope_scaling["rope_type"] == "linear" + else 2.0 + ) + + self.rope = DynamicNTKScalingRoPE( + head_dim, + max_position_embeddings=args.max_position_embeddings, + traditional=args.rope_traditional, + base=args.rope_theta, + scale=rope_scale, + ) + + def __call__( + self, + x: mx.array, + mask: Optional[mx.array] = None, + cache: Optional[Any] = None, + ) -> mx.array: + B, L, D = x.shape + + queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x) + + # Prepare the queries, keys and values for the attention computation + queries = queries.reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) + keys = keys.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) + values = values.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) + + if cache is not None: + queries = self.rope(queries, offset=cache.offset) + keys = self.rope(keys, offset=cache.offset) + keys, values = cache.update_and_fetch(keys, values) + else: + queries = self.rope(queries) + keys = self.rope(keys) + + output = scaled_dot_product_attention( + queries, keys, values, cache=cache, scale=self.scale, mask=mask + ) + + output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) + return self.o_proj(output) + + +class MLP(nn.Module): + def __init__(self, dim, hidden_dim, bias): + super().__init__() + self.gate_proj = nn.Linear(dim, hidden_dim, bias=bias) + self.down_proj = nn.Linear(hidden_dim, dim, bias=bias) + self.up_proj = nn.Linear(dim, hidden_dim, bias=bias) + + def __call__(self, x) -> mx.array: + return self.down_proj(nn.silu(self.gate_proj(x)) * self.up_proj(x)) + + +class TransformerBlock(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.self_attn = Attention(args) + self.mlp = MLP(args.hidden_size, args.intermediate_size, args.bias) + self.input_layernorm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) + self.post_attention_layernorm = nn.RMSNorm( + args.hidden_size, eps=args.rms_norm_eps + ) + + def __call__( + self, + x: mx.array, + mask: Optional[mx.array] = None, + cache: Optional[Any] = None, + ) -> mx.array: + r = self.self_attn(self.input_layernorm(x), mask, cache) + h = x + r + r = self.mlp(self.post_attention_layernorm(h)) + out = h + r + return out + + +class InternLM2Model(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + assert args.vocab_size > 0 + self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size) + self.layers = [ + TransformerBlock(args=args) for _ in range(args.num_hidden_layers) + ] + self.norm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) + + def __call__( + self, + inputs: mx.array, + mask: mx.array = None, + cache=None, + ): + h = self.embed_tokens(inputs) + + if mask is None: + mask = create_attention_mask(h, cache) + + if cache is None: + cache = [None] * len(self.layers) + + for layer, c in zip(self.layers, cache): + h = layer(h, mask, cache=c) + + return self.norm(h) + + +class Model(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.args = args + self.model_type = args.model_type + self.model = InternLM2Model(args) + if not args.tie_word_embeddings: + self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=False) + + def __call__( + self, + inputs: mx.array, + mask: mx.array = None, + cache=None, + ): + out = self.model(inputs, mask, cache) + if self.args.tie_word_embeddings: + out = self.model.embed_tokens.as_linear(out) + else: + out = self.lm_head(out) + return out + + def sanitize(self, weights): + # Remove unused precomputed rotary freqs + return {k: v for k, v in weights.items() if "attention.rope.inv_freq" not in k} + + @property + def layers(self): + return self.model.layers diff --git a/llms/mlx_lm/tuner/utils.py b/llms/mlx_lm/tuner/utils.py index 3986952a..594f8040 100644 --- a/llms/mlx_lm/tuner/utils.py +++ b/llms/mlx_lm/tuner/utils.py @@ -100,6 +100,7 @@ def linear_to_lora_layers( "minicpm", "deepseek", "olmo2", + "internlm3", ]: keys = set(["self_attn.q_proj", "self_attn.v_proj"]) if model.model_type in ["mixtral", "phimoe"]: diff --git a/llms/tests/test_models.py b/llms/tests/test_models.py index 118ec6f2..d8cf6820 100644 --- a/llms/tests/test_models.py +++ b/llms/tests/test_models.py @@ -927,6 +927,23 @@ class TestModels(unittest.TestCase): model, args.model_type, args.vocab_size, args.num_hidden_layers ) + def test_internlm3(self): + from mlx_lm.models import internlm3 + + args = internlm3.ModelArgs( + model_type="internlm3", + hidden_size=1024, + num_hidden_layers=4, + intermediate_size=2048, + num_attention_heads=4, + rms_norm_eps=1e-5, + vocab_size=10_000, + ) + model = internlm3.Model(args) + self.model_test_runner( + model, args.model_type, args.vocab_size, args.num_hidden_layers + ) + if __name__ == "__main__": unittest.main() From 07f88f8057d1743f2a147468ad62c51bece783cd Mon Sep 17 00:00:00 2001 From: Jarrett <2613089+jjaareet@users.noreply.github.com> Date: Thu, 16 Jan 2025 12:15:42 -0700 Subject: [PATCH 128/188] fix(lora): add back store_true default args (#1205) --- llms/mlx_lm/lora.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/llms/mlx_lm/lora.py b/llms/mlx_lm/lora.py index 4d050bd5..43f508c3 100644 --- a/llms/mlx_lm/lora.py +++ b/llms/mlx_lm/lora.py @@ -78,6 +78,7 @@ def build_parser(): "--train", action="store_true", help="Do training", + default=None, ) parser.add_argument( "--data", @@ -135,6 +136,7 @@ def build_parser(): "--test", action="store_true", help="Evaluate on the test set after training", + default=None, ) parser.add_argument( "--test-batches", @@ -156,6 +158,7 @@ def build_parser(): "--grad-checkpoint", action="store_true", help="Use gradient checkpointing to reduce memory use.", + default=None, ) parser.add_argument("--seed", type=int, help="The PRNG seed") return parser From df1406735b1e140e44b29b302783fbc837da9269 Mon Sep 17 00:00:00 2001 From: Victor Nogueira Date: Tue, 21 Jan 2025 23:12:43 +0100 Subject: [PATCH 129/188] Fix dataset variable name, in `datasets.py` (#1212) --- llms/mlx_lm/tuner/datasets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llms/mlx_lm/tuner/datasets.py b/llms/mlx_lm/tuner/datasets.py index 1b09c7e2..377e7cae 100644 --- a/llms/mlx_lm/tuner/datasets.py +++ b/llms/mlx_lm/tuner/datasets.py @@ -170,7 +170,7 @@ def load_custom_hf_dataset(args, tokenizer: PreTrainedTokenizer): if prompt_feature and completion_feature: return CompletionsDataset(ds, tokenizer, prompt_feature, completion_feature) elif text_feature: - return Dataset(train_ds, tokenizer, text_key=text_feature) + return Dataset(ds, tokenizer, text_key=text_feature) else: raise ValueError( "Specify either a prompt and completion feature or a text " From 9a3ddc3e656e2b8d43eba3576f0e72fd3a0b2681 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Tue, 21 Jan 2025 19:40:29 -0800 Subject: [PATCH 130/188] some fixes for pipeline parallel deep seek r1 (#1216) --- llms/mlx_lm/examples/pipeline_generate.py | 9 ++++++--- llms/mlx_lm/models/deepseek_v3.py | 11 ++++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/llms/mlx_lm/examples/pipeline_generate.py b/llms/mlx_lm/examples/pipeline_generate.py index b98e757b..2970b986 100644 --- a/llms/mlx_lm/examples/pipeline_generate.py +++ b/llms/mlx_lm/examples/pipeline_generate.py @@ -22,6 +22,11 @@ import mlx.core as mx from mlx_lm import load, stream_generate parser = argparse.ArgumentParser(description="LLM pipelined inference example") +parser.add_argument( + "--model", + default="mlx-community/DeepSeek-R1-3bit", + help="HF repo or path to local model.", +) parser.add_argument( "--prompt", "-p", @@ -37,9 +42,7 @@ parser.add_argument( ) args = parser.parse_args() -model_repo = "mlx-community/DeepSeek-V3-3bit" - -model, tokenizer = load(model_repo, lazy=True) +model, tokenizer = load(args.model, lazy=True) messages = [{"role": "user", "content": args.prompt}] prompt = tokenizer.apply_chat_template(messages, add_generation_prompt=True) diff --git a/llms/mlx_lm/models/deepseek_v3.py b/llms/mlx_lm/models/deepseek_v3.py index f95949f9..46ee6ab3 100644 --- a/llms/mlx_lm/models/deepseek_v3.py +++ b/llms/mlx_lm/models/deepseek_v3.py @@ -400,6 +400,8 @@ class DeepseekV3Model(nn.Module): pipeline_rank = self.pipeline_rank pipeline_size = self.pipeline_size + # Hack to avoid time-outs during prompt-processing + dist_stream = mx.cpu if h.shape[1] > 1 else mx.gpu if mask is None: mask = create_attention_mask(h, cache) @@ -407,18 +409,21 @@ class DeepseekV3Model(nn.Module): cache = [None] * len(self.layers) # Receive from the previous process in the pipeline + if pipeline_rank < pipeline_size - 1: - h = mx.distributed.recv_like(h, (pipeline_rank + 1)) + h = mx.distributed.recv_like(h, (pipeline_rank + 1), stream=dist_stream) for layer, c in zip(self.layers, cache): h = layer(h, mask, c) # Send to the next process in the pipeline if pipeline_rank != 0: - h = mx.distributed.send(h, (pipeline_rank - 1) % pipeline_size) + h = mx.distributed.send( + h, (pipeline_rank - 1) % pipeline_size, stream=dist_stream + ) # Broadcast h while keeping it in the graph - h = mx.distributed.all_gather(h)[: h.shape[0]] + h = mx.distributed.all_gather(h, stream=dist_stream)[: h.shape[0]] return self.norm(h) From 77faa14ba44dd1ac80c9395f0865632f2b3a736f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6kdeniz=20G=C3=BClmez?= <60228478+Goekdeniz-Guelmez@users.noreply.github.com> Date: Sun, 26 Jan 2025 16:19:07 +0100 Subject: [PATCH 131/188] adding support for kyutai's helium (#1208) * initial commit * adding helium into training * Update ACKNOWLEDGMENTS.md * nits * nits * fixes / nits --------- Co-authored-by: Awni Hannun --- ACKNOWLEDGMENTS.md | 2 +- llms/mlx_lm/models/helium.py | 183 +++++++++++++++++++++++++++++++++++ llms/mlx_lm/tuner/utils.py | 1 + 3 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 llms/mlx_lm/models/helium.py diff --git a/ACKNOWLEDGMENTS.md b/ACKNOWLEDGMENTS.md index 41557c29..851c995c 100644 --- a/ACKNOWLEDGMENTS.md +++ b/ACKNOWLEDGMENTS.md @@ -14,4 +14,4 @@ MLX Examples was developed with contributions from the following individuals: - Markus Enzweiler: Added the `cvae` examples. - Prince Canuma: Helped add support for `Starcoder2` models. - Shiyu Li: Added the `Segment Anything Model`. -- Gökdeniz Gülmez: Added support for `MiniCPM`, `Mamba` and support for `full-fine-tuning`. \ No newline at end of file +- Gökdeniz Gülmez: Added support for `MiniCPM`, `Helium`, `Mamba version 1` and support for `full-fine-tuning`. \ No newline at end of file diff --git a/llms/mlx_lm/models/helium.py b/llms/mlx_lm/models/helium.py new file mode 100644 index 00000000..6ca46a72 --- /dev/null +++ b/llms/mlx_lm/models/helium.py @@ -0,0 +1,183 @@ +from dataclasses import dataclass +from typing import Any, Optional, Tuple + +import mlx.core as mx +import mlx.nn as nn + +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention + + +@dataclass +class ModelArgs(BaseModelArgs): + hidden_size: int + num_hidden_layers: int + intermediate_size: int + num_attention_heads: int + num_key_value_heads: int + rms_norm_eps: float + vocab_size: int + attention_bias: bool + head_dim: int + max_position_embeddings: int + mlp_bias: bool + model_type: str + rope_theta: float + tie_word_embeddings: bool + + +class HeliumAttention(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + + dim = args.hidden_size + self.n_heads = n_heads = args.num_attention_heads + assert args.num_key_value_heads is not None + self.n_kv_heads = n_kv_heads = args.num_key_value_heads + + head_dim = args.hidden_size // n_heads + self.scale = head_dim**-0.5 + + self.q_proj = nn.Linear(dim, n_heads * head_dim, bias=args.attention_bias) + self.k_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=args.attention_bias) + self.v_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=args.attention_bias) + self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=False) + self.rope = nn.RoPE(head_dim, traditional=True, base=args.rope_theta) + + def __call__( + self, + x: mx.array, + mask: Optional[mx.array] = None, + cache: Optional[Any] = None, + ) -> mx.array: + B, L, D = x.shape + + queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x) + + # Prepare the queries, keys and values for the attention computation + queries = queries.reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) + keys = keys.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) + values = values.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) + + if cache is not None: + queries = self.rope(queries, offset=cache.offset) + keys = self.rope(keys, offset=cache.offset) + keys, values = cache.update_and_fetch(keys, values) + else: + queries = self.rope(queries) + keys = self.rope(keys) + + output = scaled_dot_product_attention( + queries, keys, values, cache=cache, scale=self.scale, mask=mask + ) + output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) + return self.o_proj(output) + + +class HeliumMLP(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.hidden_size = args.hidden_size + self.intermediate_size = args.intermediate_size + + self.gate_proj = nn.Linear( + self.hidden_size, self.intermediate_size, bias=args.mlp_bias + ) + self.up_proj = nn.Linear( + self.hidden_size, self.intermediate_size, bias=args.mlp_bias + ) + self.down_proj = nn.Linear( + self.intermediate_size, self.hidden_size, bias=args.mlp_bias + ) + + def __call__(self, x: mx.array) -> mx.array: + return self.down_proj(nn.silu(self.gate_proj(x)) * self.up_proj(x)) + + +class HeliumDecoderLayer(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.hidden_size = args.hidden_size + + self.self_attn = HeliumAttention(args) + self.mlp = HeliumMLP(args) + self.input_layernorm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) + self.post_attention_layernorm = nn.RMSNorm( + args.hidden_size, eps=args.rms_norm_eps + ) + + def __call__( + self, + x: mx.array, + mask: Optional[mx.array] = None, + cache: Optional[Any] = None, + ) -> mx.array: + r = self.self_attn(self.input_layernorm(x), mask, cache) + h = x + r + r = self.mlp(self.post_attention_layernorm(h)) + out = h + r + return out + + +class HeliumModel(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.num_hidden_layers = args.num_hidden_layers + self.vocab_size = args.vocab_size + + assert self.vocab_size > 0 + self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size) + + self.layers = [HeliumDecoderLayer(args) for _ in range(args.num_hidden_layers)] + + self.norm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) + + def __call__( + self, + inputs: mx.array, + mask: mx.array = None, + cache=None, + ) -> mx.array: + h = self.embed_tokens(inputs) + + if mask is None: + mask = create_attention_mask(h, cache) + + if cache is None: + cache = [None] * len(self.layers) + + for layer, c in zip(self.layers, cache): + h = layer(h, mask, c) + + return self.norm(h) + + +class Model(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.args = args + self.model_type = args.model_type + + self.model = HeliumModel(args) + + self.vocab_size = args.vocab_size + self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=False) + + if not args.tie_word_embeddings: + self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=False) + + def __call__( + self, + inputs: mx.array, + mask: mx.array = None, + cache=None, + ) -> mx.array: + out = self.model(inputs, mask, cache) + if self.args.tie_word_embeddings: + out = self.model.embed_tokens.as_linear(out) + else: + out = self.lm_head(out) + return out + + @property + def layers(self): + return self.model.layers diff --git a/llms/mlx_lm/tuner/utils.py b/llms/mlx_lm/tuner/utils.py index 594f8040..c0e52731 100644 --- a/llms/mlx_lm/tuner/utils.py +++ b/llms/mlx_lm/tuner/utils.py @@ -94,6 +94,7 @@ def linear_to_lora_layers( "phimoe", "gemma", "gemma2", + "helium", "starcoder2", "cohere", "cohere2", From f44a52e2dc8ff2173230e69420652fafa31207cc Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Mon, 27 Jan 2025 15:40:31 -0800 Subject: [PATCH 132/188] batched min p and fix spec gen sampling (#1222) --- llms/mlx_lm/sample_utils.py | 20 +++++++++----------- llms/mlx_lm/utils.py | 5 +++-- llms/tests/test_sample_utils.py | 12 ++++++++++++ 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/llms/mlx_lm/sample_utils.py b/llms/mlx_lm/sample_utils.py index c48a32cf..23e08d97 100644 --- a/llms/mlx_lm/sample_utils.py +++ b/llms/mlx_lm/sample_utils.py @@ -147,11 +147,11 @@ def min_p_sampling( logprobs = logprobs * (1 / temperature) # Indices sorted in decreasing order - sorted_indices = mx.argsort(-logprobs).squeeze(0) - sorted_logprobs = logprobs[..., sorted_indices] + sorted_indices = mx.argsort(-logprobs, axis=-1) + sorted_logprobs = mx.take_along_axis(logprobs, sorted_indices, axis=-1) # Top probability - top_logprobs = logprobs[..., sorted_indices[0]] + top_logprobs = sorted_logprobs[:, 0:1] # Calculate the min_p threshold scaled_min_p = top_logprobs + math.log(min_p) @@ -163,9 +163,9 @@ def min_p_sampling( # Create pool of tokens with probability less than scaled min_p selected_logprobs = mx.where(tokens_to_remove, -float("inf"), sorted_logprobs) - # Return sampled token - sorted_token = mx.random.categorical(selected_logprobs) - return sorted_indices[sorted_token] + # Return sampled tokens + sorted_tokens = mx.random.categorical(selected_logprobs, axis=-1)[:, None] + return mx.take_along_axis(sorted_indices, sorted_tokens, axis=-1).squeeze(1) @partial(mx.compile, inputs=mx.random.state, outputs=mx.random.state) @@ -185,7 +185,7 @@ def top_p_sampling(logits: mx.array, top_p: float, temperature: float) -> mx.arr # sort probs in ascending order sorted_indices = mx.argsort(probs, axis=-1) - sorted_probs = probs[..., sorted_indices.squeeze(0)] + sorted_probs = mx.take_along_axis(probs, sorted_indices, axis=-1) cumulative_probs = mx.cumsum(sorted_probs, axis=-1) @@ -196,10 +196,8 @@ def top_p_sampling(logits: mx.array, top_p: float, temperature: float) -> mx.arr 0, ) - sorted_token = mx.random.categorical(mx.log(top_probs)) - token = sorted_indices.squeeze(0)[sorted_token] - - return token + sorted_tokens = mx.random.categorical(mx.log(top_probs), axis=-1)[:, None] + return mx.take_along_axis(sorted_indices, sorted_tokens, axis=-1).squeeze(1) @partial(mx.compile, inputs=mx.random.state, outputs=mx.random.state) diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index b9037295..0150f1b7 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -398,8 +398,9 @@ def speculative_generate_step( quantize_cache_fn(cache) logprobs = logits - mx.logsumexp(logits, keepdims=True) - y = sampler(logprobs).squeeze(0) - return y, logprobs.squeeze(0) + logprobs = logprobs.squeeze(0) + y = sampler(logprobs) + return y, logprobs def _prefill(model, cache, y): while y.size > prefill_step_size: diff --git a/llms/tests/test_sample_utils.py b/llms/tests/test_sample_utils.py index c45fa443..f12abbf4 100644 --- a/llms/tests/test_sample_utils.py +++ b/llms/tests/test_sample_utils.py @@ -28,6 +28,12 @@ class TestSampleUtils(unittest.TestCase): token = top_p_sampling(logits, 0.95, temperature).item() self.assertTrue(token in (1, 2, 3)) + # Batch mode works + probs = mx.array([[0.9, 0.0, 0.0, 0.1], [0.0, 0.8, 0.0, 0.1]]) + logits = mx.log(probs) + tokens = top_p_sampling(logits, 0.5, temperature) + self.assertEqual(tokens.tolist(), [0, 1]) + def test_min_p_sampling(self): probs = mx.array([0.9, 0.0, 0.0, 0.1])[None] logits = mx.log(probs) @@ -42,6 +48,12 @@ class TestSampleUtils(unittest.TestCase): token = min_p_sampling(logits, 0.05) self.assertTrue(token in (0, 3)) + # Batch mode works + probs = mx.array([[0.9, 0.0, 0.0, 0.1], [0.0, 0.8, 0.0, 0.1]]) + logits = mx.log(probs) + tokens = min_p_sampling(logits, 0.7) + self.assertEqual(tokens.tolist(), [0, 1]) + def test_top_k_sampling(self): probs = mx.array([0.9, 0.0, 0.0, 0.1])[None] logits = mx.log(probs) From 7a83077cd7798f0502d0f35af2a469f113d5e0f9 Mon Sep 17 00:00:00 2001 From: Anchen Date: Tue, 28 Jan 2025 12:13:50 +1100 Subject: [PATCH 133/188] chore(mlx-lm): support text type content in messages (#1225) * chore(mlx-lm): support text type content * chore: optimize the messagef content processing * nits + format --------- Co-authored-by: Awni Hannun --- llms/mlx_lm/server.py | 31 ++++++++++++++++++++++++++++++- llms/tests/test_server.py | 23 +++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/llms/mlx_lm/server.py b/llms/mlx_lm/server.py index 4523e3ae..de02704d 100644 --- a/llms/mlx_lm/server.py +++ b/llms/mlx_lm/server.py @@ -114,6 +114,33 @@ def convert_chat(messages: List[dict], role_mapping: Optional[dict] = None): return prompt.rstrip() +def process_message_content(messages): + """ + Convert message content to a format suitable for `apply_chat_template`. + + The function operates on messages in place. It converts the 'content' field + to a string instead of a list of text fragments. + + Args: + message_list (list): A list of dictionaries, where each dictionary may + have a 'content' key containing a list of dictionaries with 'type' and + 'text' keys. + + Raises: + ValueError: If the 'content' type is not supported or if 'text' is missing. + + """ + for message in messages: + content = message["content"] + if isinstance(content, list): + text_fragments = [ + fragment["text"] for fragment in content if fragment["type"] == "text" + ] + if len(text_fragments) != len(content): + raise ValueError("Only 'text' content type is supported.") + message["content"] = "".join(text_fragments) + + @dataclass class PromptCache: cache: List[Any] = field(default_factory=list) @@ -591,8 +618,10 @@ class APIHandler(BaseHTTPRequestHandler): self.request_id = f"chatcmpl-{uuid.uuid4()}" self.object_type = "chat.completion.chunk" if self.stream else "chat.completion" if self.tokenizer.chat_template: + messages = body["messages"] + process_message_content(messages) prompt = self.tokenizer.apply_chat_template( - body["messages"], + messages, body.get("tools", None), add_generation_prompt=True, ) diff --git a/llms/tests/test_server.py b/llms/tests/test_server.py index ad17554d..ecf95f78 100644 --- a/llms/tests/test_server.py +++ b/llms/tests/test_server.py @@ -80,6 +80,29 @@ class TestServer(unittest.TestCase): self.assertIn("id", response_body) self.assertIn("choices", response_body) + def test_handle_chat_completions_with_content_fragments(self): + url = f"http://localhost:{self.port}/v1/chat/completions" + chat_post_data = { + "model": "chat_model", + "max_tokens": 10, + "temperature": 0.7, + "top_p": 0.85, + "repetition_penalty": 1.2, + "messages": [ + { + "role": "system", + "content": [ + {"type": "text", "text": "You are a helpful assistant."} + ], + }, + {"role": "user", "content": [{"type": "text", "text": "Hello!"}]}, + ], + } + response = requests.post(url, json=chat_post_data) + response_body = response.text + self.assertIn("id", response_body) + self.assertIn("choices", response_body) + def test_handle_models(self): url = f"http://localhost:{self.port}/v1/models" response = requests.get(url) From e8afb59de4bb328bf73431081fd773b3a0c89ca7 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Tue, 28 Jan 2025 14:37:30 -0800 Subject: [PATCH 134/188] better overflow correction (#1229) --- llms/mlx_lm/models/deepseek_v3.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/llms/mlx_lm/models/deepseek_v3.py b/llms/mlx_lm/models/deepseek_v3.py index 46ee6ab3..96ce4f85 100644 --- a/llms/mlx_lm/models/deepseek_v3.py +++ b/llms/mlx_lm/models/deepseek_v3.py @@ -2,6 +2,7 @@ import math from dataclasses import dataclass +from functools import partial from typing import Any, Dict, Optional, Tuple import mlx.core as mx @@ -125,6 +126,12 @@ class DeepseekV3YarnRotaryEmbedding(nn.Module): ) +# A clipped silu to prevent fp16 from overflowing +@partial(mx.compile, shapeless=True) +def clipped_silu(x): + return mx.clip(x * mx.sigmoid(x), a_min=-100, a_max=100) + + class DeepseekV3Attention(nn.Module): def __init__(self, config: ModelArgs): super().__init__() @@ -312,7 +319,10 @@ class DeepseekV3MoE(nn.Module): self.config = config self.num_experts_per_tok = config.num_experts_per_tok self.switch_mlp = SwitchGLU( - config.hidden_size, config.moe_intermediate_size, config.n_routed_experts + config.hidden_size, + config.moe_intermediate_size, + config.n_routed_experts, + activation=clipped_silu, ) self.gate = MoEGate(config) @@ -359,11 +369,7 @@ class DeepseekV3DecoderLayer(nn.Module): r = self.self_attn(self.input_layernorm(x), mask, cache) h = x + r r = self.mlp(self.post_attention_layernorm(h)) - out = h + r - # Protect against overflow for fp16 - if out.dtype == mx.float16: - out = mx.clip(out, a_min=None, a_max=mx.finfo(mx.float16).max - 1000) - return out + return h + r class DeepseekV3Model(nn.Module): From 9c2ef38d4d7a6c6afc1f9c90cb26cadb95f7343e Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Sun, 2 Feb 2025 13:58:44 -0800 Subject: [PATCH 135/188] only download local shard (#1240) --- llms/mlx_lm/examples/pipeline_generate.py | 153 ++++++++++++++-------- llms/mlx_lm/models/deepseek_v2.py | 46 ++++++- llms/mlx_lm/models/deepseek_v3.py | 18 ++- llms/mlx_lm/utils.py | 7 +- 4 files changed, 159 insertions(+), 65 deletions(-) diff --git a/llms/mlx_lm/examples/pipeline_generate.py b/llms/mlx_lm/examples/pipeline_generate.py index 2970b986..d170405a 100644 --- a/llms/mlx_lm/examples/pipeline_generate.py +++ b/llms/mlx_lm/examples/pipeline_generate.py @@ -4,10 +4,11 @@ Run with: ``` -/path/to/mpirun \ - -np 2 \ +mlx.launch \ --hostfile /path/to/hosts.txt \ - python /path/to/pipeline_generate.py --prompt "hello world" + --backend mpi \ + /path/to/pipeline_generate.py \ + --prompt "hello world" ``` Make sure you can run MLX over MPI on two hosts. For more information see the @@ -17,62 +18,106 @@ https://ml-explore.github.io/mlx/build/html/usage/distributed.html). """ import argparse +import json +from pathlib import Path import mlx.core as mx +from huggingface_hub import snapshot_download +from mlx.utils import tree_flatten from mlx_lm import load, stream_generate - -parser = argparse.ArgumentParser(description="LLM pipelined inference example") -parser.add_argument( - "--model", - default="mlx-community/DeepSeek-R1-3bit", - help="HF repo or path to local model.", -) -parser.add_argument( - "--prompt", - "-p", - default="Write a quicksort in C++.", - help="Message to be processed by the model ('-' reads from stdin)", -) -parser.add_argument( - "--max-tokens", - "-m", - type=int, - default=256, - help="Maximum number of tokens to generate", -) -args = parser.parse_args() - -model, tokenizer = load(args.model, lazy=True) - -messages = [{"role": "user", "content": args.prompt}] -prompt = tokenizer.apply_chat_template(messages, add_generation_prompt=True) - -group = mx.distributed.init() -rank = group.rank() -model.model.pipeline(group) -mx.eval(model.parameters()) - -# Synchronize processes before generation to avoid timeout if downloading -# model for the first time. -mx.eval(mx.distributed.all_sum(mx.array(1.0), stream=mx.cpu)) +from mlx_lm.utils import load_model, load_tokenizer -def rprint(*args, **kwargs): - if rank == 0: - print(*args, **kwargs) +def download(repo: str, allow_patterns: list[str]) -> Path: + return Path( + snapshot_download( + repo, + allow_patterns=allow_patterns, + ) + ) -for response in stream_generate(model, tokenizer, prompt, max_tokens=args.max_tokens): - rprint(response.text, end="", flush=True) +def shard_and_load(repo): + # Get model path with everything but weight safetensors + model_path = download( + args.model, + allow_patterns=["*.json", "*.py", "tokenizer.model", "*.tiktoken", "*.txt"], + ) -rprint() -rprint("=" * 10) -rprint( - f"Prompt: {response.prompt_tokens} tokens, " - f"{response.prompt_tps:.3f} tokens-per-sec" -) -rprint( - f"Generation: {response.generation_tokens} tokens, " - f"{response.generation_tps:.3f} tokens-per-sec" -) -rprint(f"Peak memory: {response.peak_memory:.3f} GB") + # Lazy load and shard model + model, _ = load_model(model_path, lazy=True, strict=False) + + group = mx.distributed.init(backend="mpi") + rank = group.rank() + model.model.pipeline(group) + + # Figure out which files we need for the local shard + with open(model_path / "model.safetensors.index.json", "r") as fid: + weight_index = json.load(fid)["weight_map"] + + local_files = set() + for k, _ in tree_flatten(model.parameters()): + local_files.add(weight_index[k]) + + # Download weights for local shard + download(args.model, allow_patterns=local_files) + + tokenizer = load_tokenizer(model_path) + model, _ = load_model(model_path) + + # Synchronize processes before generation to avoid timeout if downloading + # model for the first time. + mx.eval(mx.distributed.all_sum(mx.array(1.0), stream=mx.cpu)) + return model, tokenizer + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="LLM pipelined inference example") + parser.add_argument( + "--model", + default="mlx-community/DeepSeek-R1-3bit", + help="HF repo or path to local model.", + ) + parser.add_argument( + "--prompt", + "-p", + default="Write a quicksort in C++.", + help="Message to be processed by the model ('-' reads from stdin)", + ) + parser.add_argument( + "--max-tokens", + "-m", + type=int, + default=256, + help="Maximum number of tokens to generate", + ) + args = parser.parse_args() + + group = mx.distributed.init(backend="mpi") + rank = group.rank() + + def rprint(*args, **kwargs): + if rank == 0: + print(*args, **kwargs) + + model, tokenizer = shard_and_load(args.model) + + messages = [{"role": "user", "content": args.prompt}] + prompt = tokenizer.apply_chat_template(messages, add_generation_prompt=True) + + for response in stream_generate( + model, tokenizer, prompt, max_tokens=args.max_tokens + ): + rprint(response.text, end="", flush=True) + + rprint() + rprint("=" * 10) + rprint( + f"Prompt: {response.prompt_tokens} tokens, " + f"{response.prompt_tps:.3f} tokens-per-sec" + ) + rprint( + f"Generation: {response.generation_tokens} tokens, " + f"{response.generation_tps:.3f} tokens-per-sec" + ) + rprint(f"Peak memory: {response.peak_memory:.3f} GB") diff --git a/llms/mlx_lm/models/deepseek_v2.py b/llms/mlx_lm/models/deepseek_v2.py index 9027da7e..3136ca7b 100644 --- a/llms/mlx_lm/models/deepseek_v2.py +++ b/llms/mlx_lm/models/deepseek_v2.py @@ -364,8 +364,29 @@ class DeepseekV2Model(nn.Module): DeepseekV2DecoderLayer(config, idx) for idx in range(config.num_hidden_layers) ] + self.start_idx = 0 + self.end_idx = len(self.layers) + self.num_layers = self.end_idx + self.norm = nn.RMSNorm(config.hidden_size, eps=config.rms_norm_eps) + self.pipeline_rank = 0 + self.pipeline_size = 1 + + def pipeline(self, group): + # Split layers in reverse so rank=0 gets the last layers and + # rank=pipeline_size-1 gets the first + self.pipeline_rank = group.rank() + self.pipeline_size = group.size() + layers_per_rank = ( + len(self.layers) + self.pipeline_size - 1 + ) // self.pipeline_size + self.start_idx = (self.pipeline_size - self.pipeline_rank - 1) * layers_per_rank + self.end_idx = self.start_idx + layers_per_rank + self.num_layers = layers_per_rank + self.layers = self.layers[: self.end_idx] + self.layers[: self.start_idx] = [None] * self.start_idx + def __call__( self, x: mx.array, @@ -374,14 +395,31 @@ class DeepseekV2Model(nn.Module): ) -> mx.array: h = self.embed_tokens(x) + pipeline_rank = self.pipeline_rank + pipeline_size = self.pipeline_size + # Hack to avoid time-outs during prompt-processing + dist_stream = mx.cpu if h.shape[1] > 1 else mx.gpu if mask is None: mask = create_attention_mask(h, cache) if cache is None: - cache = [None] * len(self.layers) + cache = [None] * self.num_layers - for layer, c in zip(self.layers, cache): - h = layer(h, mask, c) + # Receive from the previous process in the pipeline + if pipeline_rank < pipeline_size - 1: + h = mx.distributed.recv_like(h, (pipeline_rank + 1), stream=dist_stream) + + for i in range(self.num_layers): + h = self.layers[self.start_idx + i](h, mask, cache[i]) + + # Send to the next process in the pipeline + if pipeline_rank != 0: + h = mx.distributed.send( + h, (pipeline_rank - 1) % pipeline_size, stream=dist_stream + ) + + # Broadcast h while keeping it in the graph + h = mx.distributed.all_gather(h, stream=dist_stream)[: h.shape[0]] return self.norm(h) @@ -418,4 +456,4 @@ class Model(nn.Module): @property def layers(self): - return self.model.layers + return self.model.layers[self.model.start_idx : self.model.end_idx] diff --git a/llms/mlx_lm/models/deepseek_v3.py b/llms/mlx_lm/models/deepseek_v3.py index 96ce4f85..e6a0dd1e 100644 --- a/llms/mlx_lm/models/deepseek_v3.py +++ b/llms/mlx_lm/models/deepseek_v3.py @@ -381,6 +381,10 @@ class DeepseekV3Model(nn.Module): DeepseekV3DecoderLayer(config, idx) for idx in range(config.num_hidden_layers) ] + self.start_idx = 0 + self.end_idx = len(self.layers) + self.num_layers = self.end_idx + self.norm = nn.RMSNorm(config.hidden_size, eps=config.rms_norm_eps) self.pipeline_rank = 0 self.pipeline_size = 1 @@ -394,7 +398,11 @@ class DeepseekV3Model(nn.Module): len(self.layers) + self.pipeline_size - 1 ) // self.pipeline_size start = (self.pipeline_size - self.pipeline_rank - 1) * layers_per_rank - self.layers = self.layers[start : start + layers_per_rank] + self.start_idx = (self.pipeline_size - self.pipeline_rank - 1) * layers_per_rank + self.end_idx = self.start_idx + layers_per_rank + self.num_layers = layers_per_rank + self.layers = self.layers[: self.end_idx] + self.layers[: self.start_idx] = [None] * self.start_idx def __call__( self, @@ -412,15 +420,15 @@ class DeepseekV3Model(nn.Module): mask = create_attention_mask(h, cache) if cache is None: - cache = [None] * len(self.layers) + cache = [None] * self.num_layers # Receive from the previous process in the pipeline if pipeline_rank < pipeline_size - 1: h = mx.distributed.recv_like(h, (pipeline_rank + 1), stream=dist_stream) - for layer, c in zip(self.layers, cache): - h = layer(h, mask, c) + for i in range(self.num_layers): + h = self.layers[self.start_idx + i](h, mask, cache[i]) # Send to the next process in the pipeline if pipeline_rank != 0: @@ -468,4 +476,4 @@ class Model(nn.Module): @property def layers(self): - return self.model.layers + return self.model.layers[self.model.start_idx : self.model.end_idx] diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index 0150f1b7..b2e89a13 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -627,6 +627,7 @@ def load_config(model_path: Path) -> dict: def load_model( model_path: Path, lazy: bool = False, + strict: bool = True, model_config: dict = {}, get_model_classes: Callable[[dict], Tuple[Type[nn.Module], Type]] = _get_classes, ) -> nn.Module: @@ -638,6 +639,8 @@ def load_model( lazy (bool): If False eval the model parameters to make sure they are loaded in memory before returning, otherwise they will be loaded when needed. Default: ``False`` + strict (bool): Whether or not to raise an exception if weights don't + match. Default: ``True`` model_config (dict, optional): Optional configuration parameters for the model. Defaults to an empty dictionary. get_model_classes (Callable[[dict], Tuple[Type[nn.Module], Type]], optional): @@ -660,7 +663,7 @@ def load_model( # Try weight for back-compat weight_files = glob.glob(str(model_path / "weight*.safetensors")) - if not weight_files: + if not weight_files and strict: logging.error(f"No safetensors found in {model_path}") raise FileNotFoundError(f"No safetensors found in {model_path}") @@ -694,7 +697,7 @@ def load_model( class_predicate=class_predicate, ) - model.load_weights(list(weights.items())) + model.load_weights(list(weights.items()), strict=strict) if not lazy: mx.eval(model.parameters()) From d9924d08d15fbc145466f06489d106b219f12323 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Mon, 3 Feb 2025 09:55:24 -0800 Subject: [PATCH 136/188] Fix no validation in lora (#1241) --- llms/mlx_lm/tuner/trainer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/llms/mlx_lm/tuner/trainer.py b/llms/mlx_lm/tuner/trainer.py index 63ca58bb..bf84d066 100644 --- a/llms/mlx_lm/tuner/trainer.py +++ b/llms/mlx_lm/tuner/trainer.py @@ -140,8 +140,8 @@ def evaluate( loss: callable = default_loss, iterate_batches: callable = iterate_batches, ): - all_losses = 0 - ntokens = 0 + all_losses = mx.array(0.0) + ntokens = mx.array(0) index_iterator = iter(range(num_batches)) if num_batches != -1 else iter(int, 1) From 0989c073b056253e5fd59334d00919ee7a9accf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6kdeniz=20G=C3=BClmez?= <60228478+Goekdeniz-Guelmez@users.noreply.github.com> Date: Mon, 3 Feb 2025 22:36:08 +0100 Subject: [PATCH 137/188] Optimizations for mamba1 (#1213) * added mx.einsum() operations: before: 41.293 tokens-per-sec, after: 57.822 tokens-per-sec * Fused Operations in delta, B, C = ... :. Before: 57.822 tokens-per-sec, after: 83.890 tokens-per-sec * Pre-computing A_log. After: 83.890 tokens-per-sec, before: 85.848 tokens-per-sec * Update MambaBlock, Batched Input Processing, Improved Cache Handling, Pre-computed Constants, Cleaner State Management, Explicit Return Values:. Before: 82.442 tokens-per-sec, after: 129.130 tokens-per-sec. * cleaning up and adding apple copyright to helium modelfile * update Copyright to this year * nits + even faster --------- Co-authored-by: Awni Hannun --- llms/mlx_lm/models/helium.py | 2 ++ llms/mlx_lm/models/mamba.py | 64 +++++++++++++++++++++-------------- llms/mlx_lm/models/minicpm.py | 2 +- 3 files changed, 42 insertions(+), 26 deletions(-) diff --git a/llms/mlx_lm/models/helium.py b/llms/mlx_lm/models/helium.py index 6ca46a72..ff551bca 100644 --- a/llms/mlx_lm/models/helium.py +++ b/llms/mlx_lm/models/helium.py @@ -1,3 +1,5 @@ +# Copyright © 2025 Apple Inc. + from dataclasses import dataclass from typing import Any, Optional, Tuple diff --git a/llms/mlx_lm/models/mamba.py b/llms/mlx_lm/models/mamba.py index f2414660..93cc616e 100644 --- a/llms/mlx_lm/models/mamba.py +++ b/llms/mlx_lm/models/mamba.py @@ -1,4 +1,4 @@ -# Copyright © 2024 Apple Inc. +# Copyright © 2024-2025 Apple Inc. import math from dataclasses import dataclass @@ -123,17 +123,16 @@ class MambaBlock(nn.Module): self.intermediate_size, self.hidden_size, bias=args.use_bias ) - def ssm_step(self, x, state=None): - A = -mx.exp(self.A_log) + def ssm_step(self, x, A, state=None): D = self.D deltaBC = self.x_proj(x) - delta, B, C = mx.split( - deltaBC, - indices_or_sections=[ - self.time_step_rank, - self.time_step_rank + self.ssm_state_size, - ], - axis=-1, + delta, B, C = map( + self.mixer_norm if self.use_bcdt_rms else lambda x: x, + mx.split( + deltaBC, + [self.time_step_rank, self.time_step_rank + self.ssm_state_size], + axis=-1, + ), ) if self.use_bcdt_rms: delta, B, C = map(self.mixer_norm, (delta, B, C)) @@ -145,25 +144,40 @@ class MambaBlock(nn.Module): y = y + D * x return y, new_state - def __call__(self, x, cache): + def _process_sequence(self, x, conv_cache, state_cache): B, T, D = x.shape - if cache is None: - cache = [None, None] + xz = self.in_proj(x) + x, z = xz.split(indices_or_sections=2, axis=-1) + + conv_out, new_conv_cache = self.conv1d(x, conv_cache) + x = nn.silu(conv_out) + + A = -mx.exp(self.A_log) outputs = [] + current_state = state_cache + y = [] for t in range(T): - xt = x[:, t, :] - xz = self.in_proj(xt) - x_t, z_t = xz.split(indices_or_sections=2, axis=1) - conv_out, cache[0] = self.conv1d(mx.expand_dims(x_t, 1), cache[0]) - x_t = conv_out.squeeze(1) - x_t = nn.silu(x_t) - y_t, cache[1] = self.ssm_step(x_t, cache[1]) - z_t = nn.silu(z_t) - output_t = y_t * z_t - output_t = self.out_proj(output_t) - outputs.append(output_t) - output = mx.stack(outputs, axis=1) + y_t, current_state = self.ssm_step(x[:, t], A, current_state) + y.append(y_t) + y = mx.stack(y, axis=1) + z = self.out_proj(nn.silu(z) * y) + return z, (new_conv_cache, current_state) + + def __call__(self, x, cache): + if cache is None: + conv_cache, state_cache = None, None + else: + conv_cache, state_cache = cache[0], cache[1] + + output, (new_conv_cache, new_state_cache) = self._process_sequence( + x, conv_cache, state_cache + ) + + if isinstance(cache, MambaCache): + cache[0] = new_conv_cache + cache[1] = new_state_cache + return output diff --git a/llms/mlx_lm/models/minicpm.py b/llms/mlx_lm/models/minicpm.py index edddd583..7140c577 100644 --- a/llms/mlx_lm/models/minicpm.py +++ b/llms/mlx_lm/models/minicpm.py @@ -1,4 +1,4 @@ -# Copyright © 2023-2024 Apple Inc. +# Copyright © 2023-2025 Apple Inc. from dataclasses import dataclass from typing import Any, Dict, Optional, Tuple, Union From 21d0ab6e8abd3ecc549c7db526b2097fd9089352 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Mon, 3 Feb 2025 16:59:50 -0800 Subject: [PATCH 138/188] fix deepseek sharding (#1242) --- llms/mlx_lm/examples/pipeline_generate.py | 8 ++++++-- llms/mlx_lm/models/deepseek_v2.py | 1 + llms/mlx_lm/models/deepseek_v3.py | 3 +-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/llms/mlx_lm/examples/pipeline_generate.py b/llms/mlx_lm/examples/pipeline_generate.py index d170405a..1e4fb445 100644 --- a/llms/mlx_lm/examples/pipeline_generate.py +++ b/llms/mlx_lm/examples/pipeline_generate.py @@ -44,7 +44,8 @@ def shard_and_load(repo): allow_patterns=["*.json", "*.py", "tokenizer.model", "*.tiktoken", "*.txt"], ) - # Lazy load and shard model + # Lazy load and shard model to figure out + # which weights we need model, _ = load_model(model_path, lazy=True, strict=False) group = mx.distributed.init(backend="mpi") @@ -62,8 +63,11 @@ def shard_and_load(repo): # Download weights for local shard download(args.model, allow_patterns=local_files) + # Load and shard the model, and load the weights tokenizer = load_tokenizer(model_path) - model, _ = load_model(model_path) + model, _ = load_model(model_path, lazy=True, strict=False) + model.model.pipeline(group) + mx.eval(model.parameters()) # Synchronize processes before generation to avoid timeout if downloading # model for the first time. diff --git a/llms/mlx_lm/models/deepseek_v2.py b/llms/mlx_lm/models/deepseek_v2.py index 3136ca7b..3581fcbe 100644 --- a/llms/mlx_lm/models/deepseek_v2.py +++ b/llms/mlx_lm/models/deepseek_v2.py @@ -386,6 +386,7 @@ class DeepseekV2Model(nn.Module): self.num_layers = layers_per_rank self.layers = self.layers[: self.end_idx] self.layers[: self.start_idx] = [None] * self.start_idx + self.num_layers = len(self.layers) - self.start_idx def __call__( self, diff --git a/llms/mlx_lm/models/deepseek_v3.py b/llms/mlx_lm/models/deepseek_v3.py index e6a0dd1e..69ee1be0 100644 --- a/llms/mlx_lm/models/deepseek_v3.py +++ b/llms/mlx_lm/models/deepseek_v3.py @@ -397,12 +397,11 @@ class DeepseekV3Model(nn.Module): layers_per_rank = ( len(self.layers) + self.pipeline_size - 1 ) // self.pipeline_size - start = (self.pipeline_size - self.pipeline_rank - 1) * layers_per_rank self.start_idx = (self.pipeline_size - self.pipeline_rank - 1) * layers_per_rank self.end_idx = self.start_idx + layers_per_rank - self.num_layers = layers_per_rank self.layers = self.layers[: self.end_idx] self.layers[: self.start_idx] = [None] * self.start_idx + self.num_layers = len(self.layers) - self.start_idx def __call__( self, From e2e5478da5cf5542a3893ecce2eac5d63dc6ca7f Mon Sep 17 00:00:00 2001 From: Pedro Cuenca Date: Tue, 4 Feb 2025 20:52:32 +0100 Subject: [PATCH 139/188] READMEs: fix typo in link, minor update. (#1246) --- README.md | 2 +- llms/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 88888ad0..e47bd598 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Some more useful examples are listed below. ### Hugging Face -Note: You can now directly download a few converted checkpoints from the [MLX +You can directly use or download converted checkpoints from the [MLX Community](https://huggingface.co/mlx-community) organization on Hugging Face. We encourage you to join the community and [contribute new models](https://github.com/ml-explore/mlx-examples/issues/155). diff --git a/llms/README.md b/llms/README.md index e943ed69..4f7451c1 100644 --- a/llms/README.md +++ b/llms/README.md @@ -164,7 +164,7 @@ mlx_lm.convert \ ``` Models can also be converted and quantized directly in the -[mlx-my-repo]https://huggingface.co/spaces/mlx-community/mlx-my-repo) Hugging +[mlx-my-repo](https://huggingface.co/spaces/mlx-community/mlx-my-repo) Hugging Face Space. ### Long Prompts and Generations From 747c08e202c37576f90db5b778d46527130df396 Mon Sep 17 00:00:00 2001 From: Nripesh Niketan <86844847+NripeshN@users.noreply.github.com> Date: Thu, 6 Feb 2025 17:06:31 +0000 Subject: [PATCH 140/188] Chore: pre-commit bump (#1253) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 012a96ad..7a1bf9bf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,10 +1,10 @@ repos: - repo: https://github.com/psf/black-pre-commit-mirror - rev: 24.8.0 + rev: 25.1.0 hooks: - id: black - repo: https://github.com/pycqa/isort - rev: 5.13.2 + rev: 6.0.0 hooks: - id: isort args: From 52c41b5b5abfdd4ee1c35bd362162b1dc7a62138 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Thu, 6 Feb 2025 11:10:58 -0800 Subject: [PATCH 141/188] Fix prompt cache for models without chat template (#1250) * fix deepseek sharding (#1242) * fix prompt cache with no chat template --- llms/mlx_lm/cache_prompt.py | 2 +- llms/mlx_lm/generate.py | 2 +- llms/mlx_lm/models/deepseek_v2.py | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/llms/mlx_lm/cache_prompt.py b/llms/mlx_lm/cache_prompt.py index c18f1bae..fff64f78 100644 --- a/llms/mlx_lm/cache_prompt.py +++ b/llms/mlx_lm/cache_prompt.py @@ -152,7 +152,7 @@ def main(): print("Saving...") metadata = {} metadata["model"] = args.model - metadata["chat_template"] = tokenizer.chat_template + metadata["chat_template"] = json.dumps(tokenizer.chat_template) metadata["tokenizer_config"] = json.dumps(tokenizer_config) save_prompt_cache(args.prompt_cache_file, cache, metadata) diff --git a/llms/mlx_lm/generate.py b/llms/mlx_lm/generate.py index 0d286c75..e7994750 100644 --- a/llms/mlx_lm/generate.py +++ b/llms/mlx_lm/generate.py @@ -199,7 +199,7 @@ def main(): if tokenizer.chat_template is None: tokenizer.chat_template = tokenizer.default_chat_template elif using_cache: - tokenizer.chat_template = metadata["chat_template"] + tokenizer.chat_template = json.loads(metadata["chat_template"]) prompt = args.prompt.replace("\\n", "\n").replace("\\t", "\t") prompt = sys.stdin.read() if prompt == "-" else prompt diff --git a/llms/mlx_lm/models/deepseek_v2.py b/llms/mlx_lm/models/deepseek_v2.py index 3581fcbe..f22b2e3f 100644 --- a/llms/mlx_lm/models/deepseek_v2.py +++ b/llms/mlx_lm/models/deepseek_v2.py @@ -282,12 +282,12 @@ class MoEGate(nn.Module): if self.topk_method == "group_limited_greedy": bsz, seq_len = x.shape[:2] scores = scores.reshape(bsz, seq_len, self.n_group, -1) - group_scores = scores.max(axis=-1) + group_scores = scores.max(axis=-1, keepdims=True) k = self.n_group - self.topk_group - group_idx = mx.argpartition(group_scores, kth=k - 1, axis=-1)[..., :k] - batch_idx = mx.expand_dims(mx.arange(bsz), (1, 2)) - seq_idx = mx.expand_dims(mx.arange(seq_len), (0, 2)) - scores[batch_idx, seq_idx, group_idx] = 0.0 + group_idx = mx.argpartition(group_scores, kth=k - 1, axis=-2)[..., :k, :] + scores = mx.put_along_axis( + scores, group_idx, mx.array(0.0, scores.dtype), axis=-2 + ) scores = scores.reshape(bsz, seq_len, -1) k = self.top_k From 6120a5f3763788f2444e082875b917925b80afa5 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Fri, 7 Feb 2025 10:24:57 -0800 Subject: [PATCH 142/188] Faster DSv2/3 expert score computation (#1257) * fix deepseek sharding (#1242) * compile and use put along axis in deep seek routing function --- llms/mlx_lm/models/deepseek_v3.py | 68 +++++++++++++++++++------------ 1 file changed, 42 insertions(+), 26 deletions(-) diff --git a/llms/mlx_lm/models/deepseek_v3.py b/llms/mlx_lm/models/deepseek_v3.py index 69ee1be0..2df93d9f 100644 --- a/llms/mlx_lm/models/deepseek_v3.py +++ b/llms/mlx_lm/models/deepseek_v3.py @@ -271,6 +271,38 @@ class DeepseekV3MLP(nn.Module): return down_proj +@mx.compile +def group_expert_select( + gates, + e_score_correction_bias, + top_k, + n_group, + topk_group, + routed_scaling_factor, + norm_topk_prob, +): + + k = top_k + scores = mx.sigmoid(gates.astype(mx.float32)) + scores = scores + e_score_correction_bias + scores = mx.unflatten(scores, axis=-1, shape=(n_group, -1)) + group_scores = mx.topk(scores, 2, axis=-1).sum(axis=-1, keepdims=True) + k = n_group - topk_group + group_idx = mx.argpartition(group_scores, kth=k - 1, axis=-2)[..., :k, :] + scores = mx.put_along_axis(scores, group_idx, mx.array(0.0), axis=-2) + scores = mx.flatten(scores, -2, -1) + + k = top_k + inds = mx.argpartition(-scores, kth=k - 1, axis=-1)[..., :k] + scores = mx.take_along_axis(scores, inds, axis=-1) + if top_k > 1 and norm_topk_prob: + denominator = scores.sum(axis=-1, keepdims=True) + 1e-20 + scores = scores / denominator + scores = scores * routed_scaling_factor + + return inds, scores + + class MoEGate(nn.Module): def __init__(self, config: ModelArgs): super().__init__() @@ -279,38 +311,22 @@ class MoEGate(nn.Module): self.norm_topk_prob = config.norm_topk_prob self.n_routed_experts = config.n_routed_experts self.routed_scaling_factor = config.routed_scaling_factor - self.topk_method = config.topk_method self.n_group = config.n_group self.topk_group = config.topk_group self.weight = mx.zeros((self.n_routed_experts, config.hidden_size)) self.e_score_correction_bias = mx.zeros((self.n_routed_experts,)) + assert config.topk_method == "noaux_tc", "Unsupported topk method." def __call__(self, x): - gates = x @ self.weight.T - - scores = mx.sigmoid(gates.astype(mx.float32)) - - assert self.topk_method == "noaux_tc", "Unsupported topk method." - bsz, seq_len = x.shape[:2] - scores = scores + self.e_score_correction_bias - scores = scores.reshape(bsz, seq_len, self.n_group, -1) - group_scores = mx.topk(scores, 2, axis=-1).sum(axis=-1) - k = self.n_group - self.topk_group - group_idx = mx.argpartition(group_scores, kth=k - 1, axis=-1)[..., :k] - batch_idx = mx.expand_dims(mx.arange(bsz), (1, 2)) - seq_idx = mx.expand_dims(mx.arange(seq_len), (0, 2)) - scores[batch_idx, seq_idx, group_idx] = 0.0 - scores = scores.reshape(bsz, seq_len, -1) - - k = self.top_k - inds = mx.argpartition(-scores, kth=k - 1, axis=-1)[..., :k] - scores = mx.take_along_axis(scores, inds, axis=-1) - if self.top_k > 1 and self.norm_topk_prob: - denominator = scores.sum(axis=-1, keepdims=True) + 1e-20 - scores = scores / denominator - scores = scores * self.routed_scaling_factor - - return inds, scores + return group_expert_select( + x @ self.weight.T, + self.e_score_correction_bias, + self.top_k, + self.n_group, + self.topk_group, + self.routed_scaling_factor, + self.norm_topk_prob, + ) class DeepseekV3MoE(nn.Module): From 31611b62d73448cab451f7d5cf72d33a942ae99b Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Sat, 8 Feb 2025 15:46:15 -0800 Subject: [PATCH 143/188] Add IBM granite model (#1265) * add granite * add thinking option --- llms/mlx_lm/generate.py | 17 ++- llms/mlx_lm/models/granite.py | 195 ++++++++++++++++++++++++++++++++++ llms/mlx_lm/tuner/utils.py | 1 + 3 files changed, 211 insertions(+), 2 deletions(-) create mode 100644 llms/mlx_lm/models/granite.py diff --git a/llms/mlx_lm/generate.py b/llms/mlx_lm/generate.py index e7994750..d8f97e5e 100644 --- a/llms/mlx_lm/generate.py +++ b/llms/mlx_lm/generate.py @@ -93,6 +93,12 @@ def setup_arg_parser(): action="store_true", help="Use the default chat template", ) + parser.add_argument( + "--chat-template-config", + help="Additional config for `apply_chat_template`. Should be a dictionary of" + " string keys to values represented as a JSON decodable string.", + default=None, + ) parser.add_argument( "--verbose", type=str2bool, @@ -149,7 +155,6 @@ def setup_arg_parser(): def main(): parser = setup_arg_parser() args = parser.parse_args() - mx.random.seed(args.seed) # Load the prompt cache and metadata if a cache file is provided @@ -195,6 +200,10 @@ def main(): for eos_token in args.extra_eos_token: tokenizer.add_eos_token(eos_token) + template_kwargs = {} + if args.chat_template_config is not None: + template_kwargs = json.loads(args.chat_template_config) + if args.use_default_chat_template: if tokenizer.chat_template is None: tokenizer.chat_template = tokenizer.default_chat_template @@ -209,8 +218,12 @@ def main(): else: messages = [] messages.append({"role": "user", "content": prompt}) + prompt = tokenizer.apply_chat_template( - messages, tokenize=False, add_generation_prompt=True + messages, + tokenize=False, + add_generation_prompt=True, + **template_kwargs, ) # Treat the prompt as a suffix assuming that the prefix is in the diff --git a/llms/mlx_lm/models/granite.py b/llms/mlx_lm/models/granite.py new file mode 100644 index 00000000..43597d99 --- /dev/null +++ b/llms/mlx_lm/models/granite.py @@ -0,0 +1,195 @@ +# Copyright © 2023-2024 Apple Inc. + +from dataclasses import dataclass +from typing import Any, Dict, Optional, Union + +import mlx.core as mx +import mlx.nn as nn + +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention +from .rope_utils import initialize_rope + + +@dataclass +class ModelArgs(BaseModelArgs): + model_type: str + hidden_size: int + num_hidden_layers: int + intermediate_size: int + num_attention_heads: int + rms_norm_eps: float + vocab_size: int + logits_scaling: float + attention_multiplier: float + embedding_multiplier: float + residual_multiplier: float + max_position_embeddings: int + num_key_value_heads: int + attention_bias: bool + mlp_bias: bool + rope_theta: float + rope_scaling: Optional[Dict[str, Union[float, str]]] = None + tie_word_embeddings: bool = True + + +class Attention(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + + dim = args.hidden_size + self.n_heads = n_heads = args.num_attention_heads + self.n_kv_heads = n_kv_heads = args.num_key_value_heads + + self.head_dim = head_dim = args.hidden_size // n_heads + + self.scale = args.attention_multiplier + attention_bias = args.attention_bias + self.q_proj = nn.Linear(dim, n_heads * head_dim, bias=attention_bias) + self.k_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=attention_bias) + self.v_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=attention_bias) + self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=attention_bias) + + self.rope = initialize_rope( + self.head_dim, + args.rope_theta, + False, + args.rope_scaling, + args.max_position_embeddings, + ) + + def __call__( + self, + x: mx.array, + mask: Optional[mx.array] = None, + cache: Optional[Any] = None, + ) -> mx.array: + B, L, D = x.shape + + queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x) + + # Prepare the queries, keys and values for the attention computation + queries = queries.reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) + keys = keys.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) + values = values.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) + + if cache is not None: + queries = self.rope(queries, offset=cache.offset) + keys = self.rope(keys, offset=cache.offset) + keys, values = cache.update_and_fetch(keys, values) + else: + queries = self.rope(queries) + keys = self.rope(keys) + + output = scaled_dot_product_attention( + queries, keys, values, cache=cache, scale=self.scale, mask=mask + ) + + output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) + return self.o_proj(output) + + +class MLP(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + + dim = args.hidden_size + hidden_dim = args.intermediate_size + if hasattr(args, "mlp_bias"): + mlp_bias = args.mlp_bias + else: + mlp_bias = False + + self.gate_proj = nn.Linear(dim, hidden_dim, bias=mlp_bias) + self.down_proj = nn.Linear(hidden_dim, dim, bias=mlp_bias) + self.up_proj = nn.Linear(dim, hidden_dim, bias=mlp_bias) + + def __call__(self, x) -> mx.array: + return self.down_proj(nn.silu(self.gate_proj(x)) * self.up_proj(x)) + + +class TransformerBlock(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.num_attention_heads = args.num_attention_heads + self.hidden_size = args.hidden_size + self.self_attn = Attention(args) + self.mlp = MLP(args) + self.input_layernorm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) + self.post_attention_layernorm = nn.RMSNorm( + args.hidden_size, eps=args.rms_norm_eps + ) + self.residual_multiplier = args.residual_multiplier + + def __call__( + self, + x: mx.array, + mask: Optional[mx.array] = None, + cache: Optional[Any] = None, + ) -> mx.array: + r = self.self_attn(self.input_layernorm(x), mask, cache) + h = x + r * self.residual_multiplier + r = self.mlp(self.post_attention_layernorm(h)) + out = h + r * self.residual_multiplier + return out + + +class GraniteModel(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.args = args + self.vocab_size = args.vocab_size + self.num_hidden_layers = args.num_hidden_layers + assert self.vocab_size > 0 + self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size) + self.layers = [ + TransformerBlock(args=args) for _ in range(args.num_hidden_layers) + ] + self.norm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) + self.embedding_multiplier = args.embedding_multiplier + + def __call__( + self, + inputs: mx.array, + mask: mx.array = None, + cache=None, + ): + h = self.embed_tokens(inputs) * self.embedding_multiplier + + if mask is None: + mask = create_attention_mask(h, cache) + + if cache is None: + cache = [None] * len(self.layers) + + for layer, c in zip(self.layers, cache): + h = layer(h, mask, cache=c) + + return self.norm(h) + + +class Model(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.args = args + self.model_type = args.model_type + self.model = GraniteModel(args) + if not args.tie_word_embeddings: + self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=False) + self.logits_scaling = args.logits_scaling + + def __call__( + self, + inputs: mx.array, + mask: mx.array = None, + cache=None, + ): + out = self.model(inputs, mask, cache) + if self.args.tie_word_embeddings: + out = self.model.embed_tokens.as_linear(out) + else: + out = self.lm_head(out) + return out / self.logits_scaling + + @property + def layers(self): + return self.model.layers diff --git a/llms/mlx_lm/tuner/utils.py b/llms/mlx_lm/tuner/utils.py index c0e52731..d86e01dd 100644 --- a/llms/mlx_lm/tuner/utils.py +++ b/llms/mlx_lm/tuner/utils.py @@ -94,6 +94,7 @@ def linear_to_lora_layers( "phimoe", "gemma", "gemma2", + "granite", "helium", "starcoder2", "cohere", From 1503bd4f550886092b156ec897e633b448bd78bc Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Sat, 8 Feb 2025 15:46:47 -0800 Subject: [PATCH 144/188] support hunyuan 7b (#1263) --- llms/mlx_lm/models/hunyuan.py | 37 ++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/llms/mlx_lm/models/hunyuan.py b/llms/mlx_lm/models/hunyuan.py index f9dc5652..122cebda 100644 --- a/llms/mlx_lm/models/hunyuan.py +++ b/llms/mlx_lm/models/hunyuan.py @@ -76,7 +76,6 @@ class Attention(nn.Module): head_dim = args.hidden_size // n_heads self.scale = head_dim**-0.5 - self.q_proj = nn.Linear(dim, n_heads * head_dim, bias=args.attention_bias) if kv_proj: self.k_proj = nn.Linear( @@ -107,7 +106,6 @@ class Attention(nn.Module): B, L, D = x.shape queries = self.q_proj(x) - if kv_states is None: keys, values = self.k_proj(x), self.v_proj(x) kv_states = keys, values @@ -198,7 +196,10 @@ class DecoderLayer(nn.Module): super().__init__() self.hidden_size = args.hidden_size self.self_attn = Attention(kv_proj, args) - self.mlp = MoeBlock(args) + if args.num_experts == 1: + self.mlp = MLP(args.hidden_size, args.intermediate_size) + else: + self.mlp = MoeBlock(args) self.input_layernorm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) self.post_attention_layernorm = nn.RMSNorm( @@ -231,7 +232,10 @@ class HunYuanModel(nn.Module): assert self.vocab_size > 0 self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size) self.layers = [ - DecoderLayer(args=args, kv_proj=(i % args.cla_share_factor) == 0) + DecoderLayer( + args=args, + kv_proj=(not args.use_cla) or (i % args.cla_share_factor) == 0, + ) for i in range(args.num_hidden_layers) ] self.norm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) @@ -251,7 +255,7 @@ class HunYuanModel(nn.Module): cache = [None] * len(self.layers) for i, (layer, c) in enumerate(zip(self.layers, cache)): - if i % self.args.cla_share_factor == 0: + if (not self.args.use_cla) or i % self.args.cla_share_factor == 0: shared_kv_states = None h, shared_kv_states = layer(h, mask, c, shared_kv_states) @@ -275,6 +279,29 @@ class Model(nn.Module): return self.model.embed_tokens.as_linear(out) def sanitize(self, weights): + + if "model.layers.0.mlp.gate_and_up_proj.weight" in weights: + new_weights = {} + D = self.args.hidden_size + n_kv_heads = self.args.num_key_value_heads + n_kv_groups = self.args.num_attention_heads // n_kv_heads + head_dim = D // self.args.num_attention_heads + for k, v in weights.items(): + if "qkv_proj" in k: + v = v.reshape(n_kv_heads, n_kv_groups + 2, head_dim, -1) + splits = v.split([n_kv_groups, n_kv_groups + 1], axis=1) + for k_up, v_new in zip(["q_proj", "k_proj", "v_proj"], splits): + k_new = k.replace("qkv_proj", k_up) + new_weights[k_new] = mx.flatten(v_new, 0, 2) + elif "gate_and_up_proj" in k: + splits = v.split(2, axis=0) + for k_up, v_new in zip(["up_proj", "gate_proj"], splits): + k_new = k.replace("gate_and_up_proj", k_up) + new_weights[k_new] = v_new + else: + new_weights[k] = v + weights = new_weights + if "model.layers.0.mlp.experts.0.up_proj.weight" not in weights: return weights for l in range(self.args.num_hidden_layers): From f58c7de9017b54b044703f88787e6c679db9ec7e Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Sat, 8 Feb 2025 15:47:00 -0800 Subject: [PATCH 145/188] Some improvements to speedup alignment computation in MLX Whisper (#1259) * some improvements to speedup alignment computation in MLX Whisper * fix alignment --- whisper/mlx_whisper/timing.py | 9 ++++----- whisper/mlx_whisper/whisper.py | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/whisper/mlx_whisper/timing.py b/whisper/mlx_whisper/timing.py index 04915deb..07b81186 100644 --- a/whisper/mlx_whisper/timing.py +++ b/whisper/mlx_whisper/timing.py @@ -134,9 +134,7 @@ def find_alignment( logits, cross_qk = model.forward_with_cross_qk(mel[None, :], tokens[None, :]) # consider only the logits associated with predicting text sampled_logits = logits[0][len(tokenizer.sot_sequence) : -2, : tokenizer.eot] - token_probs = mx.softmax(sampled_logits.astype(mx.float32), axis=-1).astype( - sampled_logits.dtype - ) + token_probs = mx.softmax(sampled_logits, precise=True, axis=-1) text_token_probs = mx.take_along_axis( token_probs, mx.array(text_tokens)[:, None], axis=1 ).squeeze(1) @@ -144,10 +142,11 @@ def find_alignment( # heads * tokens * frames weights = mx.stack( - [cross_qk[_l.item()][0, _h.item()] for _l, _h in model.alignment_heads] + [cross_qk[_l][0, _h] for _l, _h in model.alignment_heads.tolist()] ) weights = weights[:, :, : num_frames // 2] - weights = mx.softmax(weights * qk_scale, axis=-1) + weights = mx.softmax(weights * qk_scale, axis=-1, precise=True) + weights = weights.astype(mx.float32) mean = mx.mean(weights, axis=-2, keepdims=True) std = mx.var(weights, axis=-2, keepdims=True, ddof=0).sqrt() weights = (weights - mean) / std diff --git a/whisper/mlx_whisper/whisper.py b/whisper/mlx_whisper/whisper.py index 1c2b390e..5c85195c 100644 --- a/whisper/mlx_whisper/whisper.py +++ b/whisper/mlx_whisper/whisper.py @@ -84,7 +84,7 @@ class MultiHeadAttention(nn.Module): w = mx.softmax(qk, axis=-1, precise=True) out = (w @ v).transpose(0, 2, 1, 3) out = out.reshape(n_batch, n_ctx, n_state) - return out, qk.astype(mx.float32) + return out, qk class ResidualAttentionBlock(nn.Module): From 1ced1b00ca9c2457fcbf0e54ffcffe58f53fb4fd Mon Sep 17 00:00:00 2001 From: Sri Harsha Pamu Date: Sun, 9 Feb 2025 11:39:11 -0800 Subject: [PATCH 146/188] rm temp argument (#1267) --- llms/mlx_lm/examples/chat.py | 1 - 1 file changed, 1 deletion(-) diff --git a/llms/mlx_lm/examples/chat.py b/llms/mlx_lm/examples/chat.py index 4a7020f1..dcd90b67 100644 --- a/llms/mlx_lm/examples/chat.py +++ b/llms/mlx_lm/examples/chat.py @@ -23,7 +23,6 @@ response = generate( tokenizer, prompt=prompt, verbose=True, - temp=0.0, prompt_cache=prompt_cache, ) From 5865899c81d35ea48c6b69071d7fe61a46880d30 Mon Sep 17 00:00:00 2001 From: Chime Ogbuji Date: Sun, 9 Feb 2025 23:12:34 -0500 Subject: [PATCH 147/188] Completion only fine-tuning of instruction models with collections of HF datasets (#1103) - Optional completion only fine-tuning with `--mask-prompt` - Collections of Hugging Face datasets --------- Co-authored-by: Awni Hannun --- llms/mlx_lm/LORA.md | 26 ++++- llms/mlx_lm/lora.py | 9 ++ llms/mlx_lm/tokenizer_utils.py | 6 ++ llms/mlx_lm/tuner/datasets.py | 186 +++++++++++++++++++++------------ llms/mlx_lm/tuner/trainer.py | 32 ++++-- llms/tests/test_datsets.py | 25 +++-- 6 files changed, 199 insertions(+), 85 deletions(-) diff --git a/llms/mlx_lm/LORA.md b/llms/mlx_lm/LORA.md index 9eac9d7f..e863abc4 100644 --- a/llms/mlx_lm/LORA.md +++ b/llms/mlx_lm/LORA.md @@ -76,6 +76,14 @@ You can specify the output location with `--adapter-path`. You can resume fine-tuning with an existing adapter with `--resume-adapter-file `. +#### Prompt Masking + +The default training computes a loss for every token in the sample. You can +ignore the prompt and compute loss for just the completion by passing +`--mask-prompt`. Note this is only supported for `chat` and `completion` +datasets. For `chat` datasets the final message in the message list is +considered the completion. See the [dataset section](#Data) for more details. + ### Evaluate To compute test set perplexity use: @@ -290,11 +298,27 @@ hf_dataset: - Use `prompt_feature` and `completion_feature` to specify keys for a `completions` dataset. Use `text_feature` to specify the key for a `text` - dataset. + dataset. Use `chat_feature` to specify the key for a chat dataset. - To specify the train, valid, or test splits, set the corresponding `{train,valid,test}_split` argument. +You can specify a list of Hugging Face datasets with a list of records each +with the same structure as above. For example: + +```yaml +hf_dataset: + - name: "Open-Orca/OpenOrca" + train_split: "train[:90%]" + valid_split: "train[-10%:]" + prompt_feature: "question" + completion_feature: "response" + - name: "trl-lib/ultrafeedback_binarized" + train_split: "train[:90%]" + valid_split: "train[-10%:]" + chat_feature: "chosen" +``` + - Arguments specified in `config` will be passed as keyword arguments to [`datasets.load_dataset`](https://huggingface.co/docs/datasets/v2.20.0/en/package_reference/loading_methods#datasets.load_dataset). diff --git a/llms/mlx_lm/lora.py b/llms/mlx_lm/lora.py index 43f508c3..abc5dfa9 100644 --- a/llms/mlx_lm/lora.py +++ b/llms/mlx_lm/lora.py @@ -94,6 +94,14 @@ def build_parser(): choices=["lora", "dora", "full"], help="Type of fine-tuning to perform: lora, dora, or full.", ) + + parser.add_argument( + "--mask-prompt", + action="store_true", + help="Mask the prompt in the loss when training", + default=False, + ) + parser.add_argument( "--num-layers", type=int, @@ -219,6 +227,7 @@ def train_model( build_schedule(args.lr_schedule) if args.lr_schedule else args.learning_rate ) ) + # Train model train( model=model, diff --git a/llms/mlx_lm/tokenizer_utils.py b/llms/mlx_lm/tokenizer_utils.py index 1b5bdd77..de9d5324 100644 --- a/llms/mlx_lm/tokenizer_utils.py +++ b/llms/mlx_lm/tokenizer_utils.py @@ -1,5 +1,6 @@ import json from functools import partial +from typing import List from transformers import AutoTokenizer @@ -368,3 +369,8 @@ def load_tokenizer(model_path, tokenizer_config_extra={}, eos_token_ids=None): detokenizer_class, eos_token_ids=eos_token_ids, ) + + +def no_bos_or_eos(sequence: List, bos: int, eos: int) -> List: + removed_bos = sequence if sequence[0] != bos else sequence[1:] + return removed_bos[:-1] if removed_bos[-1] == eos else removed_bos diff --git a/llms/mlx_lm/tuner/datasets.py b/llms/mlx_lm/tuner/datasets.py index 377e7cae..a6f3bd29 100644 --- a/llms/mlx_lm/tuner/datasets.py +++ b/llms/mlx_lm/tuner/datasets.py @@ -1,6 +1,8 @@ +import itertools import json +import types from pathlib import Path -from typing import Dict, List, Optional +from typing import Any, Dict, List, Optional from transformers import PreTrainedTokenizer @@ -34,14 +36,24 @@ class ChatDataset: https://platform.openai.com/docs/guides/fine-tuning/example-format """ - def __init__(self, data: List[Dict[str, str]], tokenizer: PreTrainedTokenizer): - self._data = [ - tokenizer.apply_chat_template( - d["messages"], - tools=d.get("tools", None), - ) - for d in data - ] + def __init__( + self, + data: List[Dict[str, str]], + tokenizer: PreTrainedTokenizer, + chat_key: str = "messages", + mask_prompt: bool = False, + ): + self._data = [] + for d in data: + messages = d[chat_key] + tools = d.get("tools", None) + tokens = tokenizer.apply_chat_template(messages, tools=tools) + if mask_prompt: + messages = messages[:-1] + offset = len(tokenizer.apply_chat_template(messages, tools=tools)) + self._data.append((tokens, offset)) + else: + self._data.append(tokens) def __getitem__(self, idx: int): return self._data[idx] @@ -63,16 +75,36 @@ class CompletionsDataset: tokenizer: PreTrainedTokenizer, prompt_key: str, completion_key: str, + mask_prompt: bool, ): - self._data = [ - tokenizer.apply_chat_template( + self._data = [] + for d in data: + tokens = tokenizer.apply_chat_template( [ {"role": "user", "content": d[prompt_key]}, {"role": "assistant", "content": d[completion_key]}, ], ) - for d in data - ] + if mask_prompt: + offset = len( + tokenizer.apply_chat_template( + [{"role": "user", "content": d[prompt_key]}] + ) + ) + self._data.append((tokens, offset)) + else: + self._data.append(tokens) + + def __getitem__(self, idx: int): + return self._data[idx] + + def __len__(self): + return len(self._data) + + +class ConcatenatedDataset: + def __init__(self, data: List[Any]): + self._data = list(itertools.chain(*data)) def __getitem__(self, idx: int): return self._data[idx] @@ -84,18 +116,26 @@ class CompletionsDataset: def create_dataset( data, tokenizer: PreTrainedTokenizer, - prompt_feature: Optional[str] = None, - completion_feature: Optional[str] = None, + config, ): - prompt_feature = prompt_feature or "prompt" - completion_feature = completion_feature or "completion" + mask_prompt = getattr(config, "mask_prompt", False) + prompt_feature = getattr(config, "prompt_feature", "prompt") + text_feature = getattr(config, "text_feature", "text") + completion_feature = getattr(config, "completion_feature", "completion") + chat_feature = getattr(config, "chat_feature", "messages") sample = data[0] - if "messages" in sample: - return ChatDataset(data, tokenizer) - elif prompt_feature in sample and completion_feature in sample: - return CompletionsDataset(data, tokenizer, prompt_feature, completion_feature) - elif "text" in sample: - return Dataset(data, tokenizer) + if prompt_feature in sample and completion_feature in sample: + return CompletionsDataset( + data, tokenizer, prompt_feature, completion_feature, mask_prompt + ) + elif chat_feature in sample: + return ChatDataset( + data, tokenizer, chat_key=chat_feature, mask_prompt=mask_prompt + ) + elif text_feature in sample: + if mask_prompt: + raise ValueError("Prompt masking not supported for text dataset.") + return Dataset(data, tokenizer, text_key=text_feature) else: raise ValueError( "Unsupported data format, check the supported formats here:\n" @@ -106,15 +146,14 @@ def create_dataset( def load_local_dataset( data_path: Path, tokenizer: PreTrainedTokenizer, - prompt_feature: Optional[str] = None, - completion_feature: Optional[str] = None, + config, ): def load_subset(path): if not path.exists(): return [] with open(path, "r") as fid: data = [json.loads(l) for l in fid] - return create_dataset(data, tokenizer, prompt_feature, completion_feature) + return create_dataset(data, tokenizer, config) names = ("train", "valid", "test") train, valid, test = [load_subset(data_path / f"{n}.jsonl") for n in names] @@ -124,8 +163,7 @@ def load_local_dataset( def load_hf_dataset( data_id: str, tokenizer: PreTrainedTokenizer, - prompt_feature: Optional[str] = None, - completion_feature: Optional[str] = None, + config, ): from datasets import exceptions, load_dataset @@ -136,9 +174,7 @@ def load_hf_dataset( train, valid, test = [ ( - create_dataset( - dataset[n], tokenizer, prompt_feature, completion_feature - ) + create_dataset(dataset[n], tokenizer, config) if n in dataset.keys() else [] ) @@ -154,42 +190,61 @@ def load_hf_dataset( def load_custom_hf_dataset(args, tokenizer: PreTrainedTokenizer): import datasets - hf_args = args.hf_dataset - dataset_name = hf_args["name"] - print(f"Loading Hugging Face dataset {dataset_name}.") - text_feature = hf_args.get("text_feature") - prompt_feature = hf_args.get("prompt_feature") - completion_feature = hf_args.get("completion_feature") - - def create_hf_dataset(split: str = None): + def create_hf_dataset(dataset_name, config, split, hf_config): ds = datasets.load_dataset( dataset_name, split=split, - **hf_args.get("config", {}), + **hf_config, ) - if prompt_feature and completion_feature: - return CompletionsDataset(ds, tokenizer, prompt_feature, completion_feature) - elif text_feature: - return Dataset(ds, tokenizer, text_key=text_feature) - else: - raise ValueError( - "Specify either a prompt and completion feature or a text " - "feature for the Hugging Face dataset." + return create_dataset(ds, tokenizer, config) + + dataset_collection = args.hf_dataset + if isinstance(dataset_collection, dict): + dataset_collection = [dataset_collection] + + collection = [] + for ds in dataset_collection: + ds_name = ds["name"] + print(f"Loading Hugging Face dataset {ds_name}.") + ds["mask_prompt"] = getattr(args, "mask_prompt", False) + config = types.SimpleNamespace(**ds) + hf_config = ds.get("config", {}) + if args.train: + train_split = ds.get("train_split", "train[:80%]") + valid_split = ds.get("valid_split", "train[-10%:]") + train = create_hf_dataset( + ds_name, + config, + train_split, + hf_config, ) + valid = create_hf_dataset( + ds_name, + config, + valid_split, + hf_config, + ) + else: + train, valid = [], [] - if args.train: - train_split = hf_args.get("train_split", "train[:80%]") - valid_split = hf_args.get("valid_split", "train[-10%:]") - train = create_hf_dataset(split=train_split) - valid = create_hf_dataset(split=valid_split) - else: - train, valid = [], [] - if args.test: - test = create_hf_dataset(split=hf_args.get("test_split")) - else: - test = [] + if args.test: + test_split = ds.get("test_split") + test = create_hf_dataset( + ds_name, + config, + test_split, + hf_config, + ) + else: + test = [] - return train, valid, test + collection.append((train, valid, test)) + + if len(collection) == 1: + return collection[0] + + # Otherwise concatenate them + return tuple(map(ConcatenatedDataset, zip(*collection))) def load_dataset(args, tokenizer: PreTrainedTokenizer): @@ -197,18 +252,11 @@ def load_dataset(args, tokenizer: PreTrainedTokenizer): train, valid, test = load_custom_hf_dataset(args, tokenizer) else: data_path = Path(args.data) - - prompt_feature = getattr(args, "prompt_feature", None) - completion_feature = getattr(args, "completion_feature", None) if data_path.exists(): - train, valid, test = load_local_dataset( - data_path, tokenizer, prompt_feature, completion_feature - ) + train, valid, test = load_local_dataset(data_path, tokenizer, args) else: print(f"Loading Hugging Face dataset {args.data}.") - train, valid, test = load_hf_dataset( - args.data, tokenizer, prompt_feature, completion_feature - ) + train, valid, test = load_hf_dataset(args.data, tokenizer, args) if args.train and len(train) == 0: raise ValueError( diff --git a/llms/mlx_lm/tuner/trainer.py b/llms/mlx_lm/tuner/trainer.py index bf84d066..d675f9b6 100644 --- a/llms/mlx_lm/tuner/trainer.py +++ b/llms/mlx_lm/tuner/trainer.py @@ -5,13 +5,16 @@ import shutil import time from dataclasses import dataclass, field from pathlib import Path -from typing import Union +from typing import List, Optional, Tuple import mlx.core as mx import mlx.nn as nn import numpy as np from mlx.nn.utils import average_gradients from mlx.utils import tree_flatten +from transformers import PreTrainedTokenizer + +from .datasets import CompletionsDataset def grad_checkpoint(layer): @@ -63,20 +66,30 @@ class TrainingArgs: ) -def default_loss(model, inputs, targets, lengths): +def default_loss(model, batch, lengths): + inputs = batch[:, :-1] + targets = batch[:, 1:] + logits = model(inputs) logits = logits.astype(mx.float32) - length_mask = mx.arange(inputs.shape[1])[None, :] < lengths[:, None] + steps = mx.arange(1, targets.shape[1] + 1) + mask = mx.logical_and(steps >= lengths[:, 0:1], steps <= lengths[:, 1:]) - ce = nn.losses.cross_entropy(logits, targets) * length_mask - ntoks = length_mask.sum() + ce = nn.losses.cross_entropy(logits, targets) * mask + ntoks = mask.sum() ce = ce.sum() / ntoks return ce, ntoks -def iterate_batches(dataset, tokenizer, batch_size, max_seq_length, train=False): +def iterate_batches( + dataset, + tokenizer, + batch_size, + max_seq_length, + train=False, +): # Sort by length: idx = sorted(range(len(dataset)), key=lambda idx: len(dataset[idx])) if len(dataset) < batch_size: @@ -101,6 +114,10 @@ def iterate_batches(dataset, tokenizer, batch_size, max_seq_length, train=False) indices = np.random.permutation(len(batch_idx)) for i in indices: batch = [dataset[j] for j in batch_idx[i]] + if len(batch[0]) == 2: + batch, offsets = zip(*batch) + else: + offsets = [0] * len(batch) lengths = [len(x) for x in batch] if max(lengths) > max_seq_length: print( @@ -123,8 +140,7 @@ def iterate_batches(dataset, tokenizer, batch_size, max_seq_length, train=False) truncated_length # Update lengths to match truncated lengths ) batch = mx.array(batch_arr) - - yield batch[:, :-1], batch[:, 1:], mx.array(lengths) + yield batch, mx.array(list(zip(offsets, lengths))) if not train: break diff --git a/llms/tests/test_datsets.py b/llms/tests/test_datsets.py index dd86d277..5edab8bf 100644 --- a/llms/tests/test_datsets.py +++ b/llms/tests/test_datsets.py @@ -78,14 +78,15 @@ class TestDatasets(unittest.TestCase): self.assertTrue(isinstance(train, datasets.ChatDataset)) def test_hf(self): + hf_args = { + "name": "billsum", + "prompt_feature": "text", + "completion_feature": "summary", + "train_split": "train[:2%]", + "valid_split": "train[-2%:]", + } args = types.SimpleNamespace( - hf_dataset={ - "name": "billsum", - "prompt_feature": "text", - "completion_feature": "summary", - "train_split": "train[:2%]", - "valid_split": "train[-2%:]", - }, + hf_dataset=hf_args, test=False, train=True, ) @@ -97,6 +98,16 @@ class TestDatasets(unittest.TestCase): self.assertTrue(len(valid[0]) > 0) self.assertEqual(len(test), 0) + args = types.SimpleNamespace( + hf_dataset=[hf_args, hf_args], + test=False, + train=True, + ) + train_double, valid_double, test_double = datasets.load_dataset(args, tokenizer) + self.assertEqual(2 * len(train), len(train_double)) + self.assertEqual(2 * len(valid), len(valid_double)) + self.assertEqual(2 * len(test), len(test_double)) + if __name__ == "__main__": unittest.main() From bded1a8fcd2be98288b68d7274a6e8f72d694515 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Mon, 10 Feb 2025 13:04:35 -0800 Subject: [PATCH 148/188] fix looping in whisper (#1273) --- whisper/mlx_whisper/transcribe.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/whisper/mlx_whisper/transcribe.py b/whisper/mlx_whisper/transcribe.py index 7057679b..e9c2751f 100644 --- a/whisper/mlx_whisper/transcribe.py +++ b/whisper/mlx_whisper/transcribe.py @@ -195,6 +195,8 @@ def transcribe( seek_points.append(0) if len(seek_points) % 2 == 1: seek_points.append(content_frames) + else: + seek_points[-1] = min(content_frames, seek_points[-1]) seek_clips: List[Tuple[int, int]] = list(zip(seek_points[::2], seek_points[1::2])) punctuation = "\"'“¿([{-\"'.。,,!!??::”)]}、" From 3d677f087055aca9444090a89280f6c046fd9455 Mon Sep 17 00:00:00 2001 From: Matt Clayton <156335168+mattjcly@users.noreply.github.com> Date: Tue, 11 Feb 2025 18:41:02 -0500 Subject: [PATCH 149/188] Add "from_draft" to GenerationResponse (#1272) * Add from_draft field in GenerationResponse * Cleanup * Re-work for minimal changes, add test * Fix comment --- llms/mlx_lm/utils.py | 34 +++++++++++++++++++++++------- llms/tests/test_generate.py | 41 ++++++++++++++++++++++++++++++++++--- 2 files changed, 65 insertions(+), 10 deletions(-) diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index b2e89a13..64813123 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -13,7 +13,18 @@ import time from dataclasses import dataclass from pathlib import Path from textwrap import dedent -from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Type, Union +from typing import ( + Any, + Callable, + Dict, + Generator, + List, + NamedTuple, + Optional, + Tuple, + Type, + Union, +) import mlx.core as mx import mlx.nn as nn @@ -65,6 +76,7 @@ class GenerationResponse: Args: text (str): The next segment of decoded text. This can be an empty string. token (int): The next token. + from_draft (bool): Whether the token was generated by the draft model. logprobs (mx.array): A vector of log probabilities. prompt_tokens (int): The number of tokens in the prompt. prompt_tps (float): The prompt processing tokens-per-second. @@ -77,6 +89,7 @@ class GenerationResponse: text: str token: int logprobs: mx.array + from_draft: bool prompt_tokens: int prompt_tps: float generation_tokens: int @@ -338,7 +351,7 @@ def speculative_generate_step( kv_bits: Optional[int] = None, kv_group_size: int = 64, quantized_kv_start: int = 0, -) -> Generator[Tuple[mx.array, mx.array], None, None]: +) -> Generator[Tuple[mx.array, mx.array, bool], None, None]: """ A generator producing token ids based on the given prompt from the model. @@ -365,7 +378,8 @@ def speculative_generate_step( when ``kv_bits`` is non-None. Default: ``0``. Yields: - Tuple[mx.array, mx.array]: One token and a vector of log probabilities. + Tuple[mx.array, mx.array, bool]: One token, a vector of log probabilities, + and a bool indicating if the token was generated by the draft model """ y = prompt @@ -450,12 +464,12 @@ def speculative_generate_step( break n += 1 ntoks += 1 - yield tn, lpn + yield tn, lpn, True if ntoks == max_tokens: break if ntoks < max_tokens: ntoks += 1 - yield tokens[n], logprobs[n] + yield tokens[n], logprobs[n], False if ntoks == max_tokens: break @@ -463,7 +477,7 @@ def speculative_generate_step( y = mx.array([tokens[n]], mx.uint32) draft_y = y - # If we accpeted all the draft tokens, include the last + # If we accepted all the draft tokens, include the last # draft token in the next draft step since it hasn't been # processed yet by the draft model if n == num_draft: @@ -518,6 +532,10 @@ def stream_generate( if draft_model is None: kwargs.pop("num_draft_tokens", None) token_generator = generate_step(prompt, model, **kwargs) + # from_draft always false for non-speculative generation + token_generator = ( + (token, logprobs, False) for token, logprobs in token_generator + ) else: kwargs.pop("max_kv_size", None) token_generator = speculative_generate_step( @@ -526,7 +544,7 @@ def stream_generate( with wired_limit(model, [generation_stream]): detokenizer.reset() tic = time.perf_counter() - for n, (token, logprobs) in enumerate(token_generator): + for n, (token, logprobs, from_draft) in enumerate(token_generator): if n == 0: prompt_time = time.perf_counter() - tic prompt_tps = prompt.size / prompt_time @@ -540,6 +558,7 @@ def stream_generate( text=detokenizer.last_segment, token=token, logprobs=logprobs, + from_draft=from_draft, prompt_tokens=prompt.size, prompt_tps=prompt_tps, generation_tokens=n + 1, @@ -553,6 +572,7 @@ def stream_generate( text=detokenizer.last_segment, token=token, logprobs=logprobs, + from_draft=from_draft, prompt_tokens=prompt.size, prompt_tps=prompt_tps, generation_tokens=n + 1, diff --git a/llms/tests/test_generate.py b/llms/tests/test_generate.py index f2345394..7445a9b9 100644 --- a/llms/tests/test_generate.py +++ b/llms/tests/test_generate.py @@ -1,17 +1,24 @@ # Copyright © 2024 Apple Inc. import unittest +from typing import List from mlx_lm.sample_utils import make_logits_processors -from mlx_lm.utils import generate, load +from mlx_lm.utils import ( + GenerationResponse, + generate, + load, + make_sampler, + stream_generate, +) class TestGenerate(unittest.TestCase): @classmethod def setUpClass(cls): - HF_MODEL_PATH = "mlx-community/Qwen1.5-0.5B-Chat-4bit" - cls.model, cls.tokenizer = load(HF_MODEL_PATH) + cls.HF_MODEL_PATH = "mlx-community/Qwen1.5-0.5B-Chat-4bit" + cls.model, cls.tokenizer = load(cls.HF_MODEL_PATH) def test_generate(self): # Simple test that generation runs @@ -51,6 +58,34 @@ class TestGenerate(unittest.TestCase): ) self.assertEqual(len(all_toks), len(init_toks) + 5) + def test_stream_generate_speculative(self): + # Use same model as draft model, this is not a speed test + draft_model, _ = load(self.HF_MODEL_PATH) + + results: List[GenerationResponse] = [] + drafted: List[bool] = [] + + # make a determinate sampler + sampler = make_sampler(temp=0.0) + + for generation_result in stream_generate( + model=self.model, + tokenizer=self.tokenizer, + prompt="hello", + max_tokens=5, + draft_model=draft_model, + num_draft_tokens=2, + sampler=sampler, + ): + drafted.append(generation_result.from_draft) + results.append(generation_result) + + self.assertEqual(len(results), 5) + # since num_draft_tokens is 2 and draft model is the same, the + # first 2 generations should be drafts, the third should come + # from the target model, and last two should be drafts + self.assertEqual(drafted, [True, True, False, True, True]) + if __name__ == "__main__": unittest.main() From e879ea70e188f0705563b6ee01909037a0af860d Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Tue, 11 Feb 2025 16:10:30 -0800 Subject: [PATCH 150/188] fix generation evaluations (#1277) --- llms/mlx_lm/evaluate.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/llms/mlx_lm/evaluate.py b/llms/mlx_lm/evaluate.py index ca5e83bb..2f35ade2 100644 --- a/llms/mlx_lm/evaluate.py +++ b/llms/mlx_lm/evaluate.py @@ -295,7 +295,9 @@ class MLXLM(LM): completions = [] for context, until in tqdm(zip(contexts, untils), total=len(contexts)): - context = self._tokenize(context) + context = self.tokenizer.encode( + context, add_special_tokens=not self.use_chat_template + ) max_tokens = min( self._max_tokens, self.tokenizer.model_max_length - len(context), From f8cbf159e08e505e89cfee982796e14ede6bdfbc Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Tue, 11 Feb 2025 16:26:59 -0800 Subject: [PATCH 151/188] fix sharding for more even number of layers (#1276) --- llms/mlx_lm/models/deepseek_v2.py | 8 +++++--- llms/mlx_lm/models/deepseek_v3.py | 7 ++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/llms/mlx_lm/models/deepseek_v2.py b/llms/mlx_lm/models/deepseek_v2.py index f22b2e3f..7a5bdeb1 100644 --- a/llms/mlx_lm/models/deepseek_v2.py +++ b/llms/mlx_lm/models/deepseek_v2.py @@ -378,9 +378,11 @@ class DeepseekV2Model(nn.Module): # rank=pipeline_size-1 gets the first self.pipeline_rank = group.rank() self.pipeline_size = group.size() - layers_per_rank = ( - len(self.layers) + self.pipeline_size - 1 - ) // self.pipeline_size + layers_per_rank = len(self.layers) // self.pipeline_size + extra = len(self.layers) - layers_per_rank * self.pipeline_size + if self.pipeline_rank < extra: + layers_per_rank += 1 + self.start_idx = (self.pipeline_size - self.pipeline_rank - 1) * layers_per_rank self.end_idx = self.start_idx + layers_per_rank self.num_layers = layers_per_rank diff --git a/llms/mlx_lm/models/deepseek_v3.py b/llms/mlx_lm/models/deepseek_v3.py index 2df93d9f..47e17236 100644 --- a/llms/mlx_lm/models/deepseek_v3.py +++ b/llms/mlx_lm/models/deepseek_v3.py @@ -410,9 +410,10 @@ class DeepseekV3Model(nn.Module): # rank=pipeline_size-1 gets the first self.pipeline_rank = group.rank() self.pipeline_size = group.size() - layers_per_rank = ( - len(self.layers) + self.pipeline_size - 1 - ) // self.pipeline_size + layers_per_rank = len(self.layers) // self.pipeline_size + extra = len(self.layers) - layers_per_rank * self.pipeline_size + if self.pipeline_rank < extra: + layers_per_rank += 1 self.start_idx = (self.pipeline_size - self.pipeline_rank - 1) * layers_per_rank self.end_idx = self.start_idx + layers_per_rank self.layers = self.layers[: self.end_idx] From 42413c5d851668abbc7f919107f06cf92b4e153d Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Tue, 11 Feb 2025 16:48:55 -0800 Subject: [PATCH 152/188] fix lora timings after validation (#1278) --- llms/mlx_lm/tuner/trainer.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/llms/mlx_lm/tuner/trainer.py b/llms/mlx_lm/tuner/trainer.py index d675f9b6..64e26af8 100644 --- a/llms/mlx_lm/tuner/trainer.py +++ b/llms/mlx_lm/tuner/trainer.py @@ -233,8 +233,8 @@ def train( n_tokens = 0 steps = 0 trained_tokens = 0 + train_time = 0 # Main training loop - start = time.perf_counter() for it, batch in zip( range(1, args.iters + 1), iterate_batches( @@ -245,10 +245,11 @@ def train( train=True, ), ): + tic = time.perf_counter() # Report validation loss if needed, the first validation loss # is always measured before any training. if it == 1 or it % args.steps_per_eval == 0 or it == args.iters: - stop = time.perf_counter() + tic = time.perf_counter() val_loss = evaluate( model=model, dataset=val_dataset, @@ -259,7 +260,7 @@ def train( max_seq_length=args.max_seq_length, iterate_batches=iterate_batches, ) - val_time = time.perf_counter() - stop + val_time = time.perf_counter() - tic if rank == 0: print( f"Iter {it}: " @@ -276,24 +277,23 @@ def train( } training_callback.on_val_loss_report(val_info) - start = time.perf_counter() + tic = time.perf_counter() lvalue, toks = step(batch) losses += lvalue n_tokens += toks steps += 1 mx.eval(state, losses, n_tokens) + train_time += time.perf_counter() - tic # Report training loss if needed if it % args.steps_per_report == 0 or it == args.iters: - stop = time.perf_counter() - train_loss = mx.distributed.all_sum(losses, stream=mx.cpu).item() train_loss /= steps * mx.distributed.init().size() n_tokens = mx.distributed.all_sum(n_tokens, stream=mx.cpu).item() learning_rate = optimizer.learning_rate.item() - it_sec = args.steps_per_report / (stop - start) - tokens_sec = float(n_tokens) / (stop - start) + it_sec = args.steps_per_report / train_time + tokens_sec = float(n_tokens) / train_time trained_tokens += n_tokens peak_mem = mx.metal.get_peak_memory() / 1e9 if rank == 0: @@ -322,7 +322,7 @@ def train( losses = 0 n_tokens = 0 steps = 0 - start = time.perf_counter() + train_time = 0 # Save adapter weights if it % args.steps_per_save == 0: From ec30dc35382d87614f51fe7590f015f93a491bfd Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Tue, 11 Feb 2025 16:49:35 -0800 Subject: [PATCH 153/188] hunyuan finetune (#1270) --- llms/mlx_lm/tuner/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/llms/mlx_lm/tuner/utils.py b/llms/mlx_lm/tuner/utils.py index d86e01dd..9f8d2925 100644 --- a/llms/mlx_lm/tuner/utils.py +++ b/llms/mlx_lm/tuner/utils.py @@ -89,6 +89,7 @@ def linear_to_lora_layers( "mixtral", "nemotron", "stablelm", + "hunyuan", "qwen2", "qwen2_moe", "phimoe", From 7b07b14e6742b2c162a20587f15abed972618c02 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Thu, 13 Feb 2025 19:19:53 -0800 Subject: [PATCH 154/188] add logits processor to spec gen (#1260) --- llms/mlx_lm/utils.py | 45 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index 64813123..78a2e802 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -382,8 +382,8 @@ def speculative_generate_step( and a bool indicating if the token was generated by the draft model """ - y = prompt - tokens = None + y = prompt.astype(mx.uint32) + prev_tokens = None # Create the KV cache for generation if prompt_cache is None: @@ -404,17 +404,41 @@ def speculative_generate_step( kv_bits=kv_bits, ) + def _process_and_sample(tokens, logits): + if logits_processors: + for processor in logits_processors: + logits = processor(tokens, logits) + + logprobs = logits - mx.logsumexp(logits, keepdims=True) + logprobs = logprobs.squeeze(0) + y = sampler(logprobs) + return y, logprobs + def _step(model, cache, y, n_predict=1): with mx.stream(generation_stream): logits = model(y[None], cache=cache) logits = logits[:, -n_predict:, :] quantize_cache_fn(cache) - - logprobs = logits - mx.logsumexp(logits, keepdims=True) - logprobs = logprobs.squeeze(0) - y = sampler(logprobs) - return y, logprobs + if logits_processors: + nonlocal prev_tokens + out_y, out_logprobs = [], [] + if n_predict > 1: + y = y[: -(n_predict - 1)] + for i in range(n_predict): + prev_tokens = ( + mx.concat([prev_tokens, y]) if prev_tokens is not None else y + ) + y, logprobs = _process_and_sample( + prev_tokens, logits[:, i : i + 1, :] + ) + out_y.append(y) + out_logprobs.append(logprobs) + return mx.concatenate(out_y, axis=0), mx.concatenate( + out_logprobs, axis=0 + ) + else: + return _process_and_sample(None, logits) def _prefill(model, cache, y): while y.size > prefill_step_size: @@ -451,9 +475,14 @@ def speculative_generate_step( while True: num_draft = min(max_tokens - ntoks, num_draft_tokens) draft_tokens = _draft_generate(draft_y, num_draft) + if prev_tokens is not None: + prev_tokens = prev_tokens[ + : prev_tokens.size - draft_y.size - num_draft + 1 + ] y = mx.concatenate([y, draft_tokens]) tokens, logprobs = _step(model, model_cache, y, num_draft + 1) + mx.eval(tokens, draft_tokens) draft_tokens = draft_tokens.tolist() tokens = tokens.tolist() @@ -485,6 +514,8 @@ def speculative_generate_step( [mx.array(draft_tokens[-1:], mx.uint32), draft_y] ) + if prev_tokens is not None and n < num_draft: + prev_tokens = prev_tokens[: -(num_draft - n)] _rewind_cache(num_draft, n) finally: _rewind_cache(num_draft, n) From 96bf37008e91de86538bdacf3a12a479a322902b Mon Sep 17 00:00:00 2001 From: Matthias Neumayer Date: Fri, 14 Feb 2025 04:32:56 +0100 Subject: [PATCH 155/188] Update README.md to include how to set temperature (#1280) * Update README.md to include how to set temperature * nits --------- Co-authored-by: Awni Hannun --- llms/README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/llms/README.md b/llms/README.md index 4f7451c1..e2d1db59 100644 --- a/llms/README.md +++ b/llms/README.md @@ -123,6 +123,18 @@ for response in stream_generate(model, tokenizer, prompt, max_tokens=512): print() ``` +#### Sampling + +The `generate` and `stream_generate` functions accept `sampler` and +`logits_processors` keyword arguments. A sampler is any callable which accepts +a possibly batched logits array and returns an array of sampled tokens. The +`logits_processors` must be a list of callables which take the token history +and current logits as input and return the processed logits. The logits +processors are applied in order. + +Some standard sampling functions and logits processors are provided in +`mlx_lm.sample_utils`. + ### Command Line You can also use `mlx-lm` from the command line with: From 1cbf5cdac7b081b09bb4f8a8cb4909ff9fdcf108 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Wed, 19 Feb 2025 06:22:51 -0800 Subject: [PATCH 156/188] use more standard window strategy (#1287) --- transformer_lm/main.py | 47 +++++++++++++++++------------------------- 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/transformer_lm/main.py b/transformer_lm/main.py index dc725cbe..7ff5b73f 100644 --- a/transformer_lm/main.py +++ b/transformer_lm/main.py @@ -8,7 +8,6 @@ import datasets import mlx.core as mx import mlx.nn as nn import mlx.optimizers as optim -import numpy as np from mlx.utils import tree_flatten @@ -40,26 +39,21 @@ class TransformerLM(nn.Module): def to_samples(context_size, dataset): - tokens = dataset.size window_size = context_size + 1 # include target - samples = tokens - window_size + 1 - X = np.lib.stride_tricks.as_strided( - dataset, - shape=(samples, window_size), - strides=(dataset.itemsize, dataset.itemsize), - ) - return X[:, :-1], X[:, 1:] + samples = dataset.size // window_size + dataset = dataset[: samples * window_size] + return mx.array(dataset.reshape(samples, -1)) def iterate_batches(batch_size, context_size, dataset): - inputs, targets = to_samples(context_size, dataset) + inputs = to_samples(context_size, dataset) s = 0 while True: if s == 0: # Reset permutation: - perm = np.random.permutation(inputs.shape[0]) + perm = mx.random.permutation(inputs.shape[0]) ids = perm[s : s + batch_size] - yield inputs[ids], targets[ids] + yield inputs[ids] s += batch_size if s >= inputs.shape[0]: s = 0 @@ -84,45 +78,42 @@ def main(args): ) print(f"Training a transformer with {nparams / 1024**2:.3f} M parameters") - def loss_fn(model, x, y, reduce=True): + def loss_fn(model, inputs, reduction="mean"): + x, y = inputs[..., :-1], inputs[..., 1:] logits = model(x) - losses = nn.losses.cross_entropy(logits, y) - return mx.mean(losses) if reduce else mx.mean(losses, axis=(-1, -2)) + return nn.losses.cross_entropy(logits, y, reduction=reduction) optimizer = optim.AdamW( learning_rate=args.learning_rate, weight_decay=args.weight_decay ) def eval_fn(dataset): - inputs, targets = map(mx.array, to_samples(context_size, dataset)) + inputs = to_samples(context_size, dataset) loss = 0 - for s in range(0, targets.shape[0], batch_size): - bx, by = inputs[s : s + batch_size], targets[s : s + batch_size] - bx, by = map(mx.array, (bx, by)) - losses = loss_fn(model, bx, by, reduce=False) - loss += mx.sum(losses).item() - return loss / len(targets) + for s in range(0, inputs.shape[0], batch_size): + losses = loss_fn(model, inputs[s : s + batch_size], reduction="sum") + loss += losses.item() + return loss / (inputs.size - inputs.shape[0]) state = [model.state, optimizer.state] @partial(mx.compile, inputs=state, outputs=state) - def step(inputs, targets): + def step(inputs): loss_and_grad_fn = nn.value_and_grad(model, loss_fn) - loss, grads = loss_and_grad_fn(model, inputs, targets) + loss, grads = loss_and_grad_fn(model, inputs) optimizer.update(model, grads) return loss train_iterator = iterate_batches(batch_size, context_size, train) losses = [] tic = time.perf_counter() - for it, (inputs, targets) in zip(range(args.num_iters), train_iterator): - inputs, targets = map(mx.array, (inputs, targets)) + for it, inputs in zip(range(args.num_iters), train_iterator): optimizer.learning_rate = min(1, it / args.lr_warmup) * args.learning_rate - loss = step(inputs, targets) + loss = step(inputs) mx.eval(state) losses.append(loss.item()) if (it + 1) % steps_per_report == 0: - train_loss = np.mean(losses) + train_loss = sum(losses) / len(losses) toc = time.perf_counter() print( f"Iter {it + 1}: Train loss {train_loss:.3f}, " From 85669451d0e4cfb2370994bdcad38b190cfbb417 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Thu, 20 Feb 2025 13:32:01 -0800 Subject: [PATCH 157/188] Fix num layers in fine tune (#1294) --- llms/mlx_lm/lora.py | 8 +++++++- llms/mlx_lm/tuner/utils.py | 7 +------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/llms/mlx_lm/lora.py b/llms/mlx_lm/lora.py index abc5dfa9..def3b6dd 100644 --- a/llms/mlx_lm/lora.py +++ b/llms/mlx_lm/lora.py @@ -181,8 +181,14 @@ def train_model( training_callback: TrainingCallback = None, ): model.freeze() + if args.num_layers > len(model.layers): + raise ValueError( + f"Requested to train {args.num_layers} layers " + f"but the model only has {len(model.layers)} layers." + ) + if args.fine_tune_type == "full": - for l in model.layers[-min(args.num_layers, 0) :]: + for l in model.layers[-max(args.num_layers, 0) :]: l.unfreeze() elif args.fine_tune_type in ["lora", "dora"]: # Convert linear layers to lora/dora layers and unfreeze in the process diff --git a/llms/mlx_lm/tuner/utils.py b/llms/mlx_lm/tuner/utils.py index 9f8d2925..f5df11e3 100644 --- a/llms/mlx_lm/tuner/utils.py +++ b/llms/mlx_lm/tuner/utils.py @@ -52,11 +52,6 @@ def linear_to_lora_layers( use_dora (bool): If True, uses DoRA instead of LoRA. Default: ``False`` """ - if num_layers > len(model.layers): - raise ValueError( - f"Requested {num_layers} LoRA layers " - f"but the model only has {len(model.layers)} layers." - ) def to_lora(layer): if isinstance(layer, (nn.Linear, nn.QuantizedLinear)): @@ -154,7 +149,7 @@ def linear_to_lora_layers( else: raise ValueError(f"Lora does not support {model.model_type}") - for l in model.layers[-min(num_layers, 0) :]: + for l in model.layers[-max(num_layers, 0) :]: lora_layers = [(k, to_lora(m)) for k, m in l.named_modules() if k in keys] if lora_layers: l.update_modules(tree_unflatten(lora_layers)) From 3d793ecf6887512fd81f0f0c6bd156c06a6eaf88 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Thu, 20 Feb 2025 15:55:55 -0800 Subject: [PATCH 158/188] Fix logits processor bugs with spec dec (#1291) * Fix logits processor bugs with spec dec * bump patch --- llms/mlx_lm/_version.py | 2 +- llms/mlx_lm/utils.py | 19 ++++++------------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/llms/mlx_lm/_version.py b/llms/mlx_lm/_version.py index b2f98e6f..89e6cd00 100644 --- a/llms/mlx_lm/_version.py +++ b/llms/mlx_lm/_version.py @@ -1,3 +1,3 @@ # Copyright © 2023-2024 Apple Inc. -__version__ = "0.21.0" +__version__ = "0.21.5" diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index 78a2e802..1fae76fa 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -409,8 +409,7 @@ def speculative_generate_step( for processor in logits_processors: logits = processor(tokens, logits) - logprobs = logits - mx.logsumexp(logits, keepdims=True) - logprobs = logprobs.squeeze(0) + logprobs = logits - mx.logsumexp(logits, axis=-1, keepdims=True) y = sampler(logprobs) return y, logprobs @@ -429,16 +428,14 @@ def speculative_generate_step( prev_tokens = ( mx.concat([prev_tokens, y]) if prev_tokens is not None else y ) - y, logprobs = _process_and_sample( - prev_tokens, logits[:, i : i + 1, :] - ) + y, logprobs = _process_and_sample(prev_tokens, logits[:, i, :]) out_y.append(y) out_logprobs.append(logprobs) return mx.concatenate(out_y, axis=0), mx.concatenate( out_logprobs, axis=0 ) else: - return _process_and_sample(None, logits) + return _process_and_sample(None, logits.squeeze(0)) def _prefill(model, cache, y): while y.size > prefill_step_size: @@ -476,13 +473,9 @@ def speculative_generate_step( num_draft = min(max_tokens - ntoks, num_draft_tokens) draft_tokens = _draft_generate(draft_y, num_draft) if prev_tokens is not None: - prev_tokens = prev_tokens[ - : prev_tokens.size - draft_y.size - num_draft + 1 - ] + prev_tokens = prev_tokens[: prev_tokens.size - y.size - num_draft + 1] y = mx.concatenate([y, draft_tokens]) - tokens, logprobs = _step(model, model_cache, y, num_draft + 1) - mx.eval(tokens, draft_tokens) draft_tokens = draft_tokens.tolist() tokens = tokens.tolist() @@ -514,8 +507,8 @@ def speculative_generate_step( [mx.array(draft_tokens[-1:], mx.uint32), draft_y] ) - if prev_tokens is not None and n < num_draft: - prev_tokens = prev_tokens[: -(num_draft - n)] + if prev_tokens is not None: + prev_tokens = prev_tokens[: -max(num_draft - n, 1)] _rewind_cache(num_draft, n) finally: _rewind_cache(num_draft, n) From 09b641aaa74f9737f747b62ad8c628405e7e25be Mon Sep 17 00:00:00 2001 From: Usama Ahmed <53372259+0ssamaak0@users.noreply.github.com> Date: Sat, 22 Feb 2025 17:08:54 +0300 Subject: [PATCH 159/188] Fix FutureWarning in torch.load by setting weights_only=True (#1295) --- clip/convert.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clip/convert.py b/clip/convert.py index 29bac22e..976d7494 100644 --- a/clip/convert.py +++ b/clip/convert.py @@ -121,7 +121,7 @@ if __name__ == "__main__": mlx_path.mkdir(parents=True, exist_ok=True) print("[INFO] Loading") - torch_weights = torch.load(torch_path / "pytorch_model.bin") + torch_weights = torch.load(torch_path / "pytorch_model.bin", weights_only=True) print("[INFO] Converting") mlx_weights = { k: torch_to_mx(v, dtype=args.dtype) for k, v in torch_weights.items() From c37e26a1a31657db0565e1e34f4da241a1a0b8bf Mon Sep 17 00:00:00 2001 From: Shunta Saito Date: Tue, 25 Feb 2025 12:24:43 +0900 Subject: [PATCH 160/188] Add plamo-2-1b model (#1283) * Add pfnet/plamo-2-1b * Fix cache.py to support non-top level layers * Use mlx's BaseModelArgs * Fix model * Use sanitize() * Remove unnecessary changes * Add plamo2.py * Apply formatter * Fix some part * Allow a cache obj defined externally * Fix channel first weights to channel last for right use of MLX's conv1d * Remove unused code part * Give all inputs when it's the first time call of model * Fix import * Include .jsonl files to download from Huggingface hub * Fix reference to layers * Remove unnecessary code and add a test for plamo2 * Do not pass mask to prepare_inputs_for_generation * Fix to use repeat instead of tile * Add state property to PlamoCache * Add __iter__ and __next__ methods to PlamoCache * cleanup * cleanup * fix --------- Co-authored-by: Awni Hannun --- llms/mlx_lm/models/plamo2.py | 601 +++++++++++++++++++++++++++++++++++ llms/mlx_lm/utils.py | 1 + llms/tests/test_models.py | 19 +- 3 files changed, 620 insertions(+), 1 deletion(-) create mode 100644 llms/mlx_lm/models/plamo2.py diff --git a/llms/mlx_lm/models/plamo2.py b/llms/mlx_lm/models/plamo2.py new file mode 100644 index 00000000..1d8215dd --- /dev/null +++ b/llms/mlx_lm/models/plamo2.py @@ -0,0 +1,601 @@ +# Copyright © 2025 Apple Inc. + +import math +from dataclasses import dataclass +from typing import Any, Optional, Union + +import mlx.core as mx +import mlx.nn as nn +from mlx_lm.models.base import BaseModelArgs, create_attention_mask + +from .cache import KVCache, MambaCache + + +@dataclass +class ModelArgs(BaseModelArgs): + model_type: str = "plamo2" + hidden_size: int = 4096 + num_hidden_layers: int = 32 + rms_norm_eps: float = 1e-6 + tie_word_embeddings: bool = True + num_attention_heads: int = 32 + num_key_value_heads: int = 4 + hidden_size_per_head: int = 128 + max_position_embeddings: int = 2048 + attention_window_size: int = 2048 + full_attention_idx: Optional[list[int]] = None + mamba_d_state: int = 64 + mamba_d_conv: int = 4 + mamba_num_heads: int = 64 + mamba_step: int = 2 + mamba_chunk_size: int = 256 + mamba_enabled: bool = True + intermediate_size: int = 13312 + vocab_size: int = 32000 + max_position_embeddings: int = 10 * 1024 * 1024 + + +class RMSNorm(nn.Module): + def __init__( + self, + hidden_size: int, + eps: float = 1e-6, + offset: float = 1.0, + ) -> None: + super().__init__() + self.weight = mx.zeros(hidden_size) + self.variance_epsilon = eps + self.offset = offset + + def __call__(self, hidden_states: mx.array) -> mx.array: + return mx.fast.rms_norm( + hidden_states, self.weight + self.offset, self.variance_epsilon + ) + + +def get_initial_dt_bias(num_heads: int) -> mx.array: + dt_min = 0.001 + dt_max = 0.1 + dt = mx.exp( + mx.random.uniform(shape=(num_heads,)) * (math.log(dt_max) - math.log(dt_min)) + + math.log(dt_min) + ) + dt = mx.clip(dt, a_min=1e-4, a_max=None) + inv_dt = dt + mx.log(-mx.expm1(-dt)) + return inv_dt + + +def get_initial_A(num_heads: int) -> mx.array: + A = mx.arange(1, num_heads + 1, dtype=mx.float32) + return mx.log(A) + + +# From: https://github.com/state-spaces/mamba/blob/0cce0fa645f100f00620ddf2333c2b7712abfdec/mamba_ssm/ops/triton/selective_state_update.py#L219 +def selective_state_update_ref( + state, x, dt, A, B, C, D=None, z=None, dt_bias=None, dt_softplus=False +) -> tuple[mx.array, mx.array]: + """ + Argument: + state: (batch, dim, dstate) or (batch, nheads, dim, dstate) + x: (batch, dim) or (batch, nheads, dim) + dt: (batch, dim) or (batch, nheads, dim) + A: (dim, dstate) or (nheads, dim, dstate) + B: (batch, dstate) or (batch, ngroups, dstate) + C: (batch, dstate) or (batch, ngroups, dstate) + D: (dim,) or (nheads, dim) + z: (batch, dim) or (batch, nheads, dim) + dt_bias: (dim,) or (nheads, dim) + Return: + out: (batch, dim) or (batch, nheads, dim) + """ + has_heads = state.ndim > 3 + if state.ndim == 3: + state = mx.expand_dims(state, 1) + if x.ndim == 2: + x = mx.expand_dims(x, 1) + if dt.ndim == 2: + dt = mx.expand_dims(dt, 1) + if A.ndim == 2: + A = mx.expand_dims(A, 0) + if B.ndim == 2: + B = mx.expand_dims(B, 1) + if C.ndim == 2: + C = mx.expand_dims(C, 1) + if D is not None and D.ndim == 1: + D = mx.expand_dims(D, 0) + if z is not None and z.ndim == 2: + z = mx.expand_dims(z, 1) + if dt_bias is not None and dt_bias.ndim == 1: + dt_bias = mx.expand_dims(dt_bias, 0) + batch, nheads, dim, dstate = state.shape + assert x.shape == (batch, nheads, dim) + assert dt.shape == x.shape + assert A.shape == (nheads, dim, dstate) + ngroups = B.shape[1] + assert nheads % ngroups == 0, "nheads must be divisible by ngroups" + assert B.shape == (batch, ngroups, dstate) + assert C.shape == B.shape + if D is not None: + assert D.shape == (nheads, dim) + if z is not None: + assert z.shape == x.shape + if dt_bias is not None: + assert dt_bias.shape == (nheads, dim) + dt = dt + dt_bias + dt = nn.softplus(dt) if dt_softplus else dt + dA = mx.exp(mx.expand_dims(dt, axis=-1) * A) # (batch, nheads, dim, dstate) + B = mx.reshape( + mx.repeat(mx.expand_dims(B, axis=2), nheads // ngroups, 2), + (batch, nheads, dstate), + ) # (batch, nheads, dstate) + C = mx.reshape( + mx.repeat(mx.expand_dims(C, axis=2), nheads // ngroups, 2), + (batch, nheads, dstate), + ) # (batch, nheads, dstate) + dB = mx.expand_dims(dt, axis=-1) * mx.expand_dims( + B, axis=-2 + ) # (batch, nheads, dim, dstate) + state = state * dA + dB * mx.expand_dims(x, axis=-1) # (batch, dim, dstate) + out = mx.einsum("bhdn,bhn->bhd", state.astype(C.dtype), C) + if D is not None: + out += (x * D).astype(out.dtype) + out = (out if z is None else out * nn.silu(z)).astype(x.dtype) + if not has_heads: + out = out.squeeze(1) + return out, state + + +def ssd_update_state( + ssm_state: mx.array, + x: mx.array, + dt: mx.array, + A: mx.array, + B: mx.array, + C: mx.array, + D: mx.array, + z: mx.array, + dt_bias: mx.array, + dt_softplus: bool, +) -> tuple[mx.array, mx.array]: + assert ssm_state.dtype == mx.float32 + dtype = x.dtype + + hidden_size_per_head = x.shape[-1] + d_state = B.shape[-1] + A = mx.broadcast_to( + A[:, None, None], (A.shape[0], hidden_size_per_head, d_state) + ).astype(mx.float32) + dt = mx.broadcast_to( + dt[..., None], (dt.shape[0], dt.shape[1], hidden_size_per_head) + ) + dt_bias = mx.broadcast_to( + dt_bias[:, None], (dt_bias.shape[0], hidden_size_per_head) + ) + D = mx.broadcast_to(D[:, None], (D.shape[0], hidden_size_per_head)) + out, ssm_state = selective_state_update_ref( + ssm_state, + x.astype(dtype), + dt.astype(dtype), + A.astype(mx.float32), + B.astype(dtype), + C.astype(dtype), + D.astype(mx.float32), + z.astype(dtype), + dt_bias.astype(mx.float32), + dt_softplus=dt_softplus, + ) + return out[:, None], ssm_state + + +def ssd_chunk_scan_combined( + x: mx.array, + dt: mx.array, + A: mx.array, + B: mx.array, + C: mx.array, + D: mx.array, + z: mx.array, + dt_bias: mx.array, + dt_softplus: bool, + ssm_state: mx.array, +) -> tuple[mx.array, mx.array]: + assert ssm_state.dtype == mx.float32 + length = x.shape[1] + ys = [] + for i in range(length): + y, ssm_state = ssd_update_state( + ssm_state, + x[:, i], + dt[:, i], + A, + B[:, i], + C[:, i], + D if D.ndim == 1 else D[:, i], + z=z[:, i], + dt_bias=dt_bias, + dt_softplus=dt_softplus, + ) + ys.append(y) + return mx.concatenate(ys, axis=1), ssm_state + + +def causal_conv1d_update(conv_state, x, weight) -> tuple[mx.array, mx.array]: + batch, seqlen, dim = x.shape + width = weight.shape[1] + state_len = conv_state.shape[-2] + x = mx.concatenate([conv_state, x], axis=-2) + conv_state = x[:, -state_len:] + out = mx.conv1d( + x, + weight, + padding=0, + groups=dim, + )[:, -seqlen:] + return nn.silu(out), conv_state + + +class Mamba(nn.Module): + def __init__(self, config: ModelArgs) -> None: + super().__init__() + self.config = config + self.hidden_size = config.hidden_size + self.d_state = config.mamba_d_state + self.d_conv = config.mamba_d_conv + self.chunk_size = config.mamba_chunk_size + self.num_heads = config.mamba_num_heads + self.hidden_size_per_head = config.hidden_size_per_head + + self.intermediate_size = self.num_heads * self.hidden_size_per_head + + self.in_proj = nn.Linear( + self.hidden_size, 2 * self.intermediate_size, bias=False + ) + self.conv1d = nn.Conv1d( + in_channels=self.intermediate_size, + out_channels=self.intermediate_size, + bias=False, + kernel_size=self.d_conv, + groups=self.intermediate_size, + padding=0, + ) + self.dt_dim = max(64, self.hidden_size // 16) + self.bcdt_proj = nn.Linear( + self.intermediate_size, + self.dt_dim + 2 * self.d_state, + bias=False, + ) + self.dt_proj = nn.Linear(self.dt_dim, self.num_heads, bias=False) + + self.dt_bias = get_initial_dt_bias(self.num_heads) + self.A_log = get_initial_A(self.num_heads) + self.D = mx.ones(self.num_heads, dtype=mx.float32) + + self.dt_norm_weight = mx.ones(self.dt_dim) + self.B_norm_weight = mx.ones(self.d_state) + self.C_norm_weight = mx.ones(self.d_state) + + self.out_proj = nn.Linear(self.intermediate_size, self.hidden_size, bias=False) + + def __call__( + self, + hidden_states: mx.array, + mask: Optional[mx.array] = None, + cache=None, + ): + bsize, length, _ = hidden_states.shape + + if cache is not None and cache[0] is not None: + conv_state = cache[0] + ssm_state = cache[1] + else: + conv_state = mx.zeros( + (bsize, self.d_conv - 1, self.intermediate_size), + dtype=hidden_states.dtype, + ) + ssm_state = mx.zeros( + (bsize, self.num_heads, self.hidden_size_per_head, self.d_state), + dtype=mx.float32, + ) + + zx = self.in_proj(hidden_states) + zx = zx.reshape(bsize, length, self.num_heads, -1) + # z: (bsize, length, num_heads, hidden_size_per_head) + # x: (bsize, length, num_heads, hidden_size_per_head) + z, x = mx.split( + zx, + [ + self.hidden_size_per_head, + ], + axis=-1, + ) + + x = x.reshape(bsize, -1, self.num_heads * self.hidden_size_per_head) + x, conv_state = causal_conv1d_update(conv_state, x, self.conv1d.weight) + BCdt = self.bcdt_proj(x) + x = x.reshape(bsize, length, self.num_heads, -1) + B, C, dt = mx.split(BCdt, [self.d_state, self.d_state * 2], axis=-1) + + A = -mx.exp(self.A_log.astype(mx.float32)) # (num_heads,) + dt = mx.fast.rms_norm(dt, self.dt_norm_weight, self.config.rms_norm_eps) + B = mx.fast.rms_norm(B, self.B_norm_weight, self.config.rms_norm_eps) + C = mx.fast.rms_norm(C, self.C_norm_weight, self.config.rms_norm_eps) + + # (bsize, length, num_heads, 1) + dt = self.dt_proj(dt)[..., None] + + out, ssm_state = ssd_chunk_scan_combined( + x, + dt.reshape(bsize, length, -1), + A, + B, + C, + D=self.D, + z=z, + dt_bias=self.dt_bias, + dt_softplus=True, + ssm_state=ssm_state, + ) + + if cache is not None: + cache[0] = conv_state + cache[1] = ssm_state + y = self.out_proj(out.reshape(bsize, length, -1)) + + return y + + +class Attention(nn.Module): + def __init__(self, config: ModelArgs) -> None: + super().__init__() + self.config = config + self.hidden_size = config.hidden_size + head_dim = config.hidden_size_per_head + self.max_position_embeddings = config.max_position_embeddings + self.scale = head_dim**-0.5 + + self.q_num_heads = config.num_attention_heads + self.qk_dim = self.v_dim = head_dim + self.k_num_heads = self.v_num_heads = config.num_key_value_heads + assert self.q_num_heads % self.k_num_heads == 0 + self.n_group = self.q_num_heads // self.k_num_heads + + self.q_proj_dim = self.q_num_heads * self.qk_dim + self.k_proj_dim = self.k_num_heads * self.qk_dim + self.v_proj_dim = self.k_num_heads * self.v_dim + self.qkv_proj = nn.Linear( + self.hidden_size, + self.q_proj_dim + self.k_proj_dim + self.v_proj_dim, + bias=False, + ) + self.o_proj = nn.Linear( + self.q_num_heads * self.v_dim, self.hidden_size, bias=False + ) + + self.q_weight = mx.ones((self.q_num_heads, self.qk_dim)) + self.k_weight = mx.ones((self.k_num_heads, self.qk_dim)) + + self.rope = nn.RoPE(self.qk_dim) + + def __call__( + self, + hidden_states: mx.array, + mask: Optional[mx.array] = None, + cache=None, + ): + B, T, _ = hidden_states.shape + + qkv = self.qkv_proj(hidden_states) + q, k, v = mx.split( + qkv, [self.q_proj_dim, self.q_proj_dim + self.k_proj_dim], axis=-1 + ) + q = q.reshape(B, T, self.q_num_heads, self.qk_dim).transpose(0, 2, 1, 3) + k = k.reshape(B, T, self.k_num_heads, self.qk_dim).transpose(0, 2, 1, 3) + v = v.reshape(B, T, self.v_num_heads, self.v_dim).transpose(0, 2, 1, 3) + + q = mx.fast.layer_norm(q, None, None, 1e-6) * self.q_weight[:, None] + k = mx.fast.layer_norm(k, None, None, 1e-6) * self.k_weight[:, None] + + if cache is not None: + q = self.rope(q, offset=cache.offset) + k = self.rope(k, offset=cache.offset) + k, v = cache.update_and_fetch(k, v) + else: + q = self.rope(q) + k = self.rope(k) + + output = mx.fast.scaled_dot_product_attention( + q, + k, + v, + scale=self.scale, + mask=mask, + ) + output = output.transpose(0, 2, 1, 3).reshape( + B, T, self.q_num_heads * self.v_dim + ) + return self.o_proj(output) + + +class MLP(nn.Module): + def __init__(self, config: ModelArgs) -> None: + super().__init__() + self.config = config + self.hidden_size = config.hidden_size + self.intermediate_size = config.intermediate_size + self.gate_up_proj = nn.Linear( + self.hidden_size, self.intermediate_size * 2, bias=False + ) + self.down_proj = nn.Linear(self.intermediate_size, self.hidden_size, bias=False) + + def __call__(self, x: mx.array) -> mx.array: + h = self.gate_up_proj(x) + hs = mx.split(h, 2, axis=-1) + return self.down_proj(nn.silu(hs[0]) * hs[1]) + + +class PlamoDecoderLayer(nn.Module): + def __init__(self, config: ModelArgs, is_mamba: bool) -> None: + super().__init__() + self.config = config + self.hidden_size = config.hidden_size + self.is_mamba = is_mamba + self.mixer: nn.Module + if is_mamba: + self.mixer = Mamba(config) + else: + self.mixer = Attention(config) + self.mlp = MLP(config) + self.pre_mixer_norm = RMSNorm( + config.hidden_size, eps=config.rms_norm_eps, offset=1.0 + ) + self.post_mixer_norm = RMSNorm( + config.hidden_size, eps=config.rms_norm_eps, offset=1.0 / 5 + ) + self.pre_mlp_norm = RMSNorm( + config.hidden_size, eps=config.rms_norm_eps, offset=1.0 + ) + self.post_mlp_norm = RMSNorm( + config.hidden_size, eps=config.rms_norm_eps, offset=1.0 / (5**1.5) + ) + + def __call__( + self, + hidden_states: mx.array, + mask: Optional[mx.array] = None, + cache=None, + ): + residual = hidden_states + hidden_states = self.pre_mixer_norm(hidden_states) + + hidden_states_sa = self.mixer( + hidden_states=hidden_states, + mask=mask, + cache=cache, + ) + + hidden_states_sa = self.post_mixer_norm(hidden_states_sa) + hidden_states = residual + hidden_states_sa + + residual = hidden_states + hidden_states = self.pre_mlp_norm(hidden_states) + + # Fully Connected + hidden_states_mlp = self.mlp(hidden_states) + + # Residual + hidden_states_mlp = self.post_mlp_norm(hidden_states_mlp) + return residual + hidden_states_mlp + + +def is_mamba(config: ModelArgs, i: int) -> bool: + if not config.mamba_enabled: + return False + assert config.mamba_step > 1 + assert i < config.num_hidden_layers + + if config.num_hidden_layers <= (config.mamba_step // 2): + # use attention in last layer + return i != config.num_hidden_layers - 1 + return (i % config.mamba_step) != (config.mamba_step // 2) + + +class PlamoDecoder(nn.Module): + def __init__(self, config: ModelArgs) -> None: + super().__init__() + + self.layers = [ + PlamoDecoderLayer(config, is_mamba=is_mamba(config, i)) + for i in range(config.num_hidden_layers) + ] + + def __call__(self, x: mx.array, mask: mx.array, cache): + for i, decoder_layer in enumerate(self.layers): + x = decoder_layer( + x, + mask=mask, + cache=cache[i], + ) + return x + + +class PlamoModel(nn.Module): + def __init__(self, config: ModelArgs): + super().__init__() + + self.config = config + self.vocab_size = config.vocab_size + + self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size) + self.layers = PlamoDecoder(config) # type: ignore + self.norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps) + + def __call__( + self, + inputs: mx.array, + mask: Optional[mx.array] = None, + cache=None, + ): + batch_size, seq_length = inputs.shape + + h = self.embed_tokens(inputs) + + if mask is None: + mask = create_attention_mask(h, [cache[1]] if cache is not None else None) + + if cache is None: + cache = [None] * len(self.layers.layers) + + # decoder layers + out = self.layers( + h, + mask, + cache, + ) + + return self.norm(out) + + +class Model(nn.Module): + + def __init__(self, config: ModelArgs) -> None: + super().__init__() + self.config = config + self.model_type = config.model_type + self.model = PlamoModel(config) + + self.vocab_size = config.vocab_size + + if not config.tie_word_embeddings: + self.lm_head: nn.Module = nn.Linear( + config.hidden_size, vocab_size, bias=False + ) + + def sanitize(self, weights: dict[Any, Any]) -> dict[Any, Any]: + for k, v in weights.items(): + if "conv1d.weight" in k and v.shape[-1] != 1: + weights[k] = v.moveaxis(2, 1) + return weights + + def make_cache(self): + # TODO use RotatingKVCache is not full_attn + # full_attn = self.layer_idx in self.config.full_attention_idx + return [MambaCache() if l.is_mamba else KVCache() for l in self.layers] + + def __call__( + self, inputs: mx.array, mask: Optional[mx.array] = None, cache=None + ) -> mx.array: + outputs = self.model( + inputs=inputs, + mask=None, + cache=cache, + ) + if self.config.tie_word_embeddings: + logits = self.model.embed_tokens.as_linear(outputs) + else: + logits = self.lm_head(outputs) + + return logits + + @property + def layers(self): + return self.model.layers.layers diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index 1fae76fa..2d760743 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -192,6 +192,7 @@ def get_model_path(path_or_hf_repo: str, revision: Optional[str] = None) -> Path "tokenizer.model", "*.tiktoken", "*.txt", + "*.jsonl", ], ) ) diff --git a/llms/tests/test_models.py b/llms/tests/test_models.py index d8cf6820..0c0fc601 100644 --- a/llms/tests/test_models.py +++ b/llms/tests/test_models.py @@ -183,7 +183,7 @@ class TestModels(unittest.TestCase): self.assertEqual(outputs.shape, (1, 2, vocab_size)) self.assertEqual(outputs.dtype, t) - if model_type != "mamba": + if model_type not in ("mamba", "plamo2"): mask = create_causal_mask(inputs.shape[1], 0).astype(t) outputs = model(inputs, mask=mask) self.assertEqual(outputs.shape, (1, 2, vocab_size)) @@ -372,6 +372,23 @@ class TestModels(unittest.TestCase): model, args.model_type, args.vocab_size, args.num_hidden_layers ) + def test_plamo2(self): + from mlx_lm.models import plamo2 + + args = plamo2.ModelArgs( + model_type="plamo2", + hidden_size=1024, + num_hidden_layers=4, + intermediate_size=2048, + num_attention_heads=8, + rms_norm_eps=1e-5, + vocab_size=10_000, + ) + model = plamo2.Model(args) + self.model_test_runner( + model, args.model_type, args.vocab_size, args.num_hidden_layers + ) + def test_stablelm(self): from mlx_lm.models import stablelm From b7f742ef56ee2b7b127e2c1390a4ab625dc044e4 Mon Sep 17 00:00:00 2001 From: Pedro Cuenca Date: Wed, 26 Feb 2025 20:32:36 +0100 Subject: [PATCH 161/188] Mixed quant recipes (#1300) * Mixed 3/6 and 2/6 recipes based on Alex Barron's * format / nits --------- Co-authored-by: Awni Hannun --- llms/mlx_lm/convert.py | 27 ++++++++++++++++++++++++++- llms/mlx_lm/utils.py | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/llms/mlx_lm/convert.py b/llms/mlx_lm/convert.py index 9bac77a5..86a96447 100644 --- a/llms/mlx_lm/convert.py +++ b/llms/mlx_lm/convert.py @@ -1,8 +1,27 @@ # Copyright © 2023-2024 Apple Inc. import argparse +from enum import Enum -from .utils import convert +from .utils import convert, mixed_2_6, mixed_3_6 + + +class MixedQuants(Enum): + mixed_3_6 = "mixed_3_6" + mixed_2_6 = "mixed_2_6" + + @classmethod + def recipe_names(cls): + return [member.name for member in cls] + + +def quant_args(arg): + try: + return MixedQuants[arg].value + except KeyError: + raise argparse.ArgumentTypeError( + f"Invalid q-recipe {arg!r}. Choose from: {MixedQuants.recipe_names()}" + ) def configure_parser() -> argparse.ArgumentParser: @@ -29,6 +48,12 @@ def configure_parser() -> argparse.ArgumentParser: parser.add_argument( "--q-bits", help="Bits per weight for quantization.", type=int, default=4 ) + parser.add_argument( + "--quant-predicate", + help=f"Mixed-bit quantization recipe. Choices: {MixedQuants.recipe_names()}", + type=quant_args, + required=False, + ) parser.add_argument( "--dtype", help="Type to save the non-quantized parameters.", diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index 2d760743..7dff0ee3 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -1015,6 +1015,46 @@ def save_config( json.dump(config, fid, indent=4) +def mixed_quant_predicate_builder( + low_bits: int = 4, high_bits: int = 4, group_size: int = 64 +) -> Callable[[str, nn.Module, dict], Union[bool, dict]]: + def mixed_quant_predicate( + path: str, + module: nn.Module, + config: dict, + ) -> Union[bool, dict]: + """Implements mixed quantization predicates with similar choices to, for example, llama.cpp's Q4_K_M. + Ref: https://github.com/ggerganov/llama.cpp/blob/917786f43d0f29b7c77a0c56767c0fa4df68b1c5/src/llama.cpp#L5265 + By Alex Barron: https://gist.github.com/barronalex/84addb8078be21969f1690c1454855f3 + """ + + if not hasattr(module, "to_quantized"): + return False + + index = int(path.split(".")[2]) if len(path.split(".")) > 2 else 0 + + num_layers = config["num_hidden_layers"] + use_more_bits = ( + index < num_layers // 8 + or index >= 7 * num_layers // 8 + or (index - num_layers // 8) % 3 == 2 + ) + if "v_proj" in path and use_more_bits: + return {"group_size": group_size, "bits": high_bits} + if "down_proj" in path and use_more_bits: + return {"group_size": group_size, "bits": high_bits} + if "lm_head" in path: + return {"group_size": group_size, "bits": high_bits} + + return {"group_size": group_size, "bits": low_bits} + + return mixed_quant_predicate + + +mixed_3_6 = mixed_quant_predicate_builder(low_bits=3) +mixed_2_6 = mixed_quant_predicate_builder(low_bits=2) + + def convert( hf_path: str, mlx_path: str = "mlx_model", From 56e60ad5a6692d4cee38701237de1bb37e19e994 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Wed, 26 Feb 2025 15:44:57 -0800 Subject: [PATCH 162/188] fix manage for new transformers (#1304) --- llms/mlx_lm/manage.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/llms/mlx_lm/manage.py b/llms/mlx_lm/manage.py index 9827f3dc..c06de6b3 100644 --- a/llms/mlx_lm/manage.py +++ b/llms/mlx_lm/manage.py @@ -2,7 +2,22 @@ import argparse from typing import List, Union from huggingface_hub import scan_cache_dir -from transformers.commands.user import tabulate + + +def tabulate(rows: List[List[Union[str, int]]], headers: List[str]) -> str: + """ + Inspired by: + - stackoverflow.com/a/8356620/593036 + - stackoverflow.com/questions/9535954/printing-lists-as-tabular-data + """ + col_widths = [max(len(str(x)) for x in col) for col in zip(*rows, headers)] + row_format = ("{{:{}}} " * len(headers)).format(*col_widths) + lines = [] + lines.append(row_format.format(*headers)) + lines.append(row_format.format(*["-" * w for w in col_widths])) + for row in rows: + lines.append(row_format.format(*row)) + return "\n".join(lines) def ask_for_confirmation(message: str) -> bool: From 0f240a4c7e37332361d5d595499535d6ba7cb73b Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Wed, 26 Feb 2025 15:46:16 -0800 Subject: [PATCH 163/188] Use max tokens from options in mlx_lm evaluate (#1302) --- llms/mlx_lm/evaluate.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/llms/mlx_lm/evaluate.py b/llms/mlx_lm/evaluate.py index 2f35ade2..cd6de7ec 100644 --- a/llms/mlx_lm/evaluate.py +++ b/llms/mlx_lm/evaluate.py @@ -289,17 +289,15 @@ class MLXLM(LM): contexts, options = zip(*[req.args for req in requests]) # contrary to the doc the second element of the tuple contains # {'do_sample': False, 'until': ['\n\n'], 'temperature': 0} - keys = list(options[0].keys()) - assert "until" in keys - untils = [x["until"] for x in options] completions = [] - for context, until in tqdm(zip(contexts, untils), total=len(contexts)): + for context, opt in tqdm(zip(contexts, options), total=len(contexts)): + until = opt["until"] context = self.tokenizer.encode( context, add_special_tokens=not self.use_chat_template ) max_tokens = min( - self._max_tokens, + opt.get("max_gen_tokens", self._max_tokens), self.tokenizer.model_max_length - len(context), ) text = "" @@ -334,9 +332,9 @@ def main(): ) parser.add_argument( "--limit", - default=1.0, + default=100, help="Limit the number of examples per task.", - type=float, + type=int, ) parser.add_argument("--seed", type=int, default=123, help="Random seed.") parser.add_argument( From 00a73790702991075b80f9facf219ae397e1eb15 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Wed, 26 Feb 2025 16:21:54 -0800 Subject: [PATCH 164/188] Fixes for phi4 mini (#1305) --- llms/mlx_lm/models/phi3.py | 16 ++++++++++++---- llms/mlx_lm/models/su_rope.py | 6 ++++-- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/llms/mlx_lm/models/phi3.py b/llms/mlx_lm/models/phi3.py index d1c21e25..63e985de 100644 --- a/llms/mlx_lm/models/phi3.py +++ b/llms/mlx_lm/models/phi3.py @@ -23,8 +23,10 @@ class ModelArgs(BaseModelArgs): rope_theta: float = 10000 rope_traditional: bool = False rope_scaling: Optional[Dict[str, Union[float, List[float]]]] = None + partial_rotary_factor: float = 1.0 max_position_embeddings: int = 131072 original_max_position_embeddings: int = 4096 + tie_word_embeddings: bool = False def __post_init__(self): if self.num_key_value_heads is None: @@ -59,9 +61,10 @@ class Attention(nn.Module): self.qkv_proj = nn.Linear(dim, op_size, bias=False) self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=False) + rope_dim = int(head_dim * args.partial_rotary_factor) if args.rope_scaling and args.rope_scaling["type"] in ["longrope", "su"]: self.rope = SuScaledRotaryEmbedding( - head_dim, + rope_dim, base=args.rope_theta, max_position_embeddings=args.max_position_embeddings, original_max_position_embeddings=args.original_max_position_embeddings, @@ -74,7 +77,7 @@ class Attention(nn.Module): assert isinstance(args.rope_scaling["factor"], float) rope_scale = 1 / args.rope_scaling["factor"] self.rope = nn.RoPE( - head_dim, + rope_dim, traditional=args.rope_traditional, base=args.rope_theta, scale=rope_scale, @@ -190,7 +193,8 @@ class Model(nn.Module): super().__init__() self.model_type = args.model_type self.model = Phi3Model(args) - self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=False) + if not args.tie_word_embeddings: + self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=False) self.args = args def __call__( @@ -200,7 +204,11 @@ class Model(nn.Module): cache=None, ): out = self.model(inputs, mask, cache) - return self.lm_head(out) + if self.args.tie_word_embeddings: + out = self.model.embed_tokens.as_linear(out) + else: + out = self.lm_head(out) + return out @property def layers(self): diff --git a/llms/mlx_lm/models/su_rope.py b/llms/mlx_lm/models/su_rope.py index 9c414afd..6340c77b 100644 --- a/llms/mlx_lm/models/su_rope.py +++ b/llms/mlx_lm/models/su_rope.py @@ -51,11 +51,13 @@ class SuScaledRotaryEmbedding(nn.Module): + math.log(max_position_embeddings / original_max_position_embeddings) / math.log(original_max_position_embeddings) ) + self.dim = dims def __call__(self, x, offset: int = 0): + x[..., : self.dim] = self.scale * x[..., : self.dim] return mx.fast.rope( - self.scale * x, - x.shape[-1], + x, + self.dim, traditional=False, base=None, scale=1.0, From eb7354963119df3b533cb63ace61a92c2dcb532d Mon Sep 17 00:00:00 2001 From: madroid Date: Thu, 27 Feb 2025 23:44:00 +0800 Subject: [PATCH 165/188] Generate: Support Prefill Response (#1299) * Generate: Support Prefill Prompt python -m mlx_lm.generate \ --model mlx-community/DeepSeek-R1-Distill-Qwen-1.5B-4bit \ --prompt "hello" \ --prefill-prompt "\n" * Generate: rename prefill-prompt to prefill-response * nits --------- Co-authored-by: Awni Hannun --- llms/mlx_lm/generate.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/llms/mlx_lm/generate.py b/llms/mlx_lm/generate.py index d8f97e5e..e40332dd 100644 --- a/llms/mlx_lm/generate.py +++ b/llms/mlx_lm/generate.py @@ -60,6 +60,11 @@ def setup_arg_parser(): default=DEFAULT_PROMPT, help="Message to be processed by the model ('-' reads from stdin)", ) + parser.add_argument( + "--prefill-response", + default=None, + help="Prefill response to be used for the chat template", + ) parser.add_argument( "--max-tokens", "-m", @@ -219,10 +224,14 @@ def main(): messages = [] messages.append({"role": "user", "content": prompt}) + has_prefill = args.prefill_response is not None + if has_prefill: + messages.append({"role": "assistant", "content": args.prefill_response}) prompt = tokenizer.apply_chat_template( messages, tokenize=False, - add_generation_prompt=True, + continue_final_message=has_prefill, + add_generation_prompt=not has_prefill, **template_kwargs, ) @@ -233,7 +242,8 @@ def main(): test_prompt = tokenizer.apply_chat_template( messages, tokenize=False, - add_generation_prompt=True, + continue_final_message=has_prefill, + add_generation_prompt=not has_prefill, ) prompt = prompt[test_prompt.index("") :] prompt = tokenizer.encode(prompt, add_special_tokens=False) From b2108a0de694fb753ec46f91590f0f1e59494d63 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Fri, 28 Feb 2025 11:33:04 -0800 Subject: [PATCH 166/188] Allow mask prompt in config (#1314) --- llms/mlx_lm/lora.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/llms/mlx_lm/lora.py b/llms/mlx_lm/lora.py index def3b6dd..d32bfe6d 100644 --- a/llms/mlx_lm/lora.py +++ b/llms/mlx_lm/lora.py @@ -62,6 +62,7 @@ CONFIG_DEFAULTS = { "grad_checkpoint": False, "lr_schedule": None, "lora_parameters": {"rank": 8, "alpha": 16, "dropout": 0.0, "scale": 10.0}, + "mask_prompt": False, } @@ -99,7 +100,7 @@ def build_parser(): "--mask-prompt", action="store_true", help="Mask the prompt in the loss when training", - default=False, + default=None, ) parser.add_argument( From 845cd8c01e91fb50b7a5467db3efc8de4d2c29f6 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Fri, 28 Feb 2025 11:33:18 -0800 Subject: [PATCH 167/188] support kimi + more options in chat mode (#1312) --- llms/mlx_lm/_version.py | 2 +- llms/mlx_lm/chat.py | 15 +++++++- llms/mlx_lm/models/deepseek_v3.py | 61 ++++++++++++++++++------------- llms/mlx_lm/utils.py | 1 + 4 files changed, 52 insertions(+), 27 deletions(-) diff --git a/llms/mlx_lm/_version.py b/llms/mlx_lm/_version.py index 89e6cd00..839089b6 100644 --- a/llms/mlx_lm/_version.py +++ b/llms/mlx_lm/_version.py @@ -1,3 +1,3 @@ # Copyright © 2023-2024 Apple Inc. -__version__ = "0.21.5" +__version__ = "0.21.6" diff --git a/llms/mlx_lm/chat.py b/llms/mlx_lm/chat.py index e52ad10d..5c0b78db 100644 --- a/llms/mlx_lm/chat.py +++ b/llms/mlx_lm/chat.py @@ -65,12 +65,25 @@ def main(): tokenizer_config={"trust_remote_code": True}, ) - print(f"[INFO] Starting chat session with {args.model}. To exit, enter 'q'.") + def print_help(): + print("The command list:") + print("- 'q' to exit") + print("- 'r' to reset the chat") + print("- 'h' to display these commands") + + print(f"[INFO] Starting chat session with {args.model}.") + print_help() prompt_cache = make_prompt_cache(model, args.max_kv_size) while True: query = input(">> ") if query == "q": break + if query == "r": + prompt_cache = make_prompt_cache(model, args.max_kv_size) + continue + if query == "h": + print_help() + continue messages = [{"role": "user", "content": query}] prompt = tokenizer.apply_chat_template(messages, add_generation_prompt=True) for response in stream_generate( diff --git a/llms/mlx_lm/models/deepseek_v3.py b/llms/mlx_lm/models/deepseek_v3.py index 47e17236..5cd40a0d 100644 --- a/llms/mlx_lm/models/deepseek_v3.py +++ b/llms/mlx_lm/models/deepseek_v3.py @@ -181,30 +181,37 @@ class DeepseekV3Attention(nn.Module): bias=config.attention_bias, ) - mscale_all_dim = self.config.rope_scaling.get("mscale_all_dim", 0) - scaling_factor = self.config.rope_scaling["factor"] - if mscale_all_dim: - mscale = yarn_get_mscale(scaling_factor, mscale_all_dim) - self.scale = self.scale * mscale * mscale + if self.config.rope_scaling is not None: + mscale_all_dim = self.config.rope_scaling.get("mscale_all_dim", 0) + scaling_factor = self.config.rope_scaling["factor"] + if mscale_all_dim: + mscale = yarn_get_mscale(scaling_factor, mscale_all_dim) + self.scale = self.scale * mscale * mscale - rope_kwargs = { - key: self.config.rope_scaling[key] - for key in [ - "original_max_position_embeddings", - "beta_fast", - "beta_slow", - "mscale", - "mscale_all_dim", - ] - if key in self.config.rope_scaling - } - self.rope = DeepseekV3YarnRotaryEmbedding( - dim=self.qk_rope_head_dim, - max_position_embeddings=self.max_position_embeddings, - scaling_factor=scaling_factor, - base=self.rope_theta, - **rope_kwargs, - ) + rope_kwargs = { + key: self.config.rope_scaling[key] + for key in [ + "original_max_position_embeddings", + "beta_fast", + "beta_slow", + "mscale", + "mscale_all_dim", + ] + if key in self.config.rope_scaling + } + self.rope = DeepseekV3YarnRotaryEmbedding( + dim=self.qk_rope_head_dim, + max_position_embeddings=self.max_position_embeddings, + scaling_factor=scaling_factor, + base=self.rope_theta, + **rope_kwargs, + ) + else: + self.rope = nn.RoPE( + dims=self.qk_rope_head_dim, + base=self.rope_theta, + traditional=True, + ) def __call__( self, @@ -487,8 +494,12 @@ class Model(nn.Module): ] weights[f"{prefix}.mlp.switch_mlp.{m}.{k}"] = mx.stack(to_join) - # Remove multi-token prediction layer - return {k: v for k, v in weights.items() if not k.startswith("model.layers.61")} + # Remove multi-token prediction layer and any unused precomputed rotary freqs + return { + k: v + for k, v in weights.items() + if not k.startswith("model.layers.61") and "rotary_emb.inv_freq" not in k + } @property def layers(self): diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py index 7dff0ee3..05fac92f 100644 --- a/llms/mlx_lm/utils.py +++ b/llms/mlx_lm/utils.py @@ -191,6 +191,7 @@ def get_model_path(path_or_hf_repo: str, revision: Optional[str] = None) -> Path "*.py", "tokenizer.model", "*.tiktoken", + "tiktoken.model", "*.txt", "*.jsonl", ], From 269faa5fa4cf8cdb2e1f4df3200db5a551318b43 Mon Sep 17 00:00:00 2001 From: Shunta Saito Date: Mon, 3 Mar 2025 23:12:02 +0900 Subject: [PATCH 168/188] Fix plamo2 model to use rms_norm (#1308) * Fix plamo2 model to use rms_norm and enable sliding window attention * Fix missing variable * Remove sliding window attention impl. cause it should be done by using RotatingKVCache * Remove unused imports --- llms/mlx_lm/models/plamo2.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/llms/mlx_lm/models/plamo2.py b/llms/mlx_lm/models/plamo2.py index 1d8215dd..657fa02e 100644 --- a/llms/mlx_lm/models/plamo2.py +++ b/llms/mlx_lm/models/plamo2.py @@ -2,7 +2,7 @@ import math from dataclasses import dataclass -from typing import Any, Optional, Union +from typing import Any, Optional import mlx.core as mx import mlx.nn as nn @@ -32,7 +32,6 @@ class ModelArgs(BaseModelArgs): mamba_enabled: bool = True intermediate_size: int = 13312 vocab_size: int = 32000 - max_position_embeddings: int = 10 * 1024 * 1024 class RMSNorm(nn.Module): @@ -53,6 +52,16 @@ class RMSNorm(nn.Module): ) +def _rms_norm(hidden_states: mx.array, eps: float) -> mx.array: + input_dtype = hidden_states.dtype + hidden_states = hidden_states.astype(mx.float32) + variance = mx.power(hidden_states, 2).mean(-1, keepdims=True) + hidden_states = hidden_states * mx.rsqrt(variance + eps) + hidden_states = hidden_states.astype(input_dtype) + + return hidden_states + + def get_initial_dt_bias(num_heads: int) -> mx.array: dt_min = 0.001 dt_max = 0.1 @@ -220,8 +229,7 @@ def ssd_chunk_scan_combined( def causal_conv1d_update(conv_state, x, weight) -> tuple[mx.array, mx.array]: - batch, seqlen, dim = x.shape - width = weight.shape[1] + _, seqlen, dim = x.shape state_len = conv_state.shape[-2] x = mx.concatenate([conv_state, x], axis=-2) conv_state = x[:, -state_len:] @@ -392,8 +400,8 @@ class Attention(nn.Module): k = k.reshape(B, T, self.k_num_heads, self.qk_dim).transpose(0, 2, 1, 3) v = v.reshape(B, T, self.v_num_heads, self.v_dim).transpose(0, 2, 1, 3) - q = mx.fast.layer_norm(q, None, None, 1e-6) * self.q_weight[:, None] - k = mx.fast.layer_norm(k, None, None, 1e-6) * self.k_weight[:, None] + q = _rms_norm(q, 1e-6) * self.q_weight[:, None] + k = _rms_norm(k, 1e-6) * self.k_weight[:, None] if cache is not None: q = self.rope(q, offset=cache.offset) @@ -556,7 +564,6 @@ class PlamoModel(nn.Module): class Model(nn.Module): - def __init__(self, config: ModelArgs) -> None: super().__init__() self.config = config @@ -567,7 +574,7 @@ class Model(nn.Module): if not config.tie_word_embeddings: self.lm_head: nn.Module = nn.Linear( - config.hidden_size, vocab_size, bias=False + config.hidden_size, self.vocab_size, bias=False ) def sanitize(self, weights: dict[Any, Any]) -> dict[Any, Any]: From 1bc3476a46d8d8e6bf7f2cb17570c2b7a26eafd0 Mon Sep 17 00:00:00 2001 From: Pierre-Louis <78484833+PierreLouisLetoquart@users.noreply.github.com> Date: Mon, 3 Mar 2025 09:12:33 -0500 Subject: [PATCH 169/188] chore(lora): Add real-time log buffering fix for nohup execution (#1311) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(lora): Add real-time log buffering fix for nohup execution Disable Python stdout buffering to ensure logs appear in nohup.out in real-time instead of only after script completion. * chore(lora): remove python 3.7+ check * chore(lora): running pre-commit hook --------- Co-authored-by: Pierre-Louis Létoquart --- lora/lora.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lora/lora.py b/lora/lora.py index 723e783d..6f91ccca 100644 --- a/lora/lora.py +++ b/lora/lora.py @@ -3,6 +3,7 @@ import argparse import json import math +import sys import time from pathlib import Path @@ -14,6 +15,9 @@ import utils as lora_utils from mlx.utils import tree_flatten from models import LoRALinear +# Disable output buffering to see print statements in real-time +sys.stdout.reconfigure(line_buffering=True) + def build_parser(): parser = argparse.ArgumentParser(description="LoRA or QLoRA finetuning.") From 65aa2ec84918d4438a73d7504bae2f8e9f0d396b Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Tue, 4 Mar 2025 12:47:32 -0800 Subject: [PATCH 170/188] use a bool mask for attention (#1319) --- llms/mlx_lm/generate.py | 2 +- llms/mlx_lm/models/base.py | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/llms/mlx_lm/generate.py b/llms/mlx_lm/generate.py index e40332dd..bd11dcf0 100644 --- a/llms/mlx_lm/generate.py +++ b/llms/mlx_lm/generate.py @@ -152,7 +152,7 @@ def setup_arg_parser(): "--num-draft-tokens", type=int, help="Number of tokens to draft when using speculative decoding.", - default=2, + default=3, ) return parser diff --git a/llms/mlx_lm/models/base.py b/llms/mlx_lm/models/base.py index ad7a4a65..8b40effb 100644 --- a/llms/mlx_lm/models/base.py +++ b/llms/mlx_lm/models/base.py @@ -33,13 +33,13 @@ def create_causal_mask( linds = mx.arange(offset, offset + N) if offset else rinds linds = linds[:, None] rinds = rinds[None] - mask = linds < rinds + mask = linds >= rinds if window_size is not None: - mask = mask | (linds > rinds + window_size) + mask = mask & (linds <= rinds + window_size) if lengths is not None: lengths = lengths[:, None, None, None] - mask = mask | (rinds >= lengths) - return mask * -1e9 + mask = mask & (rinds < lengths) + return mask def create_attention_mask(h: mx.array, cache: Optional[Any] = None): @@ -55,7 +55,6 @@ def create_attention_mask(h: mx.array, cache: Optional[Any] = None): else: offset = c.offset mask = create_causal_mask(T, offset, window_size=window_size) - mask = mask.astype(h.dtype) else: mask = None return mask From f621218ff5284306c0f78ea4a34cd22c033e4b9d Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Tue, 4 Mar 2025 13:53:20 -0800 Subject: [PATCH 171/188] Tool use example (#1316) * tool use example * nits --- llms/mlx_lm/examples/tool_use.py | 73 ++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 llms/mlx_lm/examples/tool_use.py diff --git a/llms/mlx_lm/examples/tool_use.py b/llms/mlx_lm/examples/tool_use.py new file mode 100644 index 00000000..624b9e5b --- /dev/null +++ b/llms/mlx_lm/examples/tool_use.py @@ -0,0 +1,73 @@ +# Copyright © 2025 Apple Inc. + +import json + +from mlx_lm import generate, load +from mlx_lm.models.cache import make_prompt_cache + +# Specify the checkpoint +checkpoint = "mlx-community/Qwen2.5-32B-Instruct-4bit" + +# Load the corresponding model and tokenizer +model, tokenizer = load(path_or_hf_repo=checkpoint) + + +# An example tool, make sure to include a docstring and type hints +def multiply(a: float, b: float): + """ + A function that multiplies two numbers + + Args: + a: The first number to multiply + b: The second number to multiply + """ + return a * b + + +tools = {"multiply": multiply} + +# Specify the prompt and conversation history +prompt = "Multiply 12234585 and 48838483920." +messages = [{"role": "user", "content": prompt}] + +prompt = tokenizer.apply_chat_template( + messages, add_generation_prompt=True, tools=list(tools.values()) +) + +prompt_cache = make_prompt_cache(model) + +# Generate the initial tool call: +response = generate( + model=model, + tokenizer=tokenizer, + prompt=prompt, + max_tokens=2048, + verbose=True, + prompt_cache=prompt_cache, +) + +# Parse the tool call: +# (Note, the tool call format is model specific) +tool_open = "" +tool_close = "" +start_tool = response.find(tool_open) + len(tool_open) +end_tool = response.find(tool_close) +tool_call = json.loads(response[start_tool:end_tool].strip()) +tool_result = tools[tool_call["name"]](**tool_call["arguments"]) + +# Put the tool result in the prompt +messages = [{"role": "tool", "name": tool_call["name"], "content": tool_result}] +prompt = tokenizer.apply_chat_template( + messages, + add_generation_prompt=True, +) + +# Generate the final response: +response = generate( + model=model, + tokenizer=tokenizer, + prompt=prompt, + max_tokens=2048, + verbose=True, + prompt_cache=prompt_cache, +) From e7267d30f83bc3f22ff6f0f8132ca0bcd9c38115 Mon Sep 17 00:00:00 2001 From: Angelos Katharopoulos Date: Wed, 5 Mar 2025 13:33:15 -0800 Subject: [PATCH 172/188] Distributed support cifar (#1301) --- cifar/README.md | 14 ++++++++ cifar/dataset.py | 11 +++++- cifar/main.py | 91 +++++++++++++++++++++++++++++++----------------- 3 files changed, 84 insertions(+), 32 deletions(-) diff --git a/cifar/README.md b/cifar/README.md index 763e641d..2016200d 100644 --- a/cifar/README.md +++ b/cifar/README.md @@ -48,3 +48,17 @@ Note this was run on an M1 Macbook Pro with 16GB RAM. At the time of writing, `mlx` doesn't have built-in learning rate schedules. We intend to update this example once these features are added. + +## Distributed training + +The example also supports distributed data parallel training. You can launch a +distributed training as follows: + +```shell +$ cat >hostfile.json +[ + {"ssh": "host-to-ssh-to", "ips": ["ip-to-bind-to"]}, + {"ssh": "host-to-ssh-to", "ips": ["ip-to-bind-to"]} +] +$ mlx.launch --verbose --hostfile hostfile.json main.py --batch 256 --epochs 5 --arch resnet20 +``` diff --git a/cifar/dataset.py b/cifar/dataset.py index 22b229f8..8967591e 100644 --- a/cifar/dataset.py +++ b/cifar/dataset.py @@ -1,3 +1,4 @@ +import mlx.core as mx import numpy as np from mlx.data.datasets import load_cifar10 @@ -12,8 +13,11 @@ def get_cifar10(batch_size, root=None): x = x.astype("float32") / 255.0 return (x - mean) / std + group = mx.distributed.init() + tr_iter = ( tr.shuffle() + .partition_if(group.size() > 1, group.size(), group.rank()) .to_stream() .image_random_h_flip("image", prob=0.5) .pad("image", 0, 4, 4, 0.0) @@ -25,6 +29,11 @@ def get_cifar10(batch_size, root=None): ) test = load_cifar10(root=root, train=False) - test_iter = test.to_stream().key_transform("image", normalize).batch(batch_size) + test_iter = ( + test.to_stream() + .partition_if(group.size() > 1, group.size(), group.rank()) + .key_transform("image", normalize) + .batch(batch_size) + ) return tr_iter, test_iter diff --git a/cifar/main.py b/cifar/main.py index 378bc424..ac010636 100644 --- a/cifar/main.py +++ b/cifar/main.py @@ -23,6 +23,13 @@ parser.add_argument("--seed", type=int, default=0, help="random seed") parser.add_argument("--cpu", action="store_true", help="use cpu only") +def print_zero(group, *args, **kwargs): + if group.rank() != 0: + return + flush = kwargs.pop("flush", True) + print(*args, **kwargs, flush=flush) + + def eval_fn(model, inp, tgt): return mx.mean(mx.argmax(model(inp), axis=1) == tgt) @@ -34,9 +41,20 @@ def train_epoch(model, train_iter, optimizer, epoch): acc = mx.mean(mx.argmax(output, axis=1) == tgt) return loss, acc - losses = [] - accs = [] - samples_per_sec = [] + world = mx.distributed.init() + losses = 0 + accuracies = 0 + samples_per_sec = 0 + count = 0 + + def average_stats(stats, count): + if world.size() == 1: + return [s / count for s in stats] + + with mx.stream(mx.cpu): + stats = mx.distributed.all_sum(mx.array(stats)) + count = mx.distributed.all_sum(count) + return (stats / count).tolist() state = [model.state, optimizer.state] @@ -44,6 +62,7 @@ def train_epoch(model, train_iter, optimizer, epoch): def step(inp, tgt): train_step_fn = nn.value_and_grad(model, train_step) (loss, acc), grads = train_step_fn(model, inp, tgt) + grads = nn.utils.average_gradients(grads) optimizer.update(model, grads) return loss, acc @@ -52,69 +71,79 @@ def train_epoch(model, train_iter, optimizer, epoch): y = mx.array(batch["label"]) tic = time.perf_counter() loss, acc = step(x, y) - mx.eval(state) + mx.eval(loss, acc, state) toc = time.perf_counter() - loss = loss.item() - acc = acc.item() - losses.append(loss) - accs.append(acc) - throughput = x.shape[0] / (toc - tic) - samples_per_sec.append(throughput) + losses += loss.item() + accuracies += acc.item() + samples_per_sec += x.shape[0] / (toc - tic) + count += 1 if batch_counter % 10 == 0: - print( + l, a, s = average_stats( + [losses, accuracies, world.size() * samples_per_sec], + count, + ) + print_zero( + world, " | ".join( ( f"Epoch {epoch:02d} [{batch_counter:03d}]", - f"Train loss {loss:.3f}", - f"Train acc {acc:.3f}", - f"Throughput: {throughput:.2f} images/second", + f"Train loss {l:.3f}", + f"Train acc {a:.3f}", + f"Throughput: {s:.2f} images/second", ) - ) + ), ) - mean_tr_loss = mx.mean(mx.array(losses)) - mean_tr_acc = mx.mean(mx.array(accs)) - samples_per_sec = mx.mean(mx.array(samples_per_sec)) - return mean_tr_loss, mean_tr_acc, samples_per_sec + return average_stats([losses, accuracies, world.size() * samples_per_sec], count) def test_epoch(model, test_iter, epoch): - accs = [] + accuracies = 0 + count = 0 for batch_counter, batch in enumerate(test_iter): x = mx.array(batch["image"]) y = mx.array(batch["label"]) acc = eval_fn(model, x, y) - acc_value = acc.item() - accs.append(acc_value) - mean_acc = mx.mean(mx.array(accs)) - return mean_acc + accuracies += acc.item() + count += 1 + + with mx.stream(mx.cpu): + accuracies = mx.distributed.all_sum(accuracies) + count = mx.distributed.all_sum(count) + return (accuracies / count).item() def main(args): mx.random.seed(args.seed) + # Initialize the distributed group and report the nodes that showed up + world = mx.distributed.init() + if world.size() > 1: + print(f"Starting rank {world.rank()} of {world.size()}", flush=True) + model = getattr(resnet, args.arch)() - print("Number of params: {:0.04f} M".format(model.num_params() / 1e6)) + print_zero(world, f"Number of params: {model.num_params() / 1e6:0.04f} M") optimizer = optim.Adam(learning_rate=args.lr) train_data, test_data = get_cifar10(args.batch_size) for epoch in range(args.epochs): tr_loss, tr_acc, throughput = train_epoch(model, train_data, optimizer, epoch) - print( + print_zero( + world, " | ".join( ( f"Epoch: {epoch}", - f"avg. Train loss {tr_loss.item():.3f}", - f"avg. Train acc {tr_acc.item():.3f}", - f"Throughput: {throughput.item():.2f} images/sec", + f"avg. Train loss {tr_loss:.3f}", + f"avg. Train acc {tr_acc:.3f}", + f"Throughput: {throughput:.2f} images/sec", ) - ) + ), ) test_acc = test_epoch(model, test_data, epoch) - print(f"Epoch: {epoch} | Test acc {test_acc.item():.3f}") + print_zero(world, f"Epoch: {epoch} | Test acc {test_acc:.3f}") train_data.reset() test_data.reset() From 56d2db23e1348f046fc91d8c8c7794722e9fbe43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6kdeniz=20G=C3=BClmez?= <60228478+Goekdeniz-Guelmez@users.noreply.github.com> Date: Wed, 5 Mar 2025 22:46:06 +0100 Subject: [PATCH 173/188] adding OLMoE architecture (#1321) * initial commit * udpate ACKNOWLEDGMENTS.md * adding olmoe to training * clean up * faster generation * remove sanitize method * more clean ups * adding SwitchGLU * clean up * a little faster and adding norm_topk_prob * formated --- ACKNOWLEDGMENTS.md | 2 +- llms/mlx_lm/models/olmoe.py | 217 ++++++++++++++++++++++++++++++++++++ llms/mlx_lm/tuner/utils.py | 3 + 3 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 llms/mlx_lm/models/olmoe.py diff --git a/ACKNOWLEDGMENTS.md b/ACKNOWLEDGMENTS.md index 851c995c..c6853710 100644 --- a/ACKNOWLEDGMENTS.md +++ b/ACKNOWLEDGMENTS.md @@ -14,4 +14,4 @@ MLX Examples was developed with contributions from the following individuals: - Markus Enzweiler: Added the `cvae` examples. - Prince Canuma: Helped add support for `Starcoder2` models. - Shiyu Li: Added the `Segment Anything Model`. -- Gökdeniz Gülmez: Added support for `MiniCPM`, `Helium`, `Mamba version 1` and support for `full-fine-tuning`. \ No newline at end of file +- Gökdeniz Gülmez: Added support for `MiniCPM`, `Helium`, `Mamba version 1`, `OLMoE` archtectures and support for `full-fine-tuning`. \ No newline at end of file diff --git a/llms/mlx_lm/models/olmoe.py b/llms/mlx_lm/models/olmoe.py new file mode 100644 index 00000000..b9c0fc69 --- /dev/null +++ b/llms/mlx_lm/models/olmoe.py @@ -0,0 +1,217 @@ +# Copyright © 2023-2024 Apple Inc. + +from dataclasses import dataclass +from typing import Any, Dict, Optional, Union + +import mlx.core as mx +import mlx.nn as nn + +from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention +from .rope_utils import initialize_rope +from .switch_layers import SwitchGLU + + +@dataclass +class ModelArgs(BaseModelArgs): + model_type: str + hidden_size: int + num_hidden_layers: int + intermediate_size: int + num_attention_heads: int + rms_norm_eps: float + vocab_size: int + num_experts: int + num_experts_per_tok: int + norm_topk_prob: bool = False + head_dim: Optional[int] = None + max_position_embeddings: Optional[int] = None + num_key_value_heads: Optional[int] = None + attention_bias: bool = False + mlp_bias: bool = False + rope_theta: float = 10000 + rope_traditional: bool = False + rope_scaling: Optional[Dict[str, Union[float, str]]] = None + tie_word_embeddings: bool = True + + def __post_init__(self): + if self.num_key_value_heads is None: + self.num_key_value_heads = self.num_attention_heads + + +class Attention(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + + dim = args.hidden_size + self.n_heads = n_heads = args.num_attention_heads + self.n_kv_heads = n_kv_heads = args.num_key_value_heads + + self.head_dim = head_dim = args.head_dim or args.hidden_size // n_heads + + self.scale = head_dim**-0.5 + + self.q_proj = nn.Linear(dim, n_heads * head_dim, bias=args.attention_bias) + self.k_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=args.attention_bias) + self.v_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=args.attention_bias) + self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=args.attention_bias) + + self.rope = initialize_rope( + self.head_dim, + args.rope_theta, + args.rope_traditional, + args.rope_scaling, + args.max_position_embeddings, + ) + + self.q_norm = nn.RMSNorm(n_heads * head_dim, args.rms_norm_eps) + self.k_norm = nn.RMSNorm(n_kv_heads * head_dim, args.rms_norm_eps) + + def __call__( + self, + x: mx.array, + mask: Optional[mx.array] = None, + cache: Optional[Any] = None, + ) -> mx.array: + B, L, D = x.shape + queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x) + queries = self.q_norm(queries) + keys = self.k_norm(keys) + queries = queries.reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) + keys = keys.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) + values = values.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) + if cache is not None: + queries = self.rope(queries, offset=cache.offset) + keys = self.rope(keys, offset=cache.offset) + keys, values = cache.update_and_fetch(keys, values) + else: + queries = self.rope(queries) + keys = self.rope(keys) + output = scaled_dot_product_attention( + queries, keys, values, cache=cache, scale=self.scale, mask=mask + ) + output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) + return self.o_proj(output) + + +class OlmoeSparseMoeBlock(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.num_experts = args.num_experts + self.top_k = args.num_experts_per_tok + self.norm_topk_prob = args.norm_topk_prob + + self.gate = nn.Linear(args.hidden_size, self.num_experts, bias=False) + self.switch_mlp = SwitchGLU( + args.hidden_size, + args.intermediate_size, + self.num_experts, + bias=args.mlp_bias, + ) + + def __call__(self, x: mx.array) -> mx.array: + B, L, D = x.shape + x_flat = x.reshape(-1, D) + router_logits = self.gate(x_flat) + routing_weights = mx.softmax(router_logits, axis=1, precise=True) + k = self.top_k + indices = mx.stop_gradient( + mx.argpartition(-routing_weights, kth=k - 1, axis=-1)[..., :k] + ) + scores = mx.take_along_axis(routing_weights, indices, axis=-1) + if self.norm_topk_prob: + scores = scores / scores.sum(axis=-1, keepdims=True) + y = self.switch_mlp(x_flat, indices) + y = (y * scores[..., None]).sum(axis=-2) + return y.reshape(B, L, D) + + +class TransformerBlock(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.self_attn = Attention(args) + self.mlp = OlmoeSparseMoeBlock(args) + self.input_layernorm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) + self.post_attention_layernorm = nn.RMSNorm( + args.hidden_size, eps=args.rms_norm_eps + ) + + def __call__( + self, + x: mx.array, + mask: Optional[mx.array] = None, + cache: Optional[Any] = None, + ) -> mx.array: + x = x + self.self_attn(self.input_layernorm(x), mask, cache) + x = x + self.mlp(self.post_attention_layernorm(x)) + return x + + +class OlmoeModel(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.args = args + self.vocab_size = args.vocab_size + self.num_hidden_layers = args.num_hidden_layers + assert self.vocab_size > 0 + self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size) + self.layers = [ + TransformerBlock(args=args) for _ in range(args.num_hidden_layers) + ] + self.norm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) + + def __call__( + self, + inputs: mx.array, + cache=None, + mask=None, + ): + h = self.embed_tokens(inputs) + if mask is None: + mask = create_attention_mask(h, cache) + if cache is None: + cache = [None] * len(self.layers) + for layer, c in zip(self.layers, cache): + h = layer(h, mask, cache=c) + return self.norm(h) + + +class Model(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.args = args + self.model_type = args.model_type + self.model = OlmoeModel(args) + if not args.tie_word_embeddings: + self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=False) + + def __call__( + self, + inputs: mx.array, + cache=None, + mask=None, + ): + out = self.model(inputs, cache, mask) + if self.args.tie_word_embeddings: + out = self.model.embed_tokens.as_linear(out) + else: + out = self.lm_head(out) + return out + + def sanitize(self, weights): + if "model.layers.0.mlp.experts.0.up_proj.weight" not in weights: + return weights + for l in range(self.args.num_hidden_layers): + prefix = f"model.layers.{l}" + for n in ["up_proj", "down_proj", "gate_proj"]: + for k in ["weight", "scales", "biases"]: + if f"{prefix}.mlp.experts.0.{n}.{k}" in weights: + to_join = [ + weights.pop(f"{prefix}.mlp.experts.{e}.{n}.{k}") + for e in range(self.args.num_experts) + ] + weights[f"{prefix}.mlp.switch_mlp.{n}.{k}"] = mx.stack(to_join) + return weights + + @property + def layers(self): + return self.model.layers diff --git a/llms/mlx_lm/tuner/utils.py b/llms/mlx_lm/tuner/utils.py index f5df11e3..cc7c6c20 100644 --- a/llms/mlx_lm/tuner/utils.py +++ b/llms/mlx_lm/tuner/utils.py @@ -98,6 +98,7 @@ def linear_to_lora_layers( "minicpm", "deepseek", "olmo2", + "olmoe", "internlm3", ]: keys = set(["self_attn.q_proj", "self_attn.v_proj"]) @@ -106,6 +107,8 @@ def linear_to_lora_layers( if model.model_type == "qwen2_moe": keys.add("mlp.gate") keys.add("mlp.shared_expert_gate") + if model.model_type == "olmoe": + keys.add("mlp.gate") elif model.model_type == "gpt_bigcode": keys = set(["attn.c_attn"]) From e15062109568571aec0e2f099533ad580f0fcaf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6kdeniz=20G=C3=BClmez?= <60228478+Goekdeniz-Guelmez@users.noreply.github.com> Date: Wed, 5 Mar 2025 22:54:54 +0100 Subject: [PATCH 174/188] Adding multiple optimizers to mlx lm (#1315) * initial commmit * adding more customized YAML configuartion * update YAML example file * Changed the switch to set opt_class * removing muon * using default arguments * udpate --- llms/mlx_lm/examples/lora_config.yaml | 9 +++++++ llms/mlx_lm/lora.py | 34 +++++++++++++++++++++------ 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/llms/mlx_lm/examples/lora_config.yaml b/llms/mlx_lm/examples/lora_config.yaml index 530272c7..36bc1dff 100644 --- a/llms/mlx_lm/examples/lora_config.yaml +++ b/llms/mlx_lm/examples/lora_config.yaml @@ -7,6 +7,15 @@ train: true # The fine-tuning method: "lora", "dora", or "full". fine_tune_type: lora +# The Optimizer with its possible inputs +optimizer: adamw +# optimizer_config: +# adamw: +# betas: [0.9, 0.98] +# eps: 1e-6 +# weight_decay: 0.05 +# bias_correction: true + # Directory with {train, valid, test}.jsonl files data: "/path/to/training/data" diff --git a/llms/mlx_lm/lora.py b/llms/mlx_lm/lora.py index d32bfe6d..042b40e2 100644 --- a/llms/mlx_lm/lora.py +++ b/llms/mlx_lm/lora.py @@ -43,6 +43,11 @@ CONFIG_DEFAULTS = { "model": "mlx_model", "train": False, "fine_tune_type": "lora", + "optimizer": "adam", + "optimizer_config": { + "adam": {}, + "adamw": {}, + }, "data": "data/", "seed": 0, "num_layers": 16, @@ -95,14 +100,19 @@ def build_parser(): choices=["lora", "dora", "full"], help="Type of fine-tuning to perform: lora, dora, or full.", ) - + parser.add_argument( + "--optimizer", + type=str, + choices=["adam", "adamw"], + default=None, + help="Optimizer to use for training: adam or adamw", + ) parser.add_argument( "--mask-prompt", action="store_true", help="Mask the prompt in the loss when training", default=None, ) - parser.add_argument( "--num-layers", type=int, @@ -229,11 +239,21 @@ def train_model( ) model.train() - opt = optim.Adam( - learning_rate=( - build_schedule(args.lr_schedule) if args.lr_schedule else args.learning_rate - ) - ) + + # Initialize the selected optimizer + lr = build_schedule(args.lr_schedule) if args.lr_schedule else args.learning_rate + + optimizer_name = args.optimizer.lower() + optimizer_config = args.optimizer_config.get(optimizer_name, {}) + + if optimizer_name == "adam": + opt_class = optim.Adam + elif optimizer_name == "adamw": + opt_class = optim.AdamW + else: + raise ValueError(f"Unsupported optimizer: {optimizer_name}") + + opt = opt_class(learning_rate=lr, **optimizer_config) # Train model train( From 32d10036de94af07733c247ca44702e8135d068a Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Wed, 5 Mar 2025 14:00:09 -0800 Subject: [PATCH 175/188] fix flaky test (#1322) --- llms/tests/test_prompt_cache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llms/tests/test_prompt_cache.py b/llms/tests/test_prompt_cache.py index de5694d5..c1860892 100644 --- a/llms/tests/test_prompt_cache.py +++ b/llms/tests/test_prompt_cache.py @@ -298,7 +298,7 @@ class TestPromptCache(unittest.TestCase): ): i += 1 self.assertEqual(tok, toks[i]) - self.assertTrue(mx.allclose(logits, all_logits[i], rtol=2e-2)) + self.assertTrue(mx.allclose(logits, all_logits[i], rtol=3e-2)) if __name__ == "__main__": From 877d2a345b8119ad9ed50e2c273a5064ddd3b48c Mon Sep 17 00:00:00 2001 From: cavit99 <35897738+cavit99@users.noreply.github.com> Date: Thu, 6 Mar 2025 14:49:35 +0000 Subject: [PATCH 176/188] Change DEFAULT_SEED to None for stochastic generation by default (#1323) * Change DEFAULT_SEED to None for stochastic generation by default * Update llms/mlx_lm/chat.py * Update llms/mlx_lm/generate.py --------- Co-authored-by: Awni Hannun --- llms/mlx_lm/chat.py | 12 +++++++++--- llms/mlx_lm/generate.py | 13 ++++++++++--- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/llms/mlx_lm/chat.py b/llms/mlx_lm/chat.py index 5c0b78db..d8e1ccb9 100644 --- a/llms/mlx_lm/chat.py +++ b/llms/mlx_lm/chat.py @@ -11,7 +11,7 @@ from .utils import load, stream_generate DEFAULT_TEMP = 0.0 DEFAULT_TOP_P = 1.0 -DEFAULT_SEED = 0 +DEFAULT_SEED = None DEFAULT_MAX_TOKENS = 256 DEFAULT_MODEL = "mlx-community/Llama-3.2-3B-Instruct-4bit" @@ -36,7 +36,12 @@ def setup_arg_parser(): parser.add_argument( "--top-p", type=float, default=DEFAULT_TOP_P, help="Sampling top-p" ) - parser.add_argument("--seed", type=int, default=DEFAULT_SEED, help="PRNG seed") + parser.add_argument( + "--seed", + type=int, + default=DEFAULT_SEED, + help="PRNG seed", + ) parser.add_argument( "--max-kv-size", type=int, @@ -57,7 +62,8 @@ def main(): parser = setup_arg_parser() args = parser.parse_args() - mx.random.seed(args.seed) + if args.seed is not None: + mx.random.seed(args.seed) model, tokenizer = load( args.model, diff --git a/llms/mlx_lm/generate.py b/llms/mlx_lm/generate.py index bd11dcf0..7d58da82 100644 --- a/llms/mlx_lm/generate.py +++ b/llms/mlx_lm/generate.py @@ -16,7 +16,7 @@ DEFAULT_TEMP = 0.0 DEFAULT_TOP_P = 1.0 DEFAULT_MIN_P = 0.0 DEFAULT_MIN_TOKENS_TO_KEEP = 1 -DEFAULT_SEED = 0 +DEFAULT_SEED = None DEFAULT_MODEL = "mlx-community/Llama-3.2-3B-Instruct-4bit" DEFAULT_QUANTIZED_KV_START = 5000 @@ -87,7 +87,12 @@ def setup_arg_parser(): default=DEFAULT_MIN_TOKENS_TO_KEEP, help="Minimum tokens to keep for min-p sampling.", ) - parser.add_argument("--seed", type=int, default=DEFAULT_SEED, help="PRNG seed") + parser.add_argument( + "--seed", + type=int, + default=DEFAULT_SEED, + help="PRNG seed", + ) parser.add_argument( "--ignore-chat-template", action="store_true", @@ -160,7 +165,9 @@ def setup_arg_parser(): def main(): parser = setup_arg_parser() args = parser.parse_args() - mx.random.seed(args.seed) + + if args.seed is not None: + mx.random.seed(args.seed) # Load the prompt cache and metadata if a cache file is provided using_cache = args.prompt_cache_file is not None From 595f5da146bbf305b14fe18d343fe2777aa8a1ba Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Thu, 6 Mar 2025 15:35:47 -0800 Subject: [PATCH 177/188] remove lm head if unused (#1324) --- llms/mlx_lm/models/llama.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/llms/mlx_lm/models/llama.py b/llms/mlx_lm/models/llama.py index 7b452ea4..117adf0f 100644 --- a/llms/mlx_lm/models/llama.py +++ b/llms/mlx_lm/models/llama.py @@ -196,9 +196,12 @@ class Model(nn.Module): def sanitize(self, weights): # Remove unused precomputed rotary freqs - return { + weights = { k: v for k, v in weights.items() if "self_attn.rotary_emb.inv_freq" not in k } + if self.args.tie_word_embeddings: + weights.pop("lm_head.weight", None) + return weights @property def layers(self): From d2e02b3aae9741eea6f9c6123624406de3f10015 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Fri, 7 Mar 2025 08:35:48 -0800 Subject: [PATCH 178/188] fix mixed quant option (#1326) --- llms/mlx_lm/convert.py | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/llms/mlx_lm/convert.py b/llms/mlx_lm/convert.py index 86a96447..f268913b 100644 --- a/llms/mlx_lm/convert.py +++ b/llms/mlx_lm/convert.py @@ -1,27 +1,23 @@ # Copyright © 2023-2024 Apple Inc. import argparse -from enum import Enum -from .utils import convert, mixed_2_6, mixed_3_6 +from . import utils +from .utils import convert - -class MixedQuants(Enum): - mixed_3_6 = "mixed_3_6" - mixed_2_6 = "mixed_2_6" - - @classmethod - def recipe_names(cls): - return [member.name for member in cls] +QUANT_RECIPES = [ + "mixed_2_6", + "mixed_3_6", +] def quant_args(arg): - try: - return MixedQuants[arg].value - except KeyError: + if arg not in QUANT_RECIPES: raise argparse.ArgumentTypeError( - f"Invalid q-recipe {arg!r}. Choose from: {MixedQuants.recipe_names()}" + f"Invalid q-recipe {arg!r}. Choose from: {QUANT_RECIPES}" ) + else: + return getattr(utils, arg) def configure_parser() -> argparse.ArgumentParser: @@ -50,7 +46,7 @@ def configure_parser() -> argparse.ArgumentParser: ) parser.add_argument( "--quant-predicate", - help=f"Mixed-bit quantization recipe. Choices: {MixedQuants.recipe_names()}", + help=f"Mixed-bit quantization recipe. Choices: {QUANT_RECIPES}", type=quant_args, required=False, ) From 4c3df001624e671e6d1f5576305daa8a21b2997e Mon Sep 17 00:00:00 2001 From: Neil Mehta Date: Tue, 11 Mar 2025 16:37:35 -0400 Subject: [PATCH 179/188] `make_sampler` creates sampler chain with all sampling parameters (#1330) * top_p refactor * top_k and min_p refactor * Create sampler chain * Remove unnecessary mx.where * Use mx.allclose --- llms/mlx_lm/sample_utils.py | 67 ++++++++++++------- llms/tests/test_sample_utils.py | 110 +++++++++++++++++++------------- 2 files changed, 107 insertions(+), 70 deletions(-) diff --git a/llms/mlx_lm/sample_utils.py b/llms/mlx_lm/sample_utils.py index 23e08d97..efc5b556 100644 --- a/llms/mlx_lm/sample_utils.py +++ b/llms/mlx_lm/sample_utils.py @@ -35,14 +35,25 @@ def make_sampler( """ if temp == 0: return lambda x: mx.argmax(x, axis=-1) - elif top_p > 0 and top_p < 1.0: - return lambda x: top_p_sampling(x, top_p, temp) - elif min_p != 0.0: - return lambda x: min_p_sampling(x, min_p, min_tokens_to_keep, temp) - elif top_k > 0: - return lambda x: top_k_sampling(x, top_k, temp) - else: - return lambda x: categorical_sampling(x, temp) + + # Create sampler chain + sampling_methods = [] + if top_k > 0: + sampling_methods.append(lambda x: apply_top_k(x, top_k)) + if top_p > 0 and top_p < 1.0: + sampling_methods.append(lambda x: apply_top_p(x, top_p)) + if min_p != 0.0: + sampling_methods.append(lambda x: apply_min_p(x, min_p, min_tokens_to_keep)) + + # Apply the sampling methods + def sampler(logits): + for method in sampling_methods: + logits = method(logits) + + # Return the sampled token + return categorical_sampling(logits, temp) + + return sampler def make_logits_processors( @@ -85,10 +96,9 @@ def make_logits_processors( @partial(mx.compile, inputs=mx.random.state, outputs=mx.random.state) -def top_k_sampling( +def apply_top_k( logprobs: mx.array, top_k: int, - temperature=1.0, ) -> mx.array: """ Sample from only the top K tokens ranked by probability. @@ -103,20 +113,18 @@ def top_k_sampling( f"`top_k` has to be an integer in the (0, {vocab_size}] interval," f" but is {top_k}." ) - logprobs = logprobs * (1 / temperature) mask_idx = mx.argpartition(-logprobs, kth=top_k - 1, axis=-1)[..., top_k:] masked_logprobs = mx.put_along_axis( logprobs, mask_idx, mx.array(-float("inf"), logprobs.dtype), axis=-1 ) - return mx.random.categorical(masked_logprobs, axis=-1) + return masked_logprobs @partial(mx.compile, inputs=mx.random.state, outputs=mx.random.state) -def min_p_sampling( +def apply_min_p( logprobs: mx.array, min_p: float, min_tokens_to_keep: int = 1, - temperature=1.0, ) -> mx.array: """ Apply min-p sampling to the logprobs. @@ -144,8 +152,6 @@ def min_p_sampling( ) # reference implementation: https://github.com/huggingface/transformers/blob/main/src/transformers/generation/logits_process.py#L531-L605 - logprobs = logprobs * (1 / temperature) - # Indices sorted in decreasing order sorted_indices = mx.argsort(-logprobs, axis=-1) sorted_logprobs = mx.take_along_axis(logprobs, sorted_indices, axis=-1) @@ -163,25 +169,31 @@ def min_p_sampling( # Create pool of tokens with probability less than scaled min_p selected_logprobs = mx.where(tokens_to_remove, -float("inf"), sorted_logprobs) - # Return sampled tokens - sorted_tokens = mx.random.categorical(selected_logprobs, axis=-1)[:, None] - return mx.take_along_axis(sorted_indices, sorted_tokens, axis=-1).squeeze(1) + # Create a mapping to rearrange back to original indices + # Use argsort of sorted_indices to get the inverse permutation + inverse_indices = mx.argsort(sorted_indices, axis=-1) + + # Rearrange selected_logprobs back to original order + original_order_logprobs = mx.take_along_axis( + selected_logprobs, inverse_indices, axis=-1 + ) + + return original_order_logprobs @partial(mx.compile, inputs=mx.random.state, outputs=mx.random.state) -def top_p_sampling(logits: mx.array, top_p: float, temperature: float) -> mx.array: +def apply_top_p(logits: mx.array, top_p: float) -> mx.array: """ Apply top-p (nucleus) sampling to logits. Args: logits: The logits from the model's output. top_p: The cumulative probability threshold for top-p filtering. - temperature: Temperature parameter for softmax distribution reshaping. Returns: token selected based on the top-p criterion. """ # referenced implementation from https://github.com/huggingface/transformers/blob/main/src/transformers/generation/logits_process.py#L449-L460 - probs = mx.softmax(logits * (1 / temperature), axis=-1) + probs = mx.softmax(logits, axis=-1) # sort probs in ascending order sorted_indices = mx.argsort(probs, axis=-1) @@ -196,8 +208,15 @@ def top_p_sampling(logits: mx.array, top_p: float, temperature: float) -> mx.arr 0, ) - sorted_tokens = mx.random.categorical(mx.log(top_probs), axis=-1)[:, None] - return mx.take_along_axis(sorted_indices, sorted_tokens, axis=-1).squeeze(1) + # Create a mapping to rearrange back to original indices + # Use argsort of sorted_indices to get the inverse permutation + inverse_indices = mx.argsort(sorted_indices, axis=-1) + + # Rearrange top_probs back to original order + original_order_probs = mx.take_along_axis(top_probs, inverse_indices, axis=-1) + + # Convert back to logits and return + return mx.log(original_order_probs) @partial(mx.compile, inputs=mx.random.state, outputs=mx.random.state) diff --git a/llms/tests/test_sample_utils.py b/llms/tests/test_sample_utils.py index f12abbf4..7760c569 100644 --- a/llms/tests/test_sample_utils.py +++ b/llms/tests/test_sample_utils.py @@ -1,79 +1,97 @@ import unittest import mlx.core as mx -from mlx_lm.sample_utils import min_p_sampling, top_k_sampling, top_p_sampling +from mlx_lm.sample_utils import apply_min_p, apply_top_k, apply_top_p class TestSampleUtils(unittest.TestCase): - def test_top_p_sampling(self): + def test_apply_top_p(self): probs = mx.array([0.9, 0.0, 0.0, 0.1])[None] logits = mx.log(probs) - temperature = 1.0 - token = top_p_sampling(logits, 0.3, temperature).item() - self.assertEqual(token, 0) + new_logits = apply_top_p(logits, 0.3) + actual_probs = mx.softmax(new_logits.squeeze()) + self.assertEqual(actual_probs.tolist(), [1.0, 0.0, 0.0, 0.0]) - token = top_p_sampling(logits, 0.95, temperature).item() - self.assertTrue(token in (0, 3)) + new_logits = apply_top_p(logits, 0.95) + actual_probs = mx.softmax(new_logits.squeeze()) + self.assertTrue(mx.allclose(probs.squeeze(), actual_probs)) probs = mx.array([0.0, 0.5, 0.4, 0.1])[None] logits = mx.log(probs) + new_logits = apply_top_p(logits, 0.4) + actual_probs = mx.softmax(new_logits.squeeze()) + self.assertEqual(actual_probs.tolist(), [0.0, 1.0, 0.0, 0.0]) - token = top_p_sampling(logits, 0.4, temperature).item() - self.assertEqual(token, 1) + new_logits = apply_top_p(logits, 0.6) + actual_probs = mx.softmax(new_logits.squeeze()) + self.assertEqual( + [round(p, 4) for p in actual_probs.tolist()], [0.0, 0.5556, 0.4444, 0.0] + ) - token = top_p_sampling(logits, 0.6, temperature).item() - self.assertTrue(token in (1, 2)) + new_logits = apply_top_p(logits, 0.95) + actual_probs = mx.softmax(new_logits.squeeze()) + actual_rounded = [round(p, 4) for p in actual_probs.tolist()] + expected_rounded = [0.0, 0.5, 0.4, 0.1] + self.assertEqual(actual_rounded, expected_rounded) + self.assertAlmostEqual(sum(actual_probs.tolist()), 1.0) - token = top_p_sampling(logits, 0.95, temperature).item() - self.assertTrue(token in (1, 2, 3)) + # Batch mode works + probs = mx.array([[0.9, 0.0, 0.0, 0.1], [0.0, 0.8, 0.1, 0.1]]) + logits = mx.log(probs) + new_logits = apply_top_p(logits, 0.5) + actual_probs = mx.softmax(new_logits, axis=-1) + self.assertEqual( + actual_probs.tolist(), [[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0]] + ) + + def test_apply_min_p(self): + probs = mx.array([0.9, 0.0, 0.0, 0.1])[None] + logits = mx.log(probs) + new_logits = apply_min_p(logits, 0.8) + actual_probs = mx.softmax(new_logits.squeeze()) + self.assertEqual(actual_probs.tolist(), [1.0, 0.0, 0.0, 0.0]) + + probs = mx.array([0.9, 0.0, 0.0, 0.1])[None] + logits = mx.log(probs) + new_logits = apply_min_p(logits, 0.05) + actual_probs = mx.softmax(new_logits.squeeze()) + self.assertTrue(mx.allclose(actual_probs, mx.squeeze(probs))) # Batch mode works probs = mx.array([[0.9, 0.0, 0.0, 0.1], [0.0, 0.8, 0.0, 0.1]]) logits = mx.log(probs) - tokens = top_p_sampling(logits, 0.5, temperature) - self.assertEqual(tokens.tolist(), [0, 1]) + new_logits = apply_min_p(logits, 0.7) + actual_probs = mx.softmax(new_logits, axis=-1) + self.assertEqual( + actual_probs.tolist(), [[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0]] + ) - def test_min_p_sampling(self): - probs = mx.array([0.9, 0.0, 0.0, 0.1])[None] - logits = mx.log(probs) - temperature = 1.0 - token = min_p_sampling(logits, 0.8) - self.assertEqual(token, 0) - - probs = mx.array([0.9, 0.0, 0.0, 0.1])[None] - logits = mx.log(probs) - temperature = 1.0 - for _ in range(5): - token = min_p_sampling(logits, 0.05) - self.assertTrue(token in (0, 3)) - - # Batch mode works - probs = mx.array([[0.9, 0.0, 0.0, 0.1], [0.0, 0.8, 0.0, 0.1]]) - logits = mx.log(probs) - tokens = min_p_sampling(logits, 0.7) - self.assertEqual(tokens.tolist(), [0, 1]) - - def test_top_k_sampling(self): + def test_apply_top_k(self): probs = mx.array([0.9, 0.0, 0.0, 0.1])[None] logits = mx.log(probs) - token = top_k_sampling(logits, 1).item() - self.assertEqual(token, 0) + new_logits = apply_top_k(logits, 1) + actual_probs = mx.softmax(new_logits.squeeze()) + self.assertEqual(actual_probs.tolist(), [1.0, 0.0, 0.0, 0.0]) - probs = mx.array([0.5, 0.0, 0.0, 0.5])[None] - tokens = set() - for _ in range(100): - token = top_k_sampling(logits, 2) - tokens.add(token.item()) - self.assertEqual(tokens, {0, 3}) + probs = mx.array([0.6, 0.0, 0.1, 0.3])[None] + logits = mx.log(probs) + new_logits = apply_top_k(logits, 2) + actual_probs = mx.softmax(new_logits.squeeze()) + self.assertEqual( + [round(p, 4) for p in actual_probs.tolist()], [0.6667, 0.0, 0.0, 0.3333] + ) # Batch mode works probs = mx.array([[0.9, 0.0, 0.0, 0.1], [0.0, 0.8, 0.0, 0.1]]) logits = mx.log(probs) - tokens = top_k_sampling(logits, 1) - self.assertEqual(tokens.tolist(), [0, 1]) + new_logits = apply_top_k(logits, 1) + actual_probs = mx.softmax(new_logits, axis=-1) + self.assertEqual( + actual_probs.tolist(), [[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0]] + ) if __name__ == "__main__": From 3e5baf583bc51456715652585df2faeb13851e77 Mon Sep 17 00:00:00 2001 From: Mirko Nasato Date: Thu, 13 Mar 2025 02:17:14 +0000 Subject: [PATCH 180/188] Make sure to use UTF-8 when loading tokenizer.json (#1340) --- llms/mlx_lm/tokenizer_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llms/mlx_lm/tokenizer_utils.py b/llms/mlx_lm/tokenizer_utils.py index de9d5324..b33d504b 100644 --- a/llms/mlx_lm/tokenizer_utils.py +++ b/llms/mlx_lm/tokenizer_utils.py @@ -352,7 +352,7 @@ def load_tokenizer(model_path, tokenizer_config_extra={}, eos_token_ids=None): tokenizer_file = model_path / "tokenizer.json" if tokenizer_file.exists(): - with open(tokenizer_file, "r") as fid: + with open(tokenizer_file, "r", encoding="utf-8") as fid: tokenizer_content = json.load(fid) if "decoder" in tokenizer_content: if _is_spm_decoder(tokenizer_content["decoder"]): From 2fce02acd87193487eafeeec639eb8903cc96083 Mon Sep 17 00:00:00 2001 From: Prince Canuma Date: Thu, 13 Mar 2025 16:14:25 +0100 Subject: [PATCH 181/188] Add support for Gemma3 (#1336) * add support for gemma3 * fix model loading * revert rmsnorm * revert is sliding pattern * revert * add tests * formatting * Update llms/mlx_lm/models/gemma3_text.py Co-authored-by: Awni Hannun * Update llms/mlx_lm/models/gemma3_text.py Co-authored-by: Awni Hannun * Update llms/mlx_lm/models/gemma3_text.py Co-authored-by: Awni Hannun * Update llms/mlx_lm/models/gemma3_text.py Co-authored-by: Awni Hannun * Update llms/mlx_lm/models/gemma3_text.py Co-authored-by: Awni Hannun * Update llms/mlx_lm/models/gemma3_text.py Co-authored-by: Awni Hannun * Update llms/mlx_lm/models/gemma3_text.py Co-authored-by: Awni Hannun * fix sliding window mask --------- Co-authored-by: Awni Hannun Co-authored-by: Awni Hannun --- llms/mlx_lm/models/gemma3_text.py | 238 ++++++++++++++++++++++++++++++ llms/tests/test_models.py | 20 +++ 2 files changed, 258 insertions(+) create mode 100644 llms/mlx_lm/models/gemma3_text.py diff --git a/llms/mlx_lm/models/gemma3_text.py b/llms/mlx_lm/models/gemma3_text.py new file mode 100644 index 00000000..be71f461 --- /dev/null +++ b/llms/mlx_lm/models/gemma3_text.py @@ -0,0 +1,238 @@ +# Copyright © 2025 Apple Inc. + +from dataclasses import dataclass +from typing import Any, Optional + +import mlx.core as mx +import mlx.nn as nn + +from .base import BaseModelArgs, create_attention_mask +from .cache import KVCache, RotatingKVCache + + +@dataclass +class ModelArgs(BaseModelArgs): + model_type: str + hidden_size: int = 1152 + num_hidden_layers: int = 26 + intermediate_size: int = 6912 + num_attention_heads: int = 4 + head_dim: int = 256 + rms_norm_eps: float = 1.0e-6 + vocab_size: int = 262144 + num_key_value_heads: int = 1 + rope_global_base_freq: float = 1_000_000.0 + rope_local_base_freq: float = 10_000.0 + rope_traditional: bool = False + query_pre_attn_scalar: float = 256 + sliding_window: int = 512 + sliding_window_pattern: int = 6 + + +class Attention(nn.Module): + def __init__(self, args: ModelArgs, layer_idx: int): + super().__init__() + + dim = args.hidden_size + self.n_heads = n_heads = args.num_attention_heads + self.n_kv_heads = n_kv_heads = args.num_key_value_heads + self.repeats = n_heads // n_kv_heads + self.head_dim = head_dim = args.head_dim + self.layer_idx = layer_idx + + self.scale = args.query_pre_attn_scalar**-0.5 + + self.q_proj = nn.Linear(dim, n_heads * head_dim, bias=False) + self.k_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=False) + self.v_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=False) + self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=False) + + self.q_norm = RMSNorm(dims=head_dim, eps=args.rms_norm_eps) + self.k_norm = RMSNorm(dims=head_dim, eps=args.rms_norm_eps) + self.is_sliding = (layer_idx + 1) % args.sliding_window_pattern != 0 + + self.rope = nn.RoPE( + head_dim, + traditional=args.rope_traditional, + base=( + args.rope_local_base_freq + if self.is_sliding + else args.rope_global_base_freq + ), + ) + + def __call__( + self, + x: mx.array, + mask: Optional[mx.array] = None, + cache: Optional[Any] = None, + ) -> mx.array: + B, L, _ = x.shape + queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x) + queries = queries.reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) + + keys = keys.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) + values = values.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) + + queries = self.q_norm(queries) + keys = self.k_norm(keys) + + if cache is not None: + queries = self.rope(queries, offset=cache.offset) + keys = self.rope(keys, offset=cache.offset) + keys, values = cache.update_and_fetch(keys, values) + else: + queries = self.rope(queries) + keys = self.rope(keys) + + # Sliding window + if mask is not None and mask.shape[-1] != keys.shape[-2]: + mask = mask[..., -keys.shape[-2] :] + + output = mx.fast.scaled_dot_product_attention( + queries, keys, values, scale=self.scale, mask=mask + ) + output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) + return self.o_proj(output) + + +class RMSNorm(nn.Module): + def __init__(self, dims: int, eps: float = 1e-5): + super().__init__() + self.weight = mx.ones((dims,)) + self.eps = eps + + def __call__(self, x): + return mx.fast.rms_norm(x, 1.0 + self.weight, self.eps) + + +class MLP(nn.Module): + def __init__(self, dim, hidden_dim): + super().__init__() + self.gate_proj = nn.Linear(dim, hidden_dim, bias=False) + self.down_proj = nn.Linear(hidden_dim, dim, bias=False) + self.up_proj = nn.Linear(dim, hidden_dim, bias=False) + + def __call__(self, x) -> mx.array: + return self.down_proj(nn.gelu_approx(self.gate_proj(x)) * self.up_proj(x)) + + +class TransformerBlock(nn.Module): + def __init__(self, args: ModelArgs, layer_idx: int): + super().__init__() + self.num_attention_heads = args.num_attention_heads + self.hidden_size = args.hidden_size + self.self_attn = Attention(args, layer_idx) + self.mlp = MLP(args.hidden_size, args.intermediate_size) + self.input_layernorm = RMSNorm(args.hidden_size, eps=args.rms_norm_eps) + self.post_attention_layernorm = RMSNorm(args.hidden_size, eps=args.rms_norm_eps) + self.pre_feedforward_layernorm = RMSNorm( + args.hidden_size, eps=args.rms_norm_eps + ) + self.post_feedforward_layernorm = RMSNorm( + args.hidden_size, eps=args.rms_norm_eps + ) + + def __call__( + self, + x: mx.array, + mask: Optional[mx.array] = None, + cache: Optional[Any] = None, + ) -> mx.array: + r = self.self_attn(self.input_layernorm(x), mask, cache) + h = x + self.post_attention_layernorm(r) + r = self.mlp(self.pre_feedforward_layernorm(h)) + out = h + self.post_feedforward_layernorm(r) + return out + + +class Gemma3Model(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.args = args + self.vocab_size = args.vocab_size + self.num_hidden_layers = args.num_hidden_layers + assert self.vocab_size > 0 + self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size) + self.layers = [ + TransformerBlock(args=args, layer_idx=layer_idx) + for layer_idx in range(args.num_hidden_layers) + ] + self.norm = RMSNorm(args.hidden_size, eps=args.rms_norm_eps) + + def __call__( + self, + inputs: mx.array, + mask: mx.array = None, + cache=None, + ): + + h = self.embed_tokens(inputs) + h *= mx.array(self.args.hidden_size**0.5, mx.bfloat16).astype(h.dtype) + + if cache is None: + cache = [None] * len(self.layers) + + if mask is None: + j = self.args.sliding_window_pattern + full_mask = create_attention_mask(h, cache[j - 1 : j]) + sliding_window_mask = create_attention_mask(h, cache) + + for i, (layer, c) in enumerate(zip(self.layers, cache)): + is_sliding = ( + i % self.args.sliding_window_pattern + == self.args.sliding_window_pattern - 1 + ) + + if mask is None and is_sliding: + mask = sliding_window_mask + elif mask is None: + mask = full_mask + + h = layer(h, mask, c) + + return self.norm(h) + + +class Model(nn.Module): + def __init__(self, args: ModelArgs): + super().__init__() + self.args = args + self.model_type = args.model_type + self.model = Gemma3Model(args) + self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=False) + + def __call__( + self, + inputs: mx.array, + cache=None, + mask: Optional[mx.array] = None, + ): + out = self.model(inputs, mask, cache) + out = self.lm_head(out) + return out + + def sanitize(self, weights): + if "lm_head.weight" not in weights: + weights["lm_head.weight"] = weights["model.embed_tokens.weight"] + return { + k: v for k, v in weights.items() if "self_attn.rotary_emb.inv_freq" not in k + } + + @property + def layers(self): + return self.model.layers + + def make_cache(self): + caches = [] + for i in range(self.args.num_hidden_layers): + if ( + i % self.args.sliding_window_pattern + == self.args.sliding_window_pattern - 1 + ): + caches.append(KVCache()) + else: + caches.append( + RotatingKVCache(max_size=self.args.sliding_window, keep=0) + ) + return caches diff --git a/llms/tests/test_models.py b/llms/tests/test_models.py index 0c0fc601..b4e7aab8 100644 --- a/llms/tests/test_models.py +++ b/llms/tests/test_models.py @@ -755,6 +755,26 @@ class TestModels(unittest.TestCase): model, args.model_type, args.vocab_size, args.num_hidden_layers ) + def test_gemma3_text(self): + from mlx_lm.models import gemma3_text + + args = gemma3_text.ModelArgs( + model_type="gemma3_text", + hidden_size=128, + num_hidden_layers=12, + intermediate_size=256, + num_attention_heads=4, + head_dim=32, + rms_norm_eps=1e-4, + num_key_value_heads=1, + sliding_window=1024, + sliding_window_pattern=6, + ) + model = gemma3_text.Model(args) + self.model_test_runner( + model, args.model_type, args.vocab_size, args.num_hidden_layers + ) + def test_gpt_bigcode(self): from mlx_lm.models import gpt_bigcode From d9e1d9c0ef385ef2099997a9c95705b52fed9916 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Sun, 16 Mar 2025 15:14:28 -0700 Subject: [PATCH 182/188] mlx-lm move notice (#1346) * mlx-lm move notice * remove mlx lm tests --- .circleci/config.yml | 25 ------------------------- README.md | 7 ++++--- llms/README.md | 6 ++++++ llms/mlx_lm/README.md | 6 ++++++ 4 files changed, 16 insertions(+), 28 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8367281e..42a39194 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -17,30 +17,6 @@ jobs: pre-commit run --all if ! git diff --quiet; then echo 'Style checks failed, please install pre-commit and run pre-commit run --all and push the change'; exit 1; fi - mlx_lm_build_and_test: - macos: - xcode: "15.2.0" - resource_class: macos.m1.large.gen1 - steps: - - checkout - - run: - name: Install dependencies - command: | - brew install python@3.9 - python3.9 -m venv env - source env/bin/activate - pip install --upgrade pip - pip install unittest-xml-reporting - cd llms/ - pip install -e ".[test]" - - run: - name: Run Python tests - command: | - source env/bin/activate - python -m xmlrunner discover -v llms/tests -o test-results/ - - store_test_results: - path: test-results - workflows: build_and_test: when: @@ -48,7 +24,6 @@ workflows: pattern: "^(?!pull/)[-\\w]+$" value: << pipeline.git.branch >> jobs: - - mlx_lm_build_and_test - linux_build_and_test prb: diff --git a/README.md b/README.md index e47bd598..9b88bd34 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,12 @@ This repo contains a variety of standalone examples using the [MLX framework](https://github.com/ml-explore/mlx). The [MNIST](mnist) example is a good starting point to learn how to use MLX. - -Some more useful examples are listed below. +Some more useful examples are listed below. Check-out [MLX +LM](https://github.com/ml-explore/mlx-lm) for a more fully featured Python +package for LLMs with MLX. ### Text Models -- [MLX LM](llms/README.md) a package for LLM text generation, fine-tuning, and more. - [Transformer language model](transformer_lm) training. - Minimal examples of large scale text generation with [LLaMA](llms/llama), [Mistral](llms/mistral), and more in the [LLMs](llms) directory. @@ -30,6 +30,7 @@ Some more useful examples are listed below. - Speech recognition with [OpenAI's Whisper](whisper). - Audio compression and generation with [Meta's EnCodec](encodec). +- Music generation with [Meta's MusicGen](musicgen). ### Multimodal models diff --git a/llms/README.md b/llms/README.md index e2d1db59..c9283a0d 100644 --- a/llms/README.md +++ b/llms/README.md @@ -1,3 +1,9 @@ +# DEPRECATED + +The mlx-lm package has moved to a [new repo](https://github.com/ml-explore/mlx-lm). + +The package here will be removed soon. Send new contributions and issues to the MLX LM repo. + ## Generate Text with LLMs and MLX The easiest way to get started is to install the `mlx-lm` package: diff --git a/llms/mlx_lm/README.md b/llms/mlx_lm/README.md index 66f2b5e9..fd11a8f2 100644 --- a/llms/mlx_lm/README.md +++ b/llms/mlx_lm/README.md @@ -1,3 +1,9 @@ +# DEPRECATED + +The mlx-lm package has moved to a [new repo](https://github.com/ml-explore/mlx-lm). + +The package here will be removed soon. Send new contributions and issues to the MLX LM repo. + ## Generate Text with MLX and :hugs: Hugging Face This an example of large language model text generation that can pull models from From 7ca05d2e510ea0d150986ba5810de0d5c02fc068 Mon Sep 17 00:00:00 2001 From: Tingzhen Date: Sun, 16 Mar 2025 23:02:52 -0400 Subject: [PATCH 183/188] LoRa/README.md should be --hf-path instead of --hf-repo (#1350) Co-authored-by: du tingzhen --- lora/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lora/README.md b/lora/README.md index 5c4b5ee8..844d8b28 100644 --- a/lora/README.md +++ b/lora/README.md @@ -166,7 +166,7 @@ useful for the sake of attribution and model versioning. For example, to fuse and upload a model derived from Mistral-7B-v0.1, run: ``` -python fuse.py --upload-name My-4-bit-model --hf-repo mistralai/Mistral-7B-v0.1 +python fuse.py --upload-name My-4-bit-model --hf-path mistralai/Mistral-7B-v0.1 ``` ## Custom Data From c243370044509bcb88fc702e1babbaa7da27add7 Mon Sep 17 00:00:00 2001 From: Awni Hannun Date: Tue, 18 Mar 2025 18:47:55 -0700 Subject: [PATCH 184/188] remove mlx lm (#1353) --- llms/CONTRIBUTING.md | 47 - llms/MANIFEST.in | 2 - llms/README.md | 300 +----- llms/mlx_lm/LORA.md | 392 -------- llms/mlx_lm/MANAGE.md | 22 - llms/mlx_lm/MERGE.md | 50 - llms/mlx_lm/README.md | 16 - llms/mlx_lm/SERVER.md | 131 --- llms/mlx_lm/UPLOAD.md | 37 - llms/mlx_lm/__init__.py | 9 - llms/mlx_lm/_version.py | 3 - llms/mlx_lm/cache_prompt.py | 161 --- llms/mlx_lm/chat.py | 108 -- llms/mlx_lm/convert.py | 83 -- llms/mlx_lm/evaluate.py | 392 -------- llms/mlx_lm/examples/chat.py | 47 - llms/mlx_lm/examples/generate_response.py | 33 - llms/mlx_lm/examples/lora_config.yaml | 89 -- llms/mlx_lm/examples/merge_config.yaml | 11 - llms/mlx_lm/examples/pipeline_generate.py | 127 --- llms/mlx_lm/examples/tool_use.py | 73 -- llms/mlx_lm/fuse.py | 130 --- llms/mlx_lm/generate.py | 287 ------ llms/mlx_lm/gguf.py | 314 ------ llms/mlx_lm/lora.py | 335 ------ llms/mlx_lm/manage.py | 139 --- llms/mlx_lm/merge.py | 172 ---- llms/mlx_lm/models/__init__.py | 0 llms/mlx_lm/models/base.py | 120 --- llms/mlx_lm/models/cache.py | 438 -------- llms/mlx_lm/models/cohere.py | 195 ---- llms/mlx_lm/models/cohere2.py | 206 ---- llms/mlx_lm/models/dbrx.py | 254 ----- llms/mlx_lm/models/deepseek.py | 261 ----- llms/mlx_lm/models/deepseek_v2.py | 462 --------- llms/mlx_lm/models/deepseek_v3.py | 506 ---------- llms/mlx_lm/models/exaone.py | 166 --- llms/mlx_lm/models/gemma.py | 178 ---- llms/mlx_lm/models/gemma2.py | 203 ---- llms/mlx_lm/models/gemma3_text.py | 238 ----- llms/mlx_lm/models/gpt2.py | 201 ---- llms/mlx_lm/models/gpt_bigcode.py | 189 ---- llms/mlx_lm/models/gpt_neox.py | 219 ---- llms/mlx_lm/models/granite.py | 195 ---- llms/mlx_lm/models/helium.py | 185 ---- llms/mlx_lm/models/hunyuan.py | 321 ------ llms/mlx_lm/models/internlm2.py | 241 ----- llms/mlx_lm/models/internlm3.py | 241 ----- llms/mlx_lm/models/llama.py | 208 ---- llms/mlx_lm/models/mamba.py | 242 ----- llms/mlx_lm/models/minicpm.py | 210 ---- llms/mlx_lm/models/mixtral.py | 220 ---- llms/mlx_lm/models/nemotron.py | 220 ---- llms/mlx_lm/models/olmo.py | 180 ---- llms/mlx_lm/models/olmo2.py | 212 ---- llms/mlx_lm/models/olmoe.py | 217 ---- llms/mlx_lm/models/openelm.py | 223 ---- llms/mlx_lm/models/phi.py | 179 ---- llms/mlx_lm/models/phi3.py | 215 ---- llms/mlx_lm/models/phi3small.py | 313 ------ llms/mlx_lm/models/phimoe.py | 214 ---- llms/mlx_lm/models/phixtral.py | 202 ---- llms/mlx_lm/models/plamo.py | 214 ---- llms/mlx_lm/models/plamo2.py | 608 ----------- llms/mlx_lm/models/qwen.py | 159 --- llms/mlx_lm/models/qwen2.py | 201 ---- llms/mlx_lm/models/qwen2_moe.py | 241 ----- llms/mlx_lm/models/recurrent_gemma.py | 458 --------- llms/mlx_lm/models/rope_utils.py | 91 -- llms/mlx_lm/models/stablelm.py | 211 ---- llms/mlx_lm/models/starcoder2.py | 169 ---- llms/mlx_lm/models/su_rope.py | 66 -- llms/mlx_lm/models/switch_layers.py | 167 --- llms/mlx_lm/py.typed | 1 - llms/mlx_lm/requirements.txt | 6 - llms/mlx_lm/sample_utils.py | 257 ----- llms/mlx_lm/server.py | 785 --------------- llms/mlx_lm/tokenizer_utils.py | 376 ------- llms/mlx_lm/tuner/__init__.py | 2 - llms/mlx_lm/tuner/datasets.py | 273 ----- llms/mlx_lm/tuner/dora.py | 228 ----- llms/mlx_lm/tuner/lora.py | 285 ------ llms/mlx_lm/tuner/trainer.py | 343 ------- llms/mlx_lm/tuner/utils.py | 276 ----- llms/mlx_lm/utils.py | 1118 --------------------- llms/setup.py | 47 - llms/tests/test_datsets.py | 113 --- llms/tests/test_finetune.py | 447 -------- llms/tests/test_generate.py | 91 -- llms/tests/test_gguf.py | 58 -- llms/tests/test_models.py | 986 ------------------ llms/tests/test_prompt_cache.py | 305 ------ llms/tests/test_sample_utils.py | 98 -- llms/tests/test_server.py | 133 --- llms/tests/test_tokenizers.py | 98 -- llms/tests/test_tuner_utils.py | 85 -- llms/tests/test_utils.py | 94 -- llms/tests/test_utils_load_model.py | 50 - 98 files changed, 3 insertions(+), 20721 deletions(-) delete mode 100644 llms/CONTRIBUTING.md delete mode 100644 llms/MANIFEST.in delete mode 100644 llms/mlx_lm/LORA.md delete mode 100644 llms/mlx_lm/MANAGE.md delete mode 100644 llms/mlx_lm/MERGE.md delete mode 100644 llms/mlx_lm/README.md delete mode 100644 llms/mlx_lm/SERVER.md delete mode 100644 llms/mlx_lm/UPLOAD.md delete mode 100644 llms/mlx_lm/__init__.py delete mode 100644 llms/mlx_lm/_version.py delete mode 100644 llms/mlx_lm/cache_prompt.py delete mode 100644 llms/mlx_lm/chat.py delete mode 100644 llms/mlx_lm/convert.py delete mode 100644 llms/mlx_lm/evaluate.py delete mode 100644 llms/mlx_lm/examples/chat.py delete mode 100644 llms/mlx_lm/examples/generate_response.py delete mode 100644 llms/mlx_lm/examples/lora_config.yaml delete mode 100644 llms/mlx_lm/examples/merge_config.yaml delete mode 100644 llms/mlx_lm/examples/pipeline_generate.py delete mode 100644 llms/mlx_lm/examples/tool_use.py delete mode 100644 llms/mlx_lm/fuse.py delete mode 100644 llms/mlx_lm/generate.py delete mode 100644 llms/mlx_lm/gguf.py delete mode 100644 llms/mlx_lm/lora.py delete mode 100644 llms/mlx_lm/manage.py delete mode 100644 llms/mlx_lm/merge.py delete mode 100644 llms/mlx_lm/models/__init__.py delete mode 100644 llms/mlx_lm/models/base.py delete mode 100644 llms/mlx_lm/models/cache.py delete mode 100644 llms/mlx_lm/models/cohere.py delete mode 100644 llms/mlx_lm/models/cohere2.py delete mode 100644 llms/mlx_lm/models/dbrx.py delete mode 100644 llms/mlx_lm/models/deepseek.py delete mode 100644 llms/mlx_lm/models/deepseek_v2.py delete mode 100644 llms/mlx_lm/models/deepseek_v3.py delete mode 100644 llms/mlx_lm/models/exaone.py delete mode 100644 llms/mlx_lm/models/gemma.py delete mode 100644 llms/mlx_lm/models/gemma2.py delete mode 100644 llms/mlx_lm/models/gemma3_text.py delete mode 100644 llms/mlx_lm/models/gpt2.py delete mode 100644 llms/mlx_lm/models/gpt_bigcode.py delete mode 100644 llms/mlx_lm/models/gpt_neox.py delete mode 100644 llms/mlx_lm/models/granite.py delete mode 100644 llms/mlx_lm/models/helium.py delete mode 100644 llms/mlx_lm/models/hunyuan.py delete mode 100644 llms/mlx_lm/models/internlm2.py delete mode 100644 llms/mlx_lm/models/internlm3.py delete mode 100644 llms/mlx_lm/models/llama.py delete mode 100644 llms/mlx_lm/models/mamba.py delete mode 100644 llms/mlx_lm/models/minicpm.py delete mode 100644 llms/mlx_lm/models/mixtral.py delete mode 100644 llms/mlx_lm/models/nemotron.py delete mode 100644 llms/mlx_lm/models/olmo.py delete mode 100644 llms/mlx_lm/models/olmo2.py delete mode 100644 llms/mlx_lm/models/olmoe.py delete mode 100644 llms/mlx_lm/models/openelm.py delete mode 100644 llms/mlx_lm/models/phi.py delete mode 100644 llms/mlx_lm/models/phi3.py delete mode 100644 llms/mlx_lm/models/phi3small.py delete mode 100644 llms/mlx_lm/models/phimoe.py delete mode 100644 llms/mlx_lm/models/phixtral.py delete mode 100644 llms/mlx_lm/models/plamo.py delete mode 100644 llms/mlx_lm/models/plamo2.py delete mode 100644 llms/mlx_lm/models/qwen.py delete mode 100644 llms/mlx_lm/models/qwen2.py delete mode 100644 llms/mlx_lm/models/qwen2_moe.py delete mode 100644 llms/mlx_lm/models/recurrent_gemma.py delete mode 100644 llms/mlx_lm/models/rope_utils.py delete mode 100644 llms/mlx_lm/models/stablelm.py delete mode 100644 llms/mlx_lm/models/starcoder2.py delete mode 100644 llms/mlx_lm/models/su_rope.py delete mode 100644 llms/mlx_lm/models/switch_layers.py delete mode 100644 llms/mlx_lm/py.typed delete mode 100644 llms/mlx_lm/requirements.txt delete mode 100644 llms/mlx_lm/sample_utils.py delete mode 100644 llms/mlx_lm/server.py delete mode 100644 llms/mlx_lm/tokenizer_utils.py delete mode 100644 llms/mlx_lm/tuner/__init__.py delete mode 100644 llms/mlx_lm/tuner/datasets.py delete mode 100644 llms/mlx_lm/tuner/dora.py delete mode 100644 llms/mlx_lm/tuner/lora.py delete mode 100644 llms/mlx_lm/tuner/trainer.py delete mode 100644 llms/mlx_lm/tuner/utils.py delete mode 100644 llms/mlx_lm/utils.py delete mode 100644 llms/setup.py delete mode 100644 llms/tests/test_datsets.py delete mode 100644 llms/tests/test_finetune.py delete mode 100644 llms/tests/test_generate.py delete mode 100644 llms/tests/test_gguf.py delete mode 100644 llms/tests/test_models.py delete mode 100644 llms/tests/test_prompt_cache.py delete mode 100644 llms/tests/test_sample_utils.py delete mode 100644 llms/tests/test_server.py delete mode 100644 llms/tests/test_tokenizers.py delete mode 100644 llms/tests/test_tuner_utils.py delete mode 100644 llms/tests/test_utils.py delete mode 100644 llms/tests/test_utils_load_model.py diff --git a/llms/CONTRIBUTING.md b/llms/CONTRIBUTING.md deleted file mode 100644 index d85067cc..00000000 --- a/llms/CONTRIBUTING.md +++ /dev/null @@ -1,47 +0,0 @@ -# Contributing to MLX LM - -Below are some tips to port LLMs available on Hugging Face to MLX. - -Before starting checkout the [general contribution -guidelines](https://github.com/ml-explore/mlx-examples/blob/main/CONTRIBUTING.md). - -Next, from this directory, do an editable install: - -```shell -pip install -e . -``` - -Then check if the model has weights in the -[safetensors](https://huggingface.co/docs/safetensors/index) format. If not -[follow instructions](https://huggingface.co/spaces/safetensors/convert) to -convert it. - -After that, add the model file to the -[`mlx_lm/models`](https://github.com/ml-explore/mlx-examples/tree/main/llms/mlx_lm/models) -directory. You can see other examples there. We recommend starting from a model -that is similar to the model you are porting. - -Make sure the name of the new model file is the same as the `model_type` in the -`config.json`, for example -[starcoder2](https://huggingface.co/bigcode/starcoder2-7b/blob/main/config.json#L17). - -To determine the model layer names, we suggest either: - -- Refer to the Transformers implementation if you are familiar with the - codebase. -- Load the model weights and check the weight names which will tell you about - the model structure. -- Look at the names of the weights by inspecting `model.safetensors.index.json` - in the Hugging Face repo. - -To add LoRA support edit -[`mlx_lm/tuner/utils.py`](https://github.com/ml-explore/mlx-examples/blob/main/llms/mlx_lm/tuner/utils.py#L27-L60) - -Finally, add a test for the new modle type to the [model -tests](https://github.com/ml-explore/mlx-examples/blob/main/llms/tests/test_models.py). - -From the `llms/` directory, you can run the tests with: - -```shell -python -m unittest discover tests/ -``` diff --git a/llms/MANIFEST.in b/llms/MANIFEST.in deleted file mode 100644 index 05b93159..00000000 --- a/llms/MANIFEST.in +++ /dev/null @@ -1,2 +0,0 @@ -include mlx_lm/requirements.txt -recursive-include mlx_lm/ *.py diff --git a/llms/README.md b/llms/README.md index c9283a0d..10751c98 100644 --- a/llms/README.md +++ b/llms/README.md @@ -1,300 +1,6 @@ -# DEPRECATED +# MOVE NOTICE The mlx-lm package has moved to a [new repo](https://github.com/ml-explore/mlx-lm). -The package here will be removed soon. Send new contributions and issues to the MLX LM repo. - -## Generate Text with LLMs and MLX - -The easiest way to get started is to install the `mlx-lm` package: - -**With `pip`**: - -```sh -pip install mlx-lm -``` - -**With `conda`**: - -```sh -conda install -c conda-forge mlx-lm -``` - -The `mlx-lm` package also has: - -- [LoRA, QLoRA, and full fine-tuning](https://github.com/ml-explore/mlx-examples/blob/main/llms/mlx_lm/LORA.md) -- [Merging models](https://github.com/ml-explore/mlx-examples/blob/main/llms/mlx_lm/MERGE.md) -- [HTTP model serving](https://github.com/ml-explore/mlx-examples/blob/main/llms/mlx_lm/SERVER.md) - -### Quick Start - -To generate text with an LLM use: - -```bash -mlx_lm.generate --prompt "Hi!" -``` - -To chat with an LLM use: - -```bash -mlx_lm.chat -``` - -This will give you a chat REPL that you can use to interact with the LLM. The -chat context is preserved during the lifetime of the REPL. - -Commands in `mlx-lm` typically take command line options which let you specify -the model, sampling parameters, and more. Use `-h` to see a list of available -options for a command, e.g.: - -```bash -mlx_lm.generate -h -``` - -### Python API - -You can use `mlx-lm` as a module: - -```python -from mlx_lm import load, generate - -model, tokenizer = load("mlx-community/Mistral-7B-Instruct-v0.3-4bit") - -prompt = "Write a story about Einstein" - -messages = [{"role": "user", "content": prompt}] -prompt = tokenizer.apply_chat_template( - messages, add_generation_prompt=True -) - -text = generate(model, tokenizer, prompt=prompt, verbose=True) -``` - -To see a description of all the arguments you can do: - -``` ->>> help(generate) -``` - -Check out the [generation -example](https://github.com/ml-explore/mlx-examples/tree/main/llms/mlx_lm/examples/generate_response.py) -to see how to use the API in more detail. - -The `mlx-lm` package also comes with functionality to quantize and optionally -upload models to the Hugging Face Hub. - -You can convert models using the Python API: - -```python -from mlx_lm import convert - -repo = "mistralai/Mistral-7B-Instruct-v0.3" -upload_repo = "mlx-community/My-Mistral-7B-Instruct-v0.3-4bit" - -convert(repo, quantize=True, upload_repo=upload_repo) -``` - -This will generate a 4-bit quantized Mistral 7B and upload it to the repo -`mlx-community/My-Mistral-7B-Instruct-v0.3-4bit`. It will also save the -converted model in the path `mlx_model` by default. - -To see a description of all the arguments you can do: - -``` ->>> help(convert) -``` - -#### Streaming - -For streaming generation, use the `stream_generate` function. This yields -a generation response object. - -For example, - -```python -from mlx_lm import load, stream_generate - -repo = "mlx-community/Mistral-7B-Instruct-v0.3-4bit" -model, tokenizer = load(repo) - -prompt = "Write a story about Einstein" - -messages = [{"role": "user", "content": prompt}] -prompt = tokenizer.apply_chat_template( - messages, add_generation_prompt=True -) - -for response in stream_generate(model, tokenizer, prompt, max_tokens=512): - print(response.text, end="", flush=True) -print() -``` - -#### Sampling - -The `generate` and `stream_generate` functions accept `sampler` and -`logits_processors` keyword arguments. A sampler is any callable which accepts -a possibly batched logits array and returns an array of sampled tokens. The -`logits_processors` must be a list of callables which take the token history -and current logits as input and return the processed logits. The logits -processors are applied in order. - -Some standard sampling functions and logits processors are provided in -`mlx_lm.sample_utils`. - -### Command Line - -You can also use `mlx-lm` from the command line with: - -``` -mlx_lm.generate --model mistralai/Mistral-7B-Instruct-v0.3 --prompt "hello" -``` - -This will download a Mistral 7B model from the Hugging Face Hub and generate -text using the given prompt. - -For a full list of options run: - -``` -mlx_lm.generate --help -``` - -To quantize a model from the command line run: - -``` -mlx_lm.convert --hf-path mistralai/Mistral-7B-Instruct-v0.3 -q -``` - -For more options run: - -``` -mlx_lm.convert --help -``` - -You can upload new models to Hugging Face by specifying `--upload-repo` to -`convert`. For example, to upload a quantized Mistral-7B model to the -[MLX Hugging Face community](https://huggingface.co/mlx-community) you can do: - -``` -mlx_lm.convert \ - --hf-path mistralai/Mistral-7B-Instruct-v0.3 \ - -q \ - --upload-repo mlx-community/my-4bit-mistral -``` - -Models can also be converted and quantized directly in the -[mlx-my-repo](https://huggingface.co/spaces/mlx-community/mlx-my-repo) Hugging -Face Space. - -### Long Prompts and Generations - -`mlx-lm` has some tools to scale efficiently to long prompts and generations: - -- A rotating fixed-size key-value cache. -- Prompt caching - -To use the rotating key-value cache pass the argument `--max-kv-size n` where -`n` can be any integer. Smaller values like `512` will use very little RAM but -result in worse quality. Larger values like `4096` or higher will use more RAM -but have better quality. - -Caching prompts can substantially speedup reusing the same long context with -different queries. To cache a prompt use `mlx_lm.cache_prompt`. For example: - -```bash -cat prompt.txt | mlx_lm.cache_prompt \ - --model mistralai/Mistral-7B-Instruct-v0.3 \ - --prompt - \ - --prompt-cache-file mistral_prompt.safetensors -``` - -Then use the cached prompt with `mlx_lm.generate`: - -``` -mlx_lm.generate \ - --prompt-cache-file mistral_prompt.safetensors \ - --prompt "\nSummarize the above text." -``` - -The cached prompt is treated as a prefix to the supplied prompt. Also notice -when using a cached prompt, the model to use is read from the cache and need -not be supplied explicitly. - -Prompt caching can also be used in the Python API in order to to avoid -recomputing the prompt. This is useful in multi-turn dialogues or across -requests that use the same context. See the -[example](https://github.com/ml-explore/mlx-examples/blob/main/llms/mlx_lm/examples/chat.py) -for more usage details. - -### Supported Models - -`mlx-lm` supports thousands of Hugging Face format LLMs. If the model you want to -run is not supported, file an -[issue](https://github.com/ml-explore/mlx-examples/issues/new) or better yet, -submit a pull request. - -Here are a few examples of Hugging Face models that work with this example: - -- [mistralai/Mistral-7B-v0.1](https://huggingface.co/mistralai/Mistral-7B-v0.1) -- [meta-llama/Llama-2-7b-hf](https://huggingface.co/meta-llama/Llama-2-7b-hf) -- [deepseek-ai/deepseek-coder-6.7b-instruct](https://huggingface.co/deepseek-ai/deepseek-coder-6.7b-instruct) -- [01-ai/Yi-6B-Chat](https://huggingface.co/01-ai/Yi-6B-Chat) -- [microsoft/phi-2](https://huggingface.co/microsoft/phi-2) -- [mistralai/Mixtral-8x7B-Instruct-v0.1](https://huggingface.co/mistralai/Mixtral-8x7B-Instruct-v0.1) -- [Qwen/Qwen-7B](https://huggingface.co/Qwen/Qwen-7B) -- [pfnet/plamo-13b](https://huggingface.co/pfnet/plamo-13b) -- [pfnet/plamo-13b-instruct](https://huggingface.co/pfnet/plamo-13b-instruct) -- [stabilityai/stablelm-2-zephyr-1_6b](https://huggingface.co/stabilityai/stablelm-2-zephyr-1_6b) -- [internlm/internlm2-7b](https://huggingface.co/internlm/internlm2-7b) -- [tiiuae/falcon-mamba-7b-instruct](https://huggingface.co/tiiuae/falcon-mamba-7b-instruct) - -Most -[Mistral](https://huggingface.co/models?library=transformers,safetensors&other=mistral&sort=trending), -[Llama](https://huggingface.co/models?library=transformers,safetensors&other=llama&sort=trending), -[Phi-2](https://huggingface.co/models?library=transformers,safetensors&other=phi&sort=trending), -and -[Mixtral](https://huggingface.co/models?library=transformers,safetensors&other=mixtral&sort=trending) -style models should work out of the box. - -For some models (such as `Qwen` and `plamo`) the tokenizer requires you to -enable the `trust_remote_code` option. You can do this by passing -`--trust-remote-code` in the command line. If you don't specify the flag -explicitly, you will be prompted to trust remote code in the terminal when -running the model. - -For `Qwen` models you must also specify the `eos_token`. You can do this by -passing `--eos-token "<|endoftext|>"` in the command -line. - -These options can also be set in the Python API. For example: - -```python -model, tokenizer = load( - "qwen/Qwen-7B", - tokenizer_config={"eos_token": "<|endoftext|>", "trust_remote_code": True}, -) -``` - -### Large Models - -> [!NOTE] - This requires macOS 15.0 or higher to work. - -Models which are large relative to the total RAM available on the machine can -be slow. `mlx-lm` will attempt to make them faster by wiring the memory -occupied by the model and cache. This requires macOS 15 or higher to -work. - -If you see the following warning message: - -> [WARNING] Generating with a model that requires ... - -then the model will likely be slow on the given machine. If the model fits in -RAM then it can often be sped up by increasing the system wired memory limit. -To increase the limit, set the following `sysctl`: - -```bash -sudo sysctl iogpu.wired_limit_mb=N -``` - -The value `N` should be larger than the size of the model in megabytes but -smaller than the memory size of the machine. +The package has been removed from the MLX Examples repo. Send new contributions +and issues to the MLX LM repo. diff --git a/llms/mlx_lm/LORA.md b/llms/mlx_lm/LORA.md deleted file mode 100644 index e863abc4..00000000 --- a/llms/mlx_lm/LORA.md +++ /dev/null @@ -1,392 +0,0 @@ -# Fine-Tuning with LoRA or QLoRA - -You can use use the `mlx-lm` package to fine-tune an LLM with low rank -adaptation (LoRA) for a target task.[^lora] The example also supports quantized -LoRA (QLoRA).[^qlora] LoRA fine-tuning works with the following model families: - -- Mistral -- Llama -- Phi2 -- Mixtral -- Qwen2 -- Gemma -- OLMo -- MiniCPM -- InternLM2 - -## Contents - -- [Run](#Run) - - [Fine-tune](#Fine-tune) - - [Evaluate](#Evaluate) - - [Generate](#Generate) -- [Fuse](#Fuse) -- [Data](#Data) -- [Memory Issues](#Memory-Issues) - -## Run - -The main command is `mlx_lm.lora`. To see a full list of command-line options run: - -```shell -mlx_lm.lora --help -``` - -Note, in the following the `--model` argument can be any compatible Hugging -Face repo or a local path to a converted model. - -You can also specify a YAML config with `-c`/`--config`. For more on the format see the -[example YAML](examples/lora_config.yaml). For example: - -```shell -mlx_lm.lora --config /path/to/config.yaml -``` - -If command-line flags are also used, they will override the corresponding -values in the config. - -### Fine-tune - -To fine-tune a model use: - -```shell -mlx_lm.lora \ - --model \ - --train \ - --data \ - --iters 600 -``` - -To fine-tune the full model weights, add the `--fine-tune-type full` flag. -Currently supported fine-tuning types are `lora` (default), `dora`, and `full`. - -The `--data` argument must specify a path to a `train.jsonl`, `valid.jsonl` -when using `--train` and a path to a `test.jsonl` when using `--test`. For more -details on the data format see the section on [Data](#Data). - -For example, to fine-tune a Mistral 7B you can use `--model -mistralai/Mistral-7B-v0.1`. - -If `--model` points to a quantized model, then the training will use QLoRA, -otherwise it will use regular LoRA. - -By default, the adapter config and learned weights are saved in `adapters/`. -You can specify the output location with `--adapter-path`. - -You can resume fine-tuning with an existing adapter with -`--resume-adapter-file `. - -#### Prompt Masking - -The default training computes a loss for every token in the sample. You can -ignore the prompt and compute loss for just the completion by passing -`--mask-prompt`. Note this is only supported for `chat` and `completion` -datasets. For `chat` datasets the final message in the message list is -considered the completion. See the [dataset section](#Data) for more details. - -### Evaluate - -To compute test set perplexity use: - -```shell -mlx_lm.lora \ - --model \ - --adapter-path \ - --data \ - --test -``` - -### Generate - -For generation use `mlx_lm.generate`: - -```shell -mlx_lm.generate \ - --model \ - --adapter-path \ - --prompt "" -``` - -## Fuse - -You can generate a model fused with the low-rank adapters using the -`mlx_lm.fuse` command. This command also allows you to optionally: - -- Upload the fused model to the Hugging Face Hub. -- Export the fused model to GGUF. Note GGUF support is limited to Mistral, - Mixtral, and Llama style models in fp16 precision. - -To see supported options run: - -```shell -mlx_lm.fuse --help -``` - -To generate the fused model run: - -```shell -mlx_lm.fuse --model -``` - -This will by default load the adapters from `adapters/`, and save the fused -model in the path `fused_model/`. All of these are configurable. - -To upload a fused model, supply the `--upload-repo` and `--hf-path` arguments -to `mlx_lm.fuse`. The latter is the repo name of the original model, which is -useful for the sake of attribution and model versioning. - -For example, to fuse and upload a model derived from Mistral-7B-v0.1, run: - -```shell -mlx_lm.fuse \ - --model mistralai/Mistral-7B-v0.1 \ - --upload-repo mlx-community/my-lora-mistral-7b \ - --hf-path mistralai/Mistral-7B-v0.1 -``` - -To export a fused model to GGUF, run: - -```shell -mlx_lm.fuse \ - --model mistralai/Mistral-7B-v0.1 \ - --export-gguf -``` - -This will save the GGUF model in `fused_model/ggml-model-f16.gguf`. You -can specify the file name with `--gguf-path`. - -## Data - -The LoRA command expects you to provide a dataset with `--data`. The MLX -Examples GitHub repo has an [example of the WikiSQL -data](https://github.com/ml-explore/mlx-examples/tree/main/lora/data) in the -correct format. - -Datasets can be specified in `*.jsonl` files locally or loaded from Hugging -Face. - -### Local Datasets - -For fine-tuning (`--train`), the data loader expects a `train.jsonl` and a -`valid.jsonl` to be in the data directory. For evaluation (`--test`), the data -loader expects a `test.jsonl` in the data directory. - -Currently, `*.jsonl` files support `chat`, `tools`, `completions`, and `text` -data formats. Here are examples of these formats: - -`chat`: - -```jsonl -{"messages": [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "Hello."}, {"role": "assistant", "content": "How can I assistant you today."}]} -``` - -`tools`: - -```jsonl -{"messages":[{"role":"user","content":"What is the weather in San Francisco?"},{"role":"assistant","tool_calls":[{"id":"call_id","type":"function","function":{"name":"get_current_weather","arguments":"{\"location\": \"San Francisco, USA\", \"format\": \"celsius\"}"}}]}],"tools":[{"type":"function","function":{"name":"get_current_weather","description":"Get the current weather","parameters":{"type":"object","properties":{"location":{"type":"string","description":"The city and country, eg. San Francisco, USA"},"format":{"type":"string","enum":["celsius","fahrenheit"]}},"required":["location","format"]}}}]} -``` - -

    -View the expanded single data tool format - -```jsonl -{ - "messages": [ - { "role": "user", "content": "What is the weather in San Francisco?" }, - { - "role": "assistant", - "tool_calls": [ - { - "id": "call_id", - "type": "function", - "function": { - "name": "get_current_weather", - "arguments": "{\"location\": \"San Francisco, USA\", \"format\": \"celsius\"}" - } - } - ] - } - ], - "tools": [ - { - "type": "function", - "function": { - "name": "get_current_weather", - "description": "Get the current weather", - "parameters": { - "type": "object", - "properties": { - "location": { - "type": "string", - "description": "The city and country, eg. San Francisco, USA" - }, - "format": { "type": "string", "enum": ["celsius", "fahrenheit"] } - }, - "required": ["location", "format"] - } - } - } - ] -} -``` - - -The format for the `arguments` field in a function varies for different models. -Common formats include JSON strings and dictionaries. The example provided -follows the format used by -[OpenAI](https://platform.openai.com/docs/guides/fine-tuning/fine-tuning-examples) -and [Mistral -AI](https://github.com/mistralai/mistral-finetune?tab=readme-ov-file#instruct). -A dictionary format is used in Hugging Face's [chat -templates](https://huggingface.co/docs/transformers/main/en/chat_templating#a-complete-tool-use-example). -Refer to the documentation for the model you are fine-tuning for more details. - -
    - -`completions`: - -```jsonl -{"prompt": "What is the capital of France?", "completion": "Paris."} -``` - -For the `completions` data format, a different key can be used for the prompt -and completion by specifying the following in the YAML config: - -```yaml -prompt_feature: "input" -completion_feature: "output" -``` - -Here, `"input"` is the expected key instead of the default `"prompt"`, and -`"output"` is the expected key instead of `"completion"`. - -`text`: - -```jsonl -{"text": "This is an example for the model."} -``` - -Note, the format is automatically determined by the dataset. Note also, keys -in each line not expected by the loader will be ignored. - -> [!NOTE] -> Each example in the datasets must be on a single line. Do not put more than -> one example per line and do not split an example across multiple lines. - -### Hugging Face Datasets - -To use Hugging Face datasets, first install the `datasets` package: - -``` -pip install datasets -``` - -If the Hugging Face dataset is already in a supported format, you can specify -it on the command line. For example, pass `--data mlx-community/wikisql` to -train on the pre-formatted WikiwSQL data. - -Otherwise, provide a mapping of keys in the dataset to the features MLX LM -expects. Use a YAML config to specify the Hugging Face dataset arguments. For -example: - -```yaml -hf_dataset: - name: "billsum" - prompt_feature: "text" - completion_feature: "summary" -``` - -- Use `prompt_feature` and `completion_feature` to specify keys for a - `completions` dataset. Use `text_feature` to specify the key for a `text` - dataset. Use `chat_feature` to specify the key for a chat dataset. - -- To specify the train, valid, or test splits, set the corresponding - `{train,valid,test}_split` argument. - -You can specify a list of Hugging Face datasets with a list of records each -with the same structure as above. For example: - -```yaml -hf_dataset: - - name: "Open-Orca/OpenOrca" - train_split: "train[:90%]" - valid_split: "train[-10%:]" - prompt_feature: "question" - completion_feature: "response" - - name: "trl-lib/ultrafeedback_binarized" - train_split: "train[:90%]" - valid_split: "train[-10%:]" - chat_feature: "chosen" -``` - -- Arguments specified in `config` will be passed as keyword arguments to - [`datasets.load_dataset`](https://huggingface.co/docs/datasets/v2.20.0/en/package_reference/loading_methods#datasets.load_dataset). - -In general, for the `chat`, `tools` and `completions` formats, Hugging Face -[chat -templates](https://huggingface.co/docs/transformers/main/en/chat_templating) -are used. This applies the model's chat template by default. If the model does -not have a chat template, then Hugging Face will use a default. For example, -the final text in the `chat` example above with Hugging Face's default template -becomes: - -```text -<|im_start|>system -You are a helpful assistant.<|im_end|> -<|im_start|>user -Hello.<|im_end|> -<|im_start|>assistant -How can I assistant you today.<|im_end|> -``` - -If you are unsure of the format to use, the `chat` or `completions` are good to -start with. For custom requirements on the format of the dataset, use the -`text` format to assemble the content yourself. - -## Memory Issues - -Fine-tuning a large model with LoRA requires a machine with a decent amount -of memory. Here are some tips to reduce memory use should you need to do so: - -1. Try quantization (QLoRA). You can use QLoRA by generating a quantized model - with `convert.py` and the `-q` flag. See the [Setup](#setup) section for - more details. - -2. Try using a smaller batch size with `--batch-size`. The default is `4` so - setting this to `2` or `1` will reduce memory consumption. This may slow - things down a little, but will also reduce the memory use. - -3. Reduce the number of layers to fine-tune with `--num-layers`. The default - is `16`, so you can try `8` or `4`. This reduces the amount of memory - needed for back propagation. It may also reduce the quality of the - fine-tuned model if you are fine-tuning with a lot of data. - -4. Longer examples require more memory. If it makes sense for your data, one thing - you can do is break your examples into smaller - sequences when making the `{train, valid, test}.jsonl` files. - -5. Gradient checkpointing lets you trade-off memory use (less) for computation - (more) by recomputing instead of storing intermediate values needed by the - backward pass. You can use gradient checkpointing by passing the - `--grad-checkpoint` flag. Gradient checkpointing will be more helpful for - larger batch sizes or sequence lengths with smaller or quantized models. - -For example, for a machine with 32 GB the following should run reasonably fast: - -``` -mlx_lm.lora \ - --model mistralai/Mistral-7B-v0.1 \ - --train \ - --batch-size 1 \ - --num-layers 4 \ - --data wikisql -``` - -The above command on an M1 Max with 32 GB runs at about 250 -tokens-per-second, using the MLX Example -[`wikisql`](https://github.com/ml-explore/mlx-examples/tree/main/lora/data) -data set. - -[^lora]: Refer to the [arXiv paper](https://arxiv.org/abs/2106.09685) for more details on LoRA. - -[^qlora]: Refer to the paper [QLoRA: Efficient Finetuning of Quantized LLMs](https://arxiv.org/abs/2305.14314) diff --git a/llms/mlx_lm/MANAGE.md b/llms/mlx_lm/MANAGE.md deleted file mode 100644 index 00858a0a..00000000 --- a/llms/mlx_lm/MANAGE.md +++ /dev/null @@ -1,22 +0,0 @@ -# Managing Models - -You can use `mlx-lm` to manage models downloaded locally in your machine. They -are stored in the Hugging Face cache. - -Scan models: - -```shell -mlx_lm.manage --scan -``` - -Specify a `--pattern` to get info on a single or specific set of models: - -```shell -mlx_lm.manage --scan --pattern mlx-community/Mistral-7B-Instruct-v0.2-4bit -``` - -To delete a model (or multiple models): - -```shell -mlx_lm.manage --delete --pattern mlx-community/Mistral-7B-Instruct-v0.2-4bit -``` diff --git a/llms/mlx_lm/MERGE.md b/llms/mlx_lm/MERGE.md deleted file mode 100644 index 093c7ed6..00000000 --- a/llms/mlx_lm/MERGE.md +++ /dev/null @@ -1,50 +0,0 @@ -# Model Merging - -You can use `mlx-lm` to merge models and upload them to the Hugging -Face hub or save them locally for LoRA fine tuning. - -The main command is `mlx_lm.merge`: - -```shell -mlx_lm.merge --config config.yaml -``` - -The merged model will be saved by default in `mlx_merged_model`. To see a -full list of options run: - -```shell -mlx_lm.merge --help -``` - -Here is an example `config.yaml`: - -```yaml -models: - - OpenPipe/mistral-ft-optimized-1218 - - mlabonne/NeuralHermes-2.5-Mistral-7B -method: slerp -parameters: - t: - - filter: self_attn - value: [0, 0.5, 0.3, 0.7, 1] - - filter: mlp - value: [1, 0.5, 0.7, 0.3, 0] - - value: 0.5 -``` - -The `models` field is a list of Hugging Face repo ids. The first model in the -list is treated as the base model into which the remaining models are merged. - -The `method` field is the merging method. Right now `slerp` is the only -supported method. - -The `parameters` are the corresponding parameters for the given `method`. -Each parameter is a list with `filter` determining which layer the parameter -applies to and `value` determining the actual value used. The last item in -the list without a `filter` field is the default. - -If `value` is a list, it specifies the start and end values for the -corresponding segment of blocks. In the example above, the models have 32 -blocks. For blocks 1-8, the layers with `self_attn` in the name will use the -values `np.linspace(0, 0.5, 8)`, the same layers in the next 8 blocks (9-16) -will use `np.linspace(0.5, 0.3, 8)`, and so on. diff --git a/llms/mlx_lm/README.md b/llms/mlx_lm/README.md deleted file mode 100644 index fd11a8f2..00000000 --- a/llms/mlx_lm/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# DEPRECATED - -The mlx-lm package has moved to a [new repo](https://github.com/ml-explore/mlx-lm). - -The package here will be removed soon. Send new contributions and issues to the MLX LM repo. - -## Generate Text with MLX and :hugs: Hugging Face - -This an example of large language model text generation that can pull models from -the Hugging Face Hub. - -For more information on this example, see the [README](../README.md) in the -parent directory. - -This package also supports fine tuning with LoRA or QLoRA. For more information -see the [LoRA documentation](LORA.md). diff --git a/llms/mlx_lm/SERVER.md b/llms/mlx_lm/SERVER.md deleted file mode 100644 index e544c6fa..00000000 --- a/llms/mlx_lm/SERVER.md +++ /dev/null @@ -1,131 +0,0 @@ -# HTTP Model Server - -You use `mlx-lm` to make an HTTP API for generating text with any supported -model. The HTTP API is intended to be similar to the [OpenAI chat -API](https://platform.openai.com/docs/api-reference). - -> [!NOTE] -> The MLX LM server is not recommended for production as it only implements -> basic security checks. - -Start the server with: - -```shell -mlx_lm.server --model -``` - -For example: - -```shell -mlx_lm.server --model mlx-community/Mistral-7B-Instruct-v0.3-4bit -``` - -This will start a text generation server on port `8080` of the `localhost` -using Mistral 7B instruct. The model will be downloaded from the provided -Hugging Face repo if it is not already in the local cache. - -To see a full list of options run: - -```shell -mlx_lm.server --help -``` - -You can make a request to the model by running: - -```shell -curl localhost:8080/v1/chat/completions \ - -H "Content-Type: application/json" \ - -d '{ - "messages": [{"role": "user", "content": "Say this is a test!"}], - "temperature": 0.7 - }' -``` - -### Request Fields - -- `messages`: An array of message objects representing the conversation - history. Each message object should have a role (e.g. user, assistant) and - content (the message text). - -- `role_mapping`: (Optional) A dictionary to customize the role prefixes in - the generated prompt. If not provided, the default mappings are used. - -- `stop`: (Optional) An array of strings or a single string. These are - sequences of tokens on which the generation should stop. - -- `max_tokens`: (Optional) An integer specifying the maximum number of tokens - to generate. Defaults to `100`. - -- `stream`: (Optional) A boolean indicating if the response should be - streamed. If true, responses are sent as they are generated. Defaults to - false. - -- `temperature`: (Optional) A float specifying the sampling temperature. - Defaults to `1.0`. - -- `top_p`: (Optional) A float specifying the nucleus sampling parameter. - Defaults to `1.0`. - -- `repetition_penalty`: (Optional) Applies a penalty to repeated tokens. - Defaults to `1.0`. - -- `repetition_context_size`: (Optional) The size of the context window for - applying repetition penalty. Defaults to `20`. - -- `logit_bias`: (Optional) A dictionary mapping token IDs to their bias - values. Defaults to `None`. - -- `logprobs`: (Optional) An integer specifying the number of top tokens and - corresponding log probabilities to return for each output in the generated - sequence. If set, this can be any value between 1 and 10, inclusive. - -- `model`: (Optional) A string path to a local model or Hugging Face repo id. - If the path is local is must be relative to the directory the server was - started in. - -- `adapters`: (Optional) A string path to low-rank adapters. The path must be - relative to the directory the server was started in. - -### Response Fields - -- `id`: A unique identifier for the chat. - -- `system_fingerprint`: A unique identifier for the system. - -- `object`: Any of "chat.completion", "chat.completion.chunk" (for - streaming), or "text.completion". - -- `model`: The model repo or path (e.g. `"mlx-community/Llama-3.2-3B-Instruct-4bit"`). - -- `created`: A time-stamp for when the request was processed. - -- `choices`: A list of outputs. Each output is a dictionary containing the fields: - - `index`: The index in the list. - - `logprobs`: A dictionary containing the fields: - - `token_logprobs`: A list of the log probabilities for the generated - tokens. - - `tokens`: A list of the generated token ids. - - `top_logprobs`: A list of lists. Each list contains the `logprobs` - top tokens (if requested) with their corresponding probabilities. - - `finish_reason`: The reason the completion ended. This can be either of - `"stop"` or `"length"`. - - `message`: The text response from the model. - -- `usage`: A dictionary containing the fields: - - `prompt_tokens`: The number of prompt tokens processed. - - `completion_tokens`: The number of tokens generated. - - `total_tokens`: The total number of tokens, i.e. the sum of the above two fields. - -### List Models - -Use the `v1/models` endpoint to list available models: - -```shell -curl localhost:8080/v1/models -H "Content-Type: application/json" -``` - -This will return a list of locally available models where each model in the -list contains the following fields: - -- `id`: The Hugging Face repo id. -- `created`: A time-stamp representing the model creation time. diff --git a/llms/mlx_lm/UPLOAD.md b/llms/mlx_lm/UPLOAD.md deleted file mode 100644 index f5de3655..00000000 --- a/llms/mlx_lm/UPLOAD.md +++ /dev/null @@ -1,37 +0,0 @@ -### Packaging for PyPI - -Install `build` and `twine`: - -``` -pip install --user --upgrade build -pip install --user --upgrade twine -``` - -Generate the source distribution and wheel: - -``` -python -m build -``` - -> [!warning] -> Use a test server first - -#### Test Upload - -Upload to test server: - -``` -python -m twine upload --repository testpypi dist/* -``` - -Install from test server and check that it works: - -``` -python -m pip install --index-url https://test.pypi.org/simple/ --no-deps mlx-lm -``` - -#### Upload - -``` -python -m twine upload dist/* -``` diff --git a/llms/mlx_lm/__init__.py b/llms/mlx_lm/__init__.py deleted file mode 100644 index 538be927..00000000 --- a/llms/mlx_lm/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -import os - -from ._version import __version__ - -os.environ["TRANSFORMERS_NO_ADVISORY_WARNINGS"] = "1" - -from .utils import convert, generate, load, stream_generate diff --git a/llms/mlx_lm/_version.py b/llms/mlx_lm/_version.py deleted file mode 100644 index 839089b6..00000000 --- a/llms/mlx_lm/_version.py +++ /dev/null @@ -1,3 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -__version__ = "0.21.6" diff --git a/llms/mlx_lm/cache_prompt.py b/llms/mlx_lm/cache_prompt.py deleted file mode 100644 index fff64f78..00000000 --- a/llms/mlx_lm/cache_prompt.py +++ /dev/null @@ -1,161 +0,0 @@ -# Copyright © 2024 Apple Inc. - -import argparse -import json -import sys -import time - -import mlx.core as mx - -from .models.cache import make_prompt_cache, save_prompt_cache -from .utils import generate_step, load - -DEFAULT_QUANTIZED_KV_START = 5000 - - -def setup_arg_parser(): - """Set up and return the argument parser.""" - parser = argparse.ArgumentParser( - description="Cache the state of a prompt to be reused with mlx_lm.generate" - ) - parser.add_argument( - "--model", - type=str, - default="mlx_model", - help="The path to the local model directory or Hugging Face repo.", - ) - parser.add_argument( - "--adapter-path", - type=str, - help="Optional path for the trained adapter weights and config.", - ) - parser.add_argument( - "--trust-remote-code", - action="store_true", - help="Enable trusting remote code for tokenizer", - ) - parser.add_argument( - "--eos-token", - type=str, - default=None, - help="End of sequence token for tokenizer", - ) - parser.add_argument( - "--ignore-chat-template", - action="store_true", - help="Use the raw prompt without the tokenizer's chat template.", - ) - parser.add_argument( - "--use-default-chat-template", - action="store_true", - help="Use the default chat template", - ) - parser.add_argument( - "--max-kv-size", - type=int, - default=None, - help="Set the maximum key-value cache size", - ) - parser.add_argument( - "--prompt-cache-file", - help="The file to save the prompt cache in", - required=True, - ) - parser.add_argument( - "--prompt", - required=True, - help="Message to be processed by the model ('-' reads from stdin)", - ) - parser.add_argument( - "--kv-bits", - type=int, - help="Number of bits for KV cache quantization. " - "Defaults to no quantization.", - default=None, - ) - parser.add_argument( - "--kv-group-size", - type=int, - help="Group size for KV cache quantization.", - default=64, - ) - parser.add_argument( - "--quantized-kv-start", - help="When --kv-bits is set, start quantizing the KV cache " - "from this step onwards.", - type=int, - default=DEFAULT_QUANTIZED_KV_START, - ) - return parser - - -def main(): - parser = setup_arg_parser() - args = parser.parse_args() - - # Building tokenizer_config - tokenizer_config = {"trust_remote_code": True if args.trust_remote_code else None} - if args.eos_token is not None: - tokenizer_config["eos_token"] = args.eos_token - - model, tokenizer = load( - args.model, - adapter_path=args.adapter_path, - tokenizer_config=tokenizer_config, - ) - - args.prompt = sys.stdin.read() if args.prompt == "-" else args.prompt - - if args.use_default_chat_template: - if tokenizer.chat_template is None: - tokenizer.chat_template = tokenizer.default_chat_template - - if not args.ignore_chat_template and tokenizer.chat_template is not None: - messages = [{"role": "user", "content": args.prompt}] - prompt = tokenizer.apply_chat_template( - messages, add_generation_prompt=False, continue_final_message=True - ) - - else: - prompt = tokenizer.encode(args.prompt) - - cache = make_prompt_cache(model, args.max_kv_size) - y = mx.array(prompt) - - # Process the prompt - start = time.time() - max_msg_len = 0 - - def callback(processed, total_tokens): - current = time.time() - speed = processed / (current - start) - msg = f"\rProcessed {processed:6d} tokens ({speed:6.2f} tok/s)" - nonlocal max_msg_len - max_msg_len = max(max_msg_len, len(msg)) - print(msg + " " * (max_msg_len - len(msg)), end="", flush=True) - - for _ in generate_step( - y, - model, - max_tokens=0, - prompt_cache=cache, - kv_bits=args.kv_bits, - kv_group_size=args.kv_group_size, - quantized_kv_start=args.quantized_kv_start, - prompt_progress_callback=callback, - ): - pass - - print() - print(f"Peak memory: {mx.metal.get_peak_memory() / 1e9:.3f} GB") - - print("Saving...") - metadata = {} - metadata["model"] = args.model - metadata["chat_template"] = json.dumps(tokenizer.chat_template) - metadata["tokenizer_config"] = json.dumps(tokenizer_config) - save_prompt_cache(args.prompt_cache_file, cache, metadata) - - -if __name__ == "__main__": - main() diff --git a/llms/mlx_lm/chat.py b/llms/mlx_lm/chat.py deleted file mode 100644 index d8e1ccb9..00000000 --- a/llms/mlx_lm/chat.py +++ /dev/null @@ -1,108 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -import argparse -import json - -import mlx.core as mx - -from .models.cache import make_prompt_cache -from .sample_utils import make_sampler -from .utils import load, stream_generate - -DEFAULT_TEMP = 0.0 -DEFAULT_TOP_P = 1.0 -DEFAULT_SEED = None -DEFAULT_MAX_TOKENS = 256 -DEFAULT_MODEL = "mlx-community/Llama-3.2-3B-Instruct-4bit" - - -def setup_arg_parser(): - """Set up and return the argument parser.""" - parser = argparse.ArgumentParser(description="Chat with an LLM") - parser.add_argument( - "--model", - type=str, - help="The path to the local model directory or Hugging Face repo.", - default=DEFAULT_MODEL, - ) - parser.add_argument( - "--adapter-path", - type=str, - help="Optional path for the trained adapter weights and config.", - ) - parser.add_argument( - "--temp", type=float, default=DEFAULT_TEMP, help="Sampling temperature" - ) - parser.add_argument( - "--top-p", type=float, default=DEFAULT_TOP_P, help="Sampling top-p" - ) - parser.add_argument( - "--seed", - type=int, - default=DEFAULT_SEED, - help="PRNG seed", - ) - parser.add_argument( - "--max-kv-size", - type=int, - help="Set the maximum key-value cache size", - default=None, - ) - parser.add_argument( - "--max-tokens", - "-m", - type=int, - default=DEFAULT_MAX_TOKENS, - help="Maximum number of tokens to generate", - ) - return parser - - -def main(): - parser = setup_arg_parser() - args = parser.parse_args() - - if args.seed is not None: - mx.random.seed(args.seed) - - model, tokenizer = load( - args.model, - adapter_path=args.adapter_path, - tokenizer_config={"trust_remote_code": True}, - ) - - def print_help(): - print("The command list:") - print("- 'q' to exit") - print("- 'r' to reset the chat") - print("- 'h' to display these commands") - - print(f"[INFO] Starting chat session with {args.model}.") - print_help() - prompt_cache = make_prompt_cache(model, args.max_kv_size) - while True: - query = input(">> ") - if query == "q": - break - if query == "r": - prompt_cache = make_prompt_cache(model, args.max_kv_size) - continue - if query == "h": - print_help() - continue - messages = [{"role": "user", "content": query}] - prompt = tokenizer.apply_chat_template(messages, add_generation_prompt=True) - for response in stream_generate( - model, - tokenizer, - prompt, - max_tokens=args.max_tokens, - sampler=make_sampler(args.temp, args.top_p), - prompt_cache=prompt_cache, - ): - print(response.text, flush=True, end="") - print() - - -if __name__ == "__main__": - main() diff --git a/llms/mlx_lm/convert.py b/llms/mlx_lm/convert.py deleted file mode 100644 index f268913b..00000000 --- a/llms/mlx_lm/convert.py +++ /dev/null @@ -1,83 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -import argparse - -from . import utils -from .utils import convert - -QUANT_RECIPES = [ - "mixed_2_6", - "mixed_3_6", -] - - -def quant_args(arg): - if arg not in QUANT_RECIPES: - raise argparse.ArgumentTypeError( - f"Invalid q-recipe {arg!r}. Choose from: {QUANT_RECIPES}" - ) - else: - return getattr(utils, arg) - - -def configure_parser() -> argparse.ArgumentParser: - """ - Configures and returns the argument parser for the script. - - Returns: - argparse.ArgumentParser: Configured argument parser. - """ - parser = argparse.ArgumentParser( - description="Convert Hugging Face model to MLX format" - ) - - parser.add_argument("--hf-path", type=str, help="Path to the Hugging Face model.") - parser.add_argument( - "--mlx-path", type=str, default="mlx_model", help="Path to save the MLX model." - ) - parser.add_argument( - "-q", "--quantize", help="Generate a quantized model.", action="store_true" - ) - parser.add_argument( - "--q-group-size", help="Group size for quantization.", type=int, default=64 - ) - parser.add_argument( - "--q-bits", help="Bits per weight for quantization.", type=int, default=4 - ) - parser.add_argument( - "--quant-predicate", - help=f"Mixed-bit quantization recipe. Choices: {QUANT_RECIPES}", - type=quant_args, - required=False, - ) - parser.add_argument( - "--dtype", - help="Type to save the non-quantized parameters.", - type=str, - choices=["float16", "bfloat16", "float32"], - default="float16", - ) - parser.add_argument( - "--upload-repo", - help="The Hugging Face repo to upload the model to.", - type=str, - default=None, - ) - parser.add_argument( - "-d", - "--dequantize", - help="Dequantize a quantized model.", - action="store_true", - default=False, - ) - return parser - - -def main(): - parser = configure_parser() - args = parser.parse_args() - convert(**vars(args)) - - -if __name__ == "__main__": - main() diff --git a/llms/mlx_lm/evaluate.py b/llms/mlx_lm/evaluate.py deleted file mode 100644 index cd6de7ec..00000000 --- a/llms/mlx_lm/evaluate.py +++ /dev/null @@ -1,392 +0,0 @@ -# Copyright © 2024 Apple Inc. - -""" -Adapted from a PyTorch implementation by David Grangier -""" - -import argparse -import json -import logging -import os -from importlib.metadata import version -from pathlib import Path -from typing import Optional, Union - -import lm_eval -import mlx.core as mx -import mlx.nn as nn -import numpy as np -from lm_eval.api.model import LM -from lm_eval.api.registry import register_model -from tqdm import tqdm - -from .models.cache import make_prompt_cache -from .utils import load, stream_generate - -PAD = 0 - - -def _len_longest_common_prefix(a, b): - l = 0 - for item_a, item_b in zip(a, b): - if item_a != item_b: - break - l += 1 - return l - - -def _rstrip_until(s, untils): - """Limit a string to the first occurrence of any substring in untils.""" - l = len(s) - f = [s.find(u) for u in untils] - f = [l if x < 0 else x for x in f] - return s[: min(f)] - - -def _pad_inputs( - inputs, - maxlen, - genlen=0, - pad_left=False, - pad_multiple=32, - truncate=False, -): - # pad the prompts to the left with at least genlen tokens. - actual_maxlen = max(len(p) for p in inputs) + genlen - if actual_maxlen > maxlen: - if not truncate: - raise ValueError("Inputs are too long.") - else: # drop begining - actual_maxlen = maxlen - inputs = [p[max(0, len(p) - maxlen) :] for p in inputs] - if pad_multiple > 0: - maxlen = (actual_maxlen + pad_multiple - 1) // pad_multiple - maxlen *= pad_multiple - assert PAD == 0 - lr = np.array((1, 0) if pad_left else (0, 1)) - return np.stack( - [np.pad(np.array(x, np.int32), lr * (maxlen - len(x))) for x in inputs], - axis=0, - ) - - -@register_model("mlxlm") -class MLXLM(LM): - def __init__( - self, - path_or_hf_repo: str, - batch_size: int = 16, - max_tokens: Optional[int] = None, - use_chat_template: Optional[bool] = None, - ) -> None: - super().__init__() - self._batch_size = batch_size - self._model, self.tokenizer = load(path_or_hf_repo) - self._max_tokens = max_tokens or self.tokenizer.model_max_length - self.use_chat_template = use_chat_template or ( - self.tokenizer.chat_template is not None - ) - - def _score_fn(self, inputs, tokenize=True, step_size=32): - if tokenize: - inputs = self._tokenize(inputs) - inputs = _pad_inputs(inputs, self._max_tokens, truncate=False) - inputs = mx.array(inputs) - inputs, targets = inputs[..., :-1], inputs[..., 1:] - - cache = make_prompt_cache(self._model) - - mask = targets != PAD - - scores, is_greedy = [], [] - for i in range(0, inputs.shape[1], step_size): - logits = self._model(inputs[:, i : i + step_size], cache=cache) - - log_probs = nn.log_softmax(logits.astype(mx.float32)) - score = mx.take_along_axis( - log_probs, targets[:, i : i + step_size, mx.newaxis], axis=-1 - )[..., 0] - ig = mask[:, i : i + step_size] * ( - targets[:, i : i + step_size] == mx.argmax(logits, axis=-1) - ) - - mx.eval(score, ig) - mx.metal.clear_cache() - - is_greedy.append(ig) - scores.append(score) - - scores = mx.concatenate(scores, axis=1) - is_greedy = mx.concatenate(is_greedy, axis=1) - - return scores, mask.sum(axis=-1), is_greedy - - def _loglikelihood(self, texts, score_spans=None, tokenize=True): - # sort by length to get batches with little padding. - sorted_indices = sorted(range(len(texts)), key=lambda i: -len(texts[i])) - sorted_inputs = [texts[sorted_indices[i]] for i in range(len(texts))] - sorted_spans = None - if score_spans is not None: - sorted_spans = [score_spans[sorted_indices[i]] for i in range(len(texts))] - - results = [] - for i in tqdm(range(0, len(sorted_inputs), self._batch_size)): - batch = sorted_inputs[i : i + self._batch_size] - scores, length, is_greedy = self._score_fn(batch, tokenize=tokenize) - for j in range(len(batch)): - if sorted_spans is None: # full sequence score - mask = mx.arange(scores[j].shape[-1]) < length - score = (scores[j].astype(mx.float32) * mask).sum(axis=-1) - ig = (is_greedy[j].astype(mx.int32) * mask).sum(axis=-1) - else: # subsequence score - start, end = sorted_spans[i + j] - score = scores[j][start:end].astype(mx.float32).sum() - ig = is_greedy[j][start:end].astype(mx.int32).sum() - length = end - start - - results.append((score.item(), ig.item(), length)) - - # reorder the outputs - inv_sort = np.argsort(sorted_indices) - results = [results[inv_sort[i]] for i in range(len(results))] - - return results - - def _tokenize(self, texts): - return [ - tuple( - self.tokenizer.encode(t, add_special_tokens=not self.use_chat_template) - ) - for t in texts - ] - - def loglikelihood(self, requests) -> list[tuple[float, bool]]: - """Compute log-likelihood of generating a continuation from a context. - Downstream tasks should attempt to use loglikelihood instead of other - LM calls whenever possible. - :param requests: list[Instance] - A list of Instance objects, with property `args` which returns a tuple (context, continuation). - `context: str` - Context string. Implementations of LM must be able to handle an - empty context string. - `continuation: str` - The continuation over which log likelihood will be calculated. If - there is a word boundary, the space should be in the continuation. - For example, context="hello" continuation=" world" is correct. - :return: list[tuple[float, bool]] - A list of pairs (logprob, isgreedy) - `logprob: float` - The log probability of `continuation`. - `isgreedy`: - Whether `continuation` would be generated by greedy sampling from `context`. - """ - logging.info("Estimating loglikelihood for %d pairs." % len(requests)) - - # tokenize prefix and prefix + completion for all requests. - tokenized = self._tokenize( - [t for r in requests for t in [r.args[0], r.args[0] + r.args[1]]] - ) - - # max length (prefix + completion) and longest common prefix per question. - length_stats = {} - for prefix, completed in zip(tokenized[0::2], tokenized[1::2]): - max_completed_l, min_prefix_l = length_stats.get(prefix, (0, 1e8)) - length_stats[prefix] = ( - max(max_completed_l, len(completed)), - min(min_prefix_l, _len_longest_common_prefix(prefix, completed)), - ) - - # truncate requests for completed sequences longer than model context. - shortened = [] - completion_spans = [] - long_completions = 0 - for prefix, completed in zip(tokenized[0::2], tokenized[1::2]): - max_completed_l, prefix_l = length_stats[prefix] - # compute truncation length - truncation = max(0, max_completed_l - self._max_tokens - 1) - prefix_l = prefix_l - truncation - if prefix_l <= 0: - # completion too long, prefix is eliminated for some requests. - long_completions += 1 - truncation = max(0, len(completed) - self._max_tokens - 1) - prefix_l = 1 - # truncate the completed sequence - completed = completed[truncation:] - shortened.append(completed) - # scores do not include initial bos, substract 1 to span bounds - completion_spans.append((prefix_l - 1, len(completed) - 1)) - - if long_completions > 0: - logging.info( - f"Prefix eliminated for {long_completions} requests with " - + "completion longer than context." - ) - - # model scoring, returns num_requests x (logp, is_greedy, length). - results = self._loglikelihood( - shortened, - score_spans=completion_spans, - tokenize=False, - ) - return [(r[0], r[1] == r[2]) for r in results] - - tokenizer_name = lm_eval.models.huggingface.HFLM.tokenizer_name - apply_chat_template = lm_eval.models.huggingface.HFLM.apply_chat_template - - def loglikelihood_rolling(self, requests) -> list[float]: - """Compute full log-likelihood of a string, with no truncation, for perplexity computation - - We will use the full max context length of the model. - - For inputs that exceed the max context length, we divide the tokenized string into chunks of up to - the max context length. - - IMPORTANT: Each document's loglikelihood/perplexity is computed *separately*, unlike other implementations - which may simply concatenate multiple documents together. - - IMPORTANT: We maximize the amount of context for each prediction. Specifically, for inputs that we break into - multiple chunks, the last input will still a full-sized context. - Example: - Input tokens: [ 0 1 2 3 4 5 6 7 8 9 ] - Prefix: EOT - Max context length: 4 - Resulting input/prediction pairs: - INPUT: EOT 0 1 2 - PRED: 0 1 2 3 - INPUT: 3 4 5 6 - PRED: 4 5 6 7 - INPUT: 5 6 7 8 - PRED: 8 9 - Observe that: - 1. Each token is predicted exactly once - 2. For the last pair, we provide the full context, but only score the last two tokens - :param requests: list[Instance] - A list of Instance objects with property `args` which returns a tuple (context,). - string: str - String for which we are computing overall loglikelihood - :return: list[tuple[float]] - A list of tuples (logprob,) - logprob: float - The log probability of `context` conditioned on the EOT token. - """ - logging.info( - "Estimating loglikelihood rolling for %d sequences." % len(requests) - ) - inputs = [req.args[0] for req in requests] - return [t[0] for t in self._loglikelihood(inputs)] - - def generate_until(self, requests) -> list[str]: - """Generate greedily until a stopping sequence - :param requests: list[Instance] - A list of Instance objects with property `args` which returns a tuple (context, until). - context: str - Context string - until: [str] - The string sequences to generate until. These string sequences - may each span across multiple tokens, or may be part of one token. - :return: list[str] - A list of strings continuation - continuation: str - The generated continuation. - """ - logging.info("Generating continuation for %d sequences." % len(requests)) - contexts, options = zip(*[req.args for req in requests]) - # contrary to the doc the second element of the tuple contains - # {'do_sample': False, 'until': ['\n\n'], 'temperature': 0} - completions = [] - - for context, opt in tqdm(zip(contexts, options), total=len(contexts)): - until = opt["until"] - context = self.tokenizer.encode( - context, add_special_tokens=not self.use_chat_template - ) - max_tokens = min( - opt.get("max_gen_tokens", self._max_tokens), - self.tokenizer.model_max_length - len(context), - ) - text = "" - for response in stream_generate( - self._model, self.tokenizer, prompt=context, max_tokens=max_tokens - ): - text += response.text - if any(u in text for u in until): - text = _rstrip_until(text, until) - completions.append(text) - break - else: - completions.append(text) - return completions - - -def main(): - parser = argparse.ArgumentParser( - "Evaluate an MLX model using lm-evaluation-harness." - ) - parser.add_argument("--model", help="Model to evaluate", required=True) - parser.add_argument("--tasks", nargs="+", required=True) - parser.add_argument( - "--output-dir", default=".", help="Output directory for result files." - ) - parser.add_argument("--batch-size", type=int, default=16, help="Batch size") - parser.add_argument("--num-shots", type=int, default=0, help="Number of shots") - parser.add_argument( - "--max-tokens", - type=int, - help="Maximum nunber of tokens to generate. Defaults to the model's max context length.", - ) - parser.add_argument( - "--limit", - default=100, - help="Limit the number of examples per task.", - type=int, - ) - parser.add_argument("--seed", type=int, default=123, help="Random seed.") - parser.add_argument( - "--fewshot-as-multiturn", - action="store_true", - help="Whether to provide the fewshot examples as a multiturn " - "conversation or a single user turn.", - default=False, - ) - parser.add_argument( - "--apply-chat-template", - action=argparse.BooleanOptionalAction, - help="Specifies whether to apply a chat template to the prompt. If " - "the model has a chat template, this defaults to `True`, " - "otherwise `False`.", - default=None, - ) - args = parser.parse_args() - - output_dir = Path(args.output_dir) - output_dir.mkdir(parents=True, exist_ok=True) - - # Silence tokenizer warnings - os.environ["TOKENIZERS_PARALLELISM"] = "false" - - mx.random.seed(args.seed) - - lm = MLXLM( - args.model, - batch_size=args.batch_size, - max_tokens=args.max_tokens, - use_chat_template=args.apply_chat_template, - ) - results = lm_eval.simple_evaluate( - model=lm, - tasks=args.tasks, - fewshot_as_multiturn=args.fewshot_as_multiturn, - apply_chat_template=lm.use_chat_template, - num_fewshot=args.num_shots, - limit=args.limit, - random_seed=args.seed, - numpy_random_seed=args.seed, - torch_random_seed=args.seed, - fewshot_random_seed=args.seed, - ) - - model_name = args.model.replace("/", "_") - task_names = "_".join(args.tasks) - ver = version("lm_eval") - filename = f"eval_{model_name}_{task_names}_{args.num_shots:02d}_v_{ver}.json" - output_path = output_dir / filename - output_path.write_text(json.dumps(results["results"], indent=4)) - print("Results:") - for result in results["results"].values(): - print(json.dumps(result, indent=4)) diff --git a/llms/mlx_lm/examples/chat.py b/llms/mlx_lm/examples/chat.py deleted file mode 100644 index dcd90b67..00000000 --- a/llms/mlx_lm/examples/chat.py +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright © 2024 Apple Inc. - -""" -An example of a multi-turn chat with prompt caching. -""" - -from mlx_lm import generate, load -from mlx_lm.models.cache import load_prompt_cache, make_prompt_cache, save_prompt_cache - -model, tokenizer = load("mlx-community/Mistral-7B-Instruct-v0.3-4bit") - -# Make the initial prompt cache for the model -prompt_cache = make_prompt_cache(model) - -# User turn -prompt = "Hi my name is ." -messages = [{"role": "user", "content": prompt}] -prompt = tokenizer.apply_chat_template(messages, add_generation_prompt=True) - -# Assistant response -response = generate( - model, - tokenizer, - prompt=prompt, - verbose=True, - prompt_cache=prompt_cache, -) - -# User turn -prompt = "What's my name?" -messages = [{"role": "user", "content": prompt}] -prompt = tokenizer.apply_chat_template(messages, add_generation_prompt=True) - -# Assistant response -response = generate( - model, - tokenizer, - prompt=prompt, - verbose=True, - prompt_cache=prompt_cache, -) - -# Save the prompt cache to disk to reuse it at a later time -save_prompt_cache("mistral_prompt.safetensors", prompt_cache) - -# Load the prompt cache from disk -prompt_cache = load_prompt_cache("mistral_prompt.safetensors") diff --git a/llms/mlx_lm/examples/generate_response.py b/llms/mlx_lm/examples/generate_response.py deleted file mode 100644 index 41eaf1da..00000000 --- a/llms/mlx_lm/examples/generate_response.py +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright © 2024 Apple Inc. - -from mlx_lm import generate, load - -# Specify the checkpoint -checkpoint = "mistralai/Mistral-7B-Instruct-v0.3" - -# Load the corresponding model and tokenizer -model, tokenizer = load(path_or_hf_repo=checkpoint) - -# Specify the prompt and conversation history -prompt = "Why is the sky blue?" -conversation = [{"role": "user", "content": prompt}] - -# Transform the prompt into the chat template -prompt = tokenizer.apply_chat_template( - conversation=conversation, add_generation_prompt=True -) - -# Specify the maximum number of tokens -max_tokens = 1_000 - -# Specify if tokens and timing information will be printed -verbose = True - -# Generate a response with the specified settings -response = generate( - model=model, - tokenizer=tokenizer, - prompt=prompt, - max_tokens=max_tokens, - verbose=verbose, -) diff --git a/llms/mlx_lm/examples/lora_config.yaml b/llms/mlx_lm/examples/lora_config.yaml deleted file mode 100644 index 36bc1dff..00000000 --- a/llms/mlx_lm/examples/lora_config.yaml +++ /dev/null @@ -1,89 +0,0 @@ -# The path to the local model directory or Hugging Face repo. -model: "mlx_model" - -# Whether or not to train (boolean) -train: true - -# The fine-tuning method: "lora", "dora", or "full". -fine_tune_type: lora - -# The Optimizer with its possible inputs -optimizer: adamw -# optimizer_config: -# adamw: -# betas: [0.9, 0.98] -# eps: 1e-6 -# weight_decay: 0.05 -# bias_correction: true - -# Directory with {train, valid, test}.jsonl files -data: "/path/to/training/data" - -# The PRNG seed -seed: 0 - -# Number of layers to fine-tune -num_layers: 16 - -# Minibatch size. -batch_size: 4 - -# Iterations to train for. -iters: 1000 - -# Number of validation batches, -1 uses the entire validation set. -val_batches: 25 - -# Adam learning rate. -learning_rate: 1e-5 - -# Number of training steps between loss reporting. -steps_per_report: 10 - -# Number of training steps between validations. -steps_per_eval: 200 - -# Load path to resume training with the given adapter weights. -resume_adapter_file: null - -# Save/load path for the trained adapter weights. -adapter_path: "adapters" - -# Save the model every N iterations. -save_every: 100 - -# Evaluate on the test set after training -test: false - -# Number of test set batches, -1 uses the entire test set. -test_batches: 100 - -# Maximum sequence length. -max_seq_length: 2048 - -# Use gradient checkpointing to reduce memory use. -grad_checkpoint: false - -# LoRA parameters can only be specified in a config file -lora_parameters: - # The layer keys to apply LoRA to. - # These will be applied for the last lora_layers - keys: ["self_attn.q_proj", "self_attn.v_proj"] - rank: 8 - scale: 20.0 - dropout: 0.0 - -# Schedule can only be specified in a config file, uncomment to use. -#lr_schedule: -# name: cosine_decay -# warmup: 100 # 0 for no warmup -# warmup_init: 1e-7 # 0 if not specified -# arguments: [1e-5, 1000, 1e-7] # passed to scheduler - -#hf_dataset: -# name: "billsum" -# train_split: "train[:1000]" -# valid_split: "train[-100:]" -# prompt_feature: "text" -# completion_feature: "summary" - diff --git a/llms/mlx_lm/examples/merge_config.yaml b/llms/mlx_lm/examples/merge_config.yaml deleted file mode 100644 index 98701e55..00000000 --- a/llms/mlx_lm/examples/merge_config.yaml +++ /dev/null @@ -1,11 +0,0 @@ -models: - - OpenPipe/mistral-ft-optimized-1218 - - mlabonne/NeuralHermes-2.5-Mistral-7B -method: slerp -parameters: - t: - - filter: self_attn - value: [0, 0.5, 0.3, 0.7, 1] - - filter: mlp - value: [1, 0.5, 0.7, 0.3, 0] - - value: 0.5 diff --git a/llms/mlx_lm/examples/pipeline_generate.py b/llms/mlx_lm/examples/pipeline_generate.py deleted file mode 100644 index 1e4fb445..00000000 --- a/llms/mlx_lm/examples/pipeline_generate.py +++ /dev/null @@ -1,127 +0,0 @@ -# Copyright © 2024 Apple Inc. - -""" -Run with: - -``` -mlx.launch \ - --hostfile /path/to/hosts.txt \ - --backend mpi \ - /path/to/pipeline_generate.py \ - --prompt "hello world" -``` - -Make sure you can run MLX over MPI on two hosts. For more information see the -documentation: - -https://ml-explore.github.io/mlx/build/html/usage/distributed.html). -""" - -import argparse -import json -from pathlib import Path - -import mlx.core as mx -from huggingface_hub import snapshot_download -from mlx.utils import tree_flatten -from mlx_lm import load, stream_generate -from mlx_lm.utils import load_model, load_tokenizer - - -def download(repo: str, allow_patterns: list[str]) -> Path: - return Path( - snapshot_download( - repo, - allow_patterns=allow_patterns, - ) - ) - - -def shard_and_load(repo): - # Get model path with everything but weight safetensors - model_path = download( - args.model, - allow_patterns=["*.json", "*.py", "tokenizer.model", "*.tiktoken", "*.txt"], - ) - - # Lazy load and shard model to figure out - # which weights we need - model, _ = load_model(model_path, lazy=True, strict=False) - - group = mx.distributed.init(backend="mpi") - rank = group.rank() - model.model.pipeline(group) - - # Figure out which files we need for the local shard - with open(model_path / "model.safetensors.index.json", "r") as fid: - weight_index = json.load(fid)["weight_map"] - - local_files = set() - for k, _ in tree_flatten(model.parameters()): - local_files.add(weight_index[k]) - - # Download weights for local shard - download(args.model, allow_patterns=local_files) - - # Load and shard the model, and load the weights - tokenizer = load_tokenizer(model_path) - model, _ = load_model(model_path, lazy=True, strict=False) - model.model.pipeline(group) - mx.eval(model.parameters()) - - # Synchronize processes before generation to avoid timeout if downloading - # model for the first time. - mx.eval(mx.distributed.all_sum(mx.array(1.0), stream=mx.cpu)) - return model, tokenizer - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="LLM pipelined inference example") - parser.add_argument( - "--model", - default="mlx-community/DeepSeek-R1-3bit", - help="HF repo or path to local model.", - ) - parser.add_argument( - "--prompt", - "-p", - default="Write a quicksort in C++.", - help="Message to be processed by the model ('-' reads from stdin)", - ) - parser.add_argument( - "--max-tokens", - "-m", - type=int, - default=256, - help="Maximum number of tokens to generate", - ) - args = parser.parse_args() - - group = mx.distributed.init(backend="mpi") - rank = group.rank() - - def rprint(*args, **kwargs): - if rank == 0: - print(*args, **kwargs) - - model, tokenizer = shard_and_load(args.model) - - messages = [{"role": "user", "content": args.prompt}] - prompt = tokenizer.apply_chat_template(messages, add_generation_prompt=True) - - for response in stream_generate( - model, tokenizer, prompt, max_tokens=args.max_tokens - ): - rprint(response.text, end="", flush=True) - - rprint() - rprint("=" * 10) - rprint( - f"Prompt: {response.prompt_tokens} tokens, " - f"{response.prompt_tps:.3f} tokens-per-sec" - ) - rprint( - f"Generation: {response.generation_tokens} tokens, " - f"{response.generation_tps:.3f} tokens-per-sec" - ) - rprint(f"Peak memory: {response.peak_memory:.3f} GB") diff --git a/llms/mlx_lm/examples/tool_use.py b/llms/mlx_lm/examples/tool_use.py deleted file mode 100644 index 624b9e5b..00000000 --- a/llms/mlx_lm/examples/tool_use.py +++ /dev/null @@ -1,73 +0,0 @@ -# Copyright © 2025 Apple Inc. - -import json - -from mlx_lm import generate, load -from mlx_lm.models.cache import make_prompt_cache - -# Specify the checkpoint -checkpoint = "mlx-community/Qwen2.5-32B-Instruct-4bit" - -# Load the corresponding model and tokenizer -model, tokenizer = load(path_or_hf_repo=checkpoint) - - -# An example tool, make sure to include a docstring and type hints -def multiply(a: float, b: float): - """ - A function that multiplies two numbers - - Args: - a: The first number to multiply - b: The second number to multiply - """ - return a * b - - -tools = {"multiply": multiply} - -# Specify the prompt and conversation history -prompt = "Multiply 12234585 and 48838483920." -messages = [{"role": "user", "content": prompt}] - -prompt = tokenizer.apply_chat_template( - messages, add_generation_prompt=True, tools=list(tools.values()) -) - -prompt_cache = make_prompt_cache(model) - -# Generate the initial tool call: -response = generate( - model=model, - tokenizer=tokenizer, - prompt=prompt, - max_tokens=2048, - verbose=True, - prompt_cache=prompt_cache, -) - -# Parse the tool call: -# (Note, the tool call format is model specific) -tool_open = "" -tool_close = "" -start_tool = response.find(tool_open) + len(tool_open) -end_tool = response.find(tool_close) -tool_call = json.loads(response[start_tool:end_tool].strip()) -tool_result = tools[tool_call["name"]](**tool_call["arguments"]) - -# Put the tool result in the prompt -messages = [{"role": "tool", "name": tool_call["name"], "content": tool_result}] -prompt = tokenizer.apply_chat_template( - messages, - add_generation_prompt=True, -) - -# Generate the final response: -response = generate( - model=model, - tokenizer=tokenizer, - prompt=prompt, - max_tokens=2048, - verbose=True, - prompt_cache=prompt_cache, -) diff --git a/llms/mlx_lm/fuse.py b/llms/mlx_lm/fuse.py deleted file mode 100644 index b0c46a74..00000000 --- a/llms/mlx_lm/fuse.py +++ /dev/null @@ -1,130 +0,0 @@ -import argparse -import glob -import shutil -from pathlib import Path - -from mlx.utils import tree_flatten, tree_unflatten - -from .gguf import convert_to_gguf -from .tuner.dora import DoRAEmbedding, DoRALinear -from .tuner.lora import LoRAEmbedding, LoRALinear, LoRASwitchLinear -from .tuner.utils import dequantize, load_adapters -from .utils import ( - fetch_from_hub, - get_model_path, - save_config, - save_weights, - upload_to_hub, -) - - -def parse_arguments() -> argparse.Namespace: - parser = argparse.ArgumentParser( - description="Fuse fine-tuned adapters into the base model." - ) - parser.add_argument( - "--model", - default="mlx_model", - help="The path to the local model directory or Hugging Face repo.", - ) - parser.add_argument( - "--save-path", - default="fused_model", - help="The path to save the fused model.", - ) - parser.add_argument( - "--adapter-path", - type=str, - default="adapters", - help="Path to the trained adapter weights and config.", - ) - parser.add_argument( - "--hf-path", - type=str, - default=None, - help="Path to the original Hugging Face model. Required for upload if --model is a local directory.", - ) - parser.add_argument( - "--upload-repo", - help="The Hugging Face repo to upload the model to.", - type=str, - default=None, - ) - parser.add_argument( - "--de-quantize", - help="Generate a de-quantized model.", - action="store_true", - ) - parser.add_argument( - "--export-gguf", - help="Export model weights in GGUF format.", - action="store_true", - ) - parser.add_argument( - "--gguf-path", - help="Path to save the exported GGUF format model weights. Default is ggml-model-f16.gguf.", - default="ggml-model-f16.gguf", - type=str, - ) - return parser.parse_args() - - -def main() -> None: - print("Loading pretrained model") - args = parse_arguments() - - model_path = get_model_path(args.model) - model, config, tokenizer = fetch_from_hub(model_path) - - model.freeze() - model = load_adapters(model, args.adapter_path) - - fused_linears = [ - (n, m.fuse()) for n, m in model.named_modules() if hasattr(m, "fuse") - ] - - if fused_linears: - model.update_modules(tree_unflatten(fused_linears)) - - if args.de_quantize: - print("De-quantizing model") - model = dequantize(model) - - weights = dict(tree_flatten(model.parameters())) - - save_path = Path(args.save_path) - - save_weights(save_path, weights) - - py_files = glob.glob(str(model_path / "*.py")) - for file in py_files: - shutil.copy(file, save_path) - - tokenizer.save_pretrained(save_path) - - if args.de_quantize: - config.pop("quantization", None) - - save_config(config, config_path=save_path / "config.json") - - if args.export_gguf: - model_type = config["model_type"] - if model_type not in ["llama", "mixtral", "mistral"]: - raise ValueError( - f"Model type {model_type} not supported for GGUF conversion." - ) - convert_to_gguf(model_path, weights, config, str(save_path / args.gguf_path)) - - if args.upload_repo is not None: - hf_path = args.hf_path or ( - args.model if not Path(args.model).exists() else None - ) - if hf_path is None: - raise ValueError( - "Must provide original Hugging Face repo to upload local model." - ) - upload_to_hub(args.save_path, args.upload_repo, hf_path) - - -if __name__ == "__main__": - main() diff --git a/llms/mlx_lm/generate.py b/llms/mlx_lm/generate.py deleted file mode 100644 index 7d58da82..00000000 --- a/llms/mlx_lm/generate.py +++ /dev/null @@ -1,287 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -import argparse -import json -import sys - -import mlx.core as mx - -from .models.cache import QuantizedKVCache, load_prompt_cache -from .sample_utils import make_sampler -from .utils import generate, load - -DEFAULT_PROMPT = "hello" -DEFAULT_MAX_TOKENS = 100 -DEFAULT_TEMP = 0.0 -DEFAULT_TOP_P = 1.0 -DEFAULT_MIN_P = 0.0 -DEFAULT_MIN_TOKENS_TO_KEEP = 1 -DEFAULT_SEED = None -DEFAULT_MODEL = "mlx-community/Llama-3.2-3B-Instruct-4bit" -DEFAULT_QUANTIZED_KV_START = 5000 - - -def str2bool(string): - return string.lower() not in ["false", "f"] - - -def setup_arg_parser(): - """Set up and return the argument parser.""" - parser = argparse.ArgumentParser(description="LLM inference script") - parser.add_argument( - "--model", - type=str, - help=( - "The path to the local model directory or Hugging Face repo. " - f"If no model is specified, then {DEFAULT_MODEL} is used." - ), - default=None, - ) - parser.add_argument( - "--adapter-path", - type=str, - help="Optional path for the trained adapter weights and config.", - ) - parser.add_argument( - "--extra-eos-token", - type=str, - default=(), - nargs="+", - help="Add tokens in the list of eos tokens that stop generation.", - ) - parser.add_argument( - "--system-prompt", - default=None, - help="System prompt to be used for the chat template", - ) - parser.add_argument( - "--prompt", - "-p", - default=DEFAULT_PROMPT, - help="Message to be processed by the model ('-' reads from stdin)", - ) - parser.add_argument( - "--prefill-response", - default=None, - help="Prefill response to be used for the chat template", - ) - parser.add_argument( - "--max-tokens", - "-m", - type=int, - default=DEFAULT_MAX_TOKENS, - help="Maximum number of tokens to generate", - ) - parser.add_argument( - "--temp", type=float, default=DEFAULT_TEMP, help="Sampling temperature" - ) - parser.add_argument( - "--top-p", type=float, default=DEFAULT_TOP_P, help="Sampling top-p" - ) - parser.add_argument( - "--min-p", type=float, default=DEFAULT_MIN_P, help="Sampling min-p" - ) - parser.add_argument( - "--min-tokens-to-keep", - type=int, - default=DEFAULT_MIN_TOKENS_TO_KEEP, - help="Minimum tokens to keep for min-p sampling.", - ) - parser.add_argument( - "--seed", - type=int, - default=DEFAULT_SEED, - help="PRNG seed", - ) - parser.add_argument( - "--ignore-chat-template", - action="store_true", - help="Use the raw prompt without the tokenizer's chat template.", - ) - parser.add_argument( - "--use-default-chat-template", - action="store_true", - help="Use the default chat template", - ) - parser.add_argument( - "--chat-template-config", - help="Additional config for `apply_chat_template`. Should be a dictionary of" - " string keys to values represented as a JSON decodable string.", - default=None, - ) - parser.add_argument( - "--verbose", - type=str2bool, - default=True, - help="Log verbose output when 'True' or 'T' or only print the response when 'False' or 'F'", - ) - parser.add_argument( - "--max-kv-size", - type=int, - help="Set the maximum key-value cache size", - default=None, - ) - parser.add_argument( - "--prompt-cache-file", - type=str, - default=None, - help="A file containing saved KV caches to avoid recomputing them", - ) - parser.add_argument( - "--kv-bits", - type=int, - help="Number of bits for KV cache quantization. " - "Defaults to no quantization.", - default=None, - ) - parser.add_argument( - "--kv-group-size", - type=int, - help="Group size for KV cache quantization.", - default=64, - ) - parser.add_argument( - "--quantized-kv-start", - help="When --kv-bits is set, start quantizing the KV cache " - "from this step onwards.", - type=int, - default=DEFAULT_QUANTIZED_KV_START, - ) - parser.add_argument( - "--draft-model", - type=str, - help="A model to be used for speculative decoding.", - default=None, - ) - parser.add_argument( - "--num-draft-tokens", - type=int, - help="Number of tokens to draft when using speculative decoding.", - default=3, - ) - return parser - - -def main(): - parser = setup_arg_parser() - args = parser.parse_args() - - if args.seed is not None: - mx.random.seed(args.seed) - - # Load the prompt cache and metadata if a cache file is provided - using_cache = args.prompt_cache_file is not None - if using_cache: - prompt_cache, metadata = load_prompt_cache( - args.prompt_cache_file, - return_metadata=True, - ) - if isinstance(prompt_cache[0], QuantizedKVCache): - if args.kv_bits is not None and args.kv_bits != prompt_cache[0].bits: - raise ValueError( - "--kv-bits does not match the kv cache loaded from --prompt-cache-file." - ) - if args.kv_group_size != prompt_cache[0].group_size: - raise ValueError( - "--kv-group-size does not match the kv cache loaded from --prompt-cache-file." - ) - - # Building tokenizer_config - tokenizer_config = ( - {} if not using_cache else json.loads(metadata["tokenizer_config"]) - ) - tokenizer_config["trust_remote_code"] = True - - model_path = args.model - if using_cache: - if model_path is None: - model_path = metadata["model"] - elif model_path != metadata["model"]: - raise ValueError( - f"Providing a different model ({model_path}) than that " - f"used to create the prompt cache ({metadata['model']}) " - "is an error." - ) - model_path = model_path or DEFAULT_MODEL - - model, tokenizer = load( - model_path, - adapter_path=args.adapter_path, - tokenizer_config=tokenizer_config, - ) - for eos_token in args.extra_eos_token: - tokenizer.add_eos_token(eos_token) - - template_kwargs = {} - if args.chat_template_config is not None: - template_kwargs = json.loads(args.chat_template_config) - - if args.use_default_chat_template: - if tokenizer.chat_template is None: - tokenizer.chat_template = tokenizer.default_chat_template - elif using_cache: - tokenizer.chat_template = json.loads(metadata["chat_template"]) - - prompt = args.prompt.replace("\\n", "\n").replace("\\t", "\t") - prompt = sys.stdin.read() if prompt == "-" else prompt - if not args.ignore_chat_template and tokenizer.chat_template is not None: - if args.system_prompt is not None: - messages = [{"role": "system", "content": args.system_prompt}] - else: - messages = [] - messages.append({"role": "user", "content": prompt}) - - has_prefill = args.prefill_response is not None - if has_prefill: - messages.append({"role": "assistant", "content": args.prefill_response}) - prompt = tokenizer.apply_chat_template( - messages, - tokenize=False, - continue_final_message=has_prefill, - add_generation_prompt=not has_prefill, - **template_kwargs, - ) - - # Treat the prompt as a suffix assuming that the prefix is in the - # stored kv cache. - if using_cache: - messages[-1]["content"] = "" - test_prompt = tokenizer.apply_chat_template( - messages, - tokenize=False, - continue_final_message=has_prefill, - add_generation_prompt=not has_prefill, - ) - prompt = prompt[test_prompt.index("") :] - prompt = tokenizer.encode(prompt, add_special_tokens=False) - else: - prompt = tokenizer.encode(prompt) - - if args.draft_model is not None: - draft_model, draft_tokenizer = load(args.draft_model) - if draft_tokenizer.vocab_size != tokenizer.vocab_size: - raise ValueError("Draft model tokenizer does not match model tokenizer.") - else: - draft_model = None - sampler = make_sampler(args.temp, args.top_p, args.min_p, args.min_tokens_to_keep) - response = generate( - model, - tokenizer, - prompt, - max_tokens=args.max_tokens, - verbose=args.verbose, - sampler=sampler, - max_kv_size=args.max_kv_size, - prompt_cache=prompt_cache if using_cache else None, - kv_bits=args.kv_bits, - kv_group_size=args.kv_group_size, - quantized_kv_start=args.quantized_kv_start, - draft_model=draft_model, - num_draft_tokens=args.num_draft_tokens, - ) - if not args.verbose: - print(response) - - -if __name__ == "__main__": - main() diff --git a/llms/mlx_lm/gguf.py b/llms/mlx_lm/gguf.py deleted file mode 100644 index 241ac35a..00000000 --- a/llms/mlx_lm/gguf.py +++ /dev/null @@ -1,314 +0,0 @@ -import re -from enum import IntEnum -from pathlib import Path -from typing import Iterable, Optional, Set, Tuple, Union - -import mlx.core as mx -from transformers import AutoTokenizer - - -class TokenType(IntEnum): - NORMAL = 1 - UNKNOWN = 2 - CONTROL = 3 - USER_DEFINED = 4 - UNUSED = 5 - BYTE = 6 - - -class GGMLFileType(IntEnum): - GGML_TYPE_F16 = 1 - - -# copied from https://github.com/ggerganov/llama.cpp/blob/master/convert.py#L455 -class HfVocab: - def __init__( - self, - fname_tokenizer: Path, - fname_added_tokens: Optional[Union[Path, None]] = None, - ) -> None: - self.tokenizer = AutoTokenizer.from_pretrained( - fname_tokenizer, - cache_dir=fname_tokenizer, - local_files_only=True, - ) - self.added_tokens_list = [] - self.added_tokens_dict = dict() - self.added_tokens_ids = set() - for tok, tokidx in sorted( - self.tokenizer.get_added_vocab().items(), key=lambda x: x[1] - ): - if tokidx >= self.tokenizer.vocab_size: - self.added_tokens_list.append(tok) - self.added_tokens_dict[tok] = tokidx - self.added_tokens_ids.add(tokidx) - self.specials = { - tok: self.tokenizer.get_vocab()[tok] - for tok in self.tokenizer.all_special_tokens - } - self.special_ids = set(self.tokenizer.all_special_ids) - self.vocab_size_base = self.tokenizer.vocab_size - self.vocab_size = self.vocab_size_base + len(self.added_tokens_list) - self.fname_tokenizer = fname_tokenizer - self.fname_added_tokens = fname_added_tokens - - def hf_tokens(self) -> Iterable[Tuple[bytes, float, TokenType]]: - reverse_vocab = { - id: encoded_tok for encoded_tok, id in self.tokenizer.get_vocab().items() - } - for token_id in range(self.vocab_size_base): - if token_id in self.added_tokens_ids: - continue - token_text = reverse_vocab[token_id] - yield token_text, self.get_token_score(token_id), self.get_token_type( - token_id, token_text, self.special_ids - ) - - def get_token_type( - self, token_id: int, token_text: bytes, special_ids: Set[int] - ) -> TokenType: - if re.fullmatch(r"<0x[0-9A-Fa-f]{2}>", token_text): - return TokenType.BYTE - return TokenType.CONTROL if token_id in special_ids else TokenType.NORMAL - - def get_token_score(self, token_id: int) -> float: - return -1000.0 - - def added_tokens(self) -> Iterable[Tuple[bytes, float, TokenType]]: - for text in self.added_tokens_list: - if text in self.specials: - toktype = self.get_token_type(self.specials[text], "", self.special_ids) - score = self.get_token_score(self.specials[text]) - else: - toktype = TokenType.USER_DEFINED - score = -1000.0 - yield text, score, toktype - - def has_newline_token(self): - return "<0x0A>" in self.tokenizer.vocab or "\n" in self.tokenizer.vocab - - def all_tokens(self) -> Iterable[Tuple[bytes, float, TokenType]]: - yield from self.hf_tokens() - yield from self.added_tokens() - - def __repr__(self) -> str: - return f"" - - @staticmethod - def load(path: Path) -> "HfVocab": - added_tokens_path = path.parent / "added_tokens.json" - return HfVocab(path, added_tokens_path if added_tokens_path.exists() else None) - - -def translate_weight_names(name): - name = name.replace("model.layers.", "blk.") - # for mixtral gate - name = name.replace("block_sparse_moe.gate", "ffn_gate_inp") - # for mixtral experts ffns - pattern = r"block_sparse_moe\.experts\.(\d+)\.w1\.weight" - replacement = r"ffn_gate.\1.weight" - name = re.sub(pattern, replacement, name) - pattern = r"block_sparse_moe\.experts\.(\d+)\.w2\.weight" - replacement = r"ffn_down.\1.weight" - name = re.sub(pattern, replacement, name) - pattern = r"block_sparse_moe\.experts\.(\d+)\.w3\.weight" - replacement = r"ffn_up.\1.weight" - name = re.sub(pattern, replacement, name) - - name = name.replace("mlp.gate_proj", "ffn_gate") - name = name.replace("mlp.down_proj", "ffn_down") - name = name.replace("mlp.up_proj", "ffn_up") - name = name.replace("self_attn.q_proj", "attn_q") - name = name.replace("self_attn.k_proj", "attn_k") - name = name.replace("self_attn.v_proj", "attn_v") - name = name.replace("self_attn.o_proj", "attn_output") - name = name.replace("input_layernorm", "attn_norm") - name = name.replace("post_attention_layernorm", "ffn_norm") - name = name.replace("model.embed_tokens", "token_embd") - name = name.replace("model.norm", "output_norm") - name = name.replace("lm_head", "output") - return name - - -def permute_weights(weights, n_head, n_head_kv=None): - if n_head_kv is not None and n_head != n_head_kv: - n_head = n_head_kv - reshaped = weights.reshape( - n_head, 2, weights.shape[0] // n_head // 2, *weights.shape[1:] - ) - swapped = reshaped.swapaxes(1, 2) - final_shape = weights.shape - return swapped.reshape(final_shape) - - -def prepare_metadata(config, vocab): - metadata = { - "general.name": "llama", - "llama.context_length": ( - mx.array(config["max_position_embeddings"], dtype=mx.uint32) - if config.get("max_position_embeddings") is not None - else None - ), - "llama.embedding_length": ( - mx.array(config["hidden_size"], dtype=mx.uint32) - if config.get("hidden_size") is not None - else None - ), - "llama.block_count": ( - mx.array(config["num_hidden_layers"], dtype=mx.uint32) - if config.get("num_hidden_layers") is not None - else None - ), - "llama.feed_forward_length": ( - mx.array(config["intermediate_size"], dtype=mx.uint32) - if config.get("intermediate_size") is not None - else None - ), - "llama.rope.dimension_count": ( - mx.array( - config["hidden_size"] // config["num_attention_heads"], dtype=mx.uint32 - ) - if config.get("hidden_size") is not None - and config.get("num_attention_heads") is not None - else None - ), - "llama.attention.head_count": ( - mx.array(config["num_attention_heads"], dtype=mx.uint32) - if config.get("num_attention_heads") is not None - else None - ), - "llama.attention.head_count_kv": ( - mx.array( - config.get("num_key_value_heads", config["num_attention_heads"]), - dtype=mx.uint32, - ) - if config.get("num_attention_heads") is not None - else None - ), - "llama.expert_count": ( - mx.array(config.get("num_local_experts", None), dtype=mx.uint32) - if config.get("num_local_experts") is not None - else None - ), - "llama.expert_used_count": ( - mx.array(config.get("num_experts_per_tok", None), dtype=mx.uint32) - if config.get("num_experts_per_tok") is not None - else None - ), - "llama.attention.layer_norm_rms_epsilon": ( - mx.array(config.get("rms_norm_eps", 1e-05)) - if config.get("rms_norm_eps") is not None - else None - ), - "llama.rope.freq_base": ( - mx.array(config.get("rope_theta", 10000), dtype=mx.float32) - if config.get("rope_theta") is not None - else None - ), - } - - rope_scaling = config.get("rope_scaling") - if rope_scaling is not None and (typ := rope_scaling.get("type")): - rope_factor = rope_scaling.get("factor") - f_rope_scale = rope_factor - if typ == "linear": - rope_scaling_type = "linear" - metadata["llama.rope.scaling.type"] = rope_scaling_type - metadata["llama.rope.scaling.factor"] = mx.array(f_rope_scale) - - metadata["general.file_type"] = mx.array( - GGMLFileType.GGML_TYPE_F16.value, - dtype=mx.uint32, - ) - metadata["general.quantization_version"] = mx.array( - GGMLFileType.GGML_TYPE_F16.value, - dtype=mx.uint32, - ) - metadata["general.name"] = config.get("_name_or_path", "llama").split("/")[-1] - metadata["general.architecture"] = "llama" - metadata["general.alignment"] = mx.array(32, dtype=mx.uint32) - - # add metadata for vocab - metadata["tokenizer.ggml.model"] = "llama" - tokens = [] - scores = [] - toktypes = [] - for text, score, toktype in vocab.all_tokens(): - tokens.append(text) - scores.append(score) - toktypes.append(toktype.value) - assert len(tokens) == vocab.vocab_size - metadata["tokenizer.ggml.tokens"] = tokens - metadata["tokenizer.ggml.scores"] = mx.array(scores, dtype=mx.float32) - metadata["tokenizer.ggml.token_type"] = mx.array(toktypes, dtype=mx.uint32) - if vocab.tokenizer.bos_token_id is not None: - metadata["tokenizer.ggml.bos_token_id"] = mx.array( - vocab.tokenizer.bos_token_id, dtype=mx.uint32 - ) - if vocab.tokenizer.eos_token_id is not None: - metadata["tokenizer.ggml.eos_token_id"] = mx.array( - vocab.tokenizer.eos_token_id, dtype=mx.uint32 - ) - if vocab.tokenizer.unk_token_id is not None: - metadata["tokenizer.ggml.unknown_token_id"] = mx.array( - vocab.tokenizer.unk_token_id, dtype=mx.uint32 - ) - - metadata = {k: v for k, v in metadata.items() if v is not None} - return metadata - - -def convert_to_gguf( - model_path: Union[str, Path], - weights: dict, - config: dict, - output_file_path: str, -): - if isinstance(model_path, str): - model_path = Path(model_path) - - quantization = config.get("quantization", None) - if quantization: - raise NotImplementedError( - "Conversion of quantized models is not yet supported." - ) - print("Converting to GGUF format") - # https://github.com/ggerganov/llama.cpp/blob/master/convert.py#L1182 seems relate to llama.cpp's multihead attention - weights = { - k: ( - permute_weights( - v, config["num_attention_heads"], config["num_attention_heads"] - ) - if "self_attn.q_proj.weight" in k - else ( - permute_weights( - v, config["num_attention_heads"], config["num_key_value_heads"] - ) - if "self_attn.k_proj.weight" in k - else v - ) - ) - for k, v in weights.items() - } - - # rename weights for gguf format - weights = {translate_weight_names(k): v for k, v in weights.items()} - - if not (model_path / "tokenizer.json").exists(): - raise ValueError("Tokenizer json not found") - - vocab = HfVocab.load(model_path) - metadata = prepare_metadata(config, vocab) - - weights = { - k: ( - v.astype(mx.float32).astype(mx.float16) - if v.dtype == mx.bfloat16 - else v.astype(mx.float32) if "norm" in k else v - ) - for k, v in weights.items() - } - - output_file_path = output_file_path - mx.save_gguf(output_file_path, weights, metadata) - print(f"Converted GGUF model saved as: {output_file_path}") diff --git a/llms/mlx_lm/lora.py b/llms/mlx_lm/lora.py deleted file mode 100644 index 042b40e2..00000000 --- a/llms/mlx_lm/lora.py +++ /dev/null @@ -1,335 +0,0 @@ -# Copyright © 2024 Apple Inc. - -import argparse -import math -import os -import re -import types -from pathlib import Path - -import mlx.nn as nn -import mlx.optimizers as optim -import numpy as np -import yaml - -from .tokenizer_utils import TokenizerWrapper -from .tuner.datasets import load_dataset -from .tuner.trainer import TrainingArgs, TrainingCallback, evaluate, train -from .tuner.utils import ( - build_schedule, - linear_to_lora_layers, - load_adapters, - print_trainable_parameters, -) -from .utils import load, save_config - -yaml_loader = yaml.SafeLoader -yaml_loader.add_implicit_resolver( - "tag:yaml.org,2002:float", - re.compile( - """^(?: - [-+]?(?:[0-9][0-9_]*)\\.[0-9_]*(?:[eE][-+]?[0-9]+)? - |[-+]?(?:[0-9][0-9_]*)(?:[eE][-+]?[0-9]+) - |\\.[0-9_]+(?:[eE][-+][0-9]+)? - |[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]* - |[-+]?\\.(?:inf|Inf|INF) - |\\.(?:nan|NaN|NAN))$""", - re.X, - ), - list("-+0123456789."), -) - -CONFIG_DEFAULTS = { - "model": "mlx_model", - "train": False, - "fine_tune_type": "lora", - "optimizer": "adam", - "optimizer_config": { - "adam": {}, - "adamw": {}, - }, - "data": "data/", - "seed": 0, - "num_layers": 16, - "batch_size": 4, - "iters": 1000, - "val_batches": 25, - "learning_rate": 1e-5, - "steps_per_report": 10, - "steps_per_eval": 200, - "resume_adapter_file": None, - "adapter_path": "adapters", - "save_every": 100, - "test": False, - "test_batches": 500, - "max_seq_length": 2048, - "config": None, - "grad_checkpoint": False, - "lr_schedule": None, - "lora_parameters": {"rank": 8, "alpha": 16, "dropout": 0.0, "scale": 10.0}, - "mask_prompt": False, -} - - -def build_parser(): - parser = argparse.ArgumentParser(description="LoRA or QLoRA finetuning.") - parser.add_argument( - "--model", - type=str, - help="The path to the local model directory or Hugging Face repo.", - ) - - # Training args - parser.add_argument( - "--train", - action="store_true", - help="Do training", - default=None, - ) - parser.add_argument( - "--data", - type=str, - help=( - "Directory with {train, valid, test}.jsonl files or the name " - "of a Hugging Face dataset (e.g., 'mlx-community/wikisql')" - ), - ) - parser.add_argument( - "--fine-tune-type", - type=str, - choices=["lora", "dora", "full"], - help="Type of fine-tuning to perform: lora, dora, or full.", - ) - parser.add_argument( - "--optimizer", - type=str, - choices=["adam", "adamw"], - default=None, - help="Optimizer to use for training: adam or adamw", - ) - parser.add_argument( - "--mask-prompt", - action="store_true", - help="Mask the prompt in the loss when training", - default=None, - ) - parser.add_argument( - "--num-layers", - type=int, - help="Number of layers to fine-tune. Default is 16, use -1 for all.", - ) - parser.add_argument("--batch-size", type=int, help="Minibatch size.") - parser.add_argument("--iters", type=int, help="Iterations to train for.") - parser.add_argument( - "--val-batches", - type=int, - help="Number of validation batches, -1 uses the entire validation set.", - ) - parser.add_argument("--learning-rate", type=float, help="Adam learning rate.") - parser.add_argument( - "--steps-per-report", - type=int, - help="Number of training steps between loss reporting.", - ) - parser.add_argument( - "--steps-per-eval", - type=int, - help="Number of training steps between validations.", - ) - parser.add_argument( - "--resume-adapter-file", - type=str, - help="Load path to resume training from the given fine-tuned weights.", - ) - parser.add_argument( - "--adapter-path", - type=str, - help="Save/load path for the fine-tuned weights.", - ) - parser.add_argument( - "--save-every", - type=int, - help="Save the model every N iterations.", - ) - parser.add_argument( - "--test", - action="store_true", - help="Evaluate on the test set after training", - default=None, - ) - parser.add_argument( - "--test-batches", - type=int, - help="Number of test set batches, -1 uses the entire test set.", - ) - parser.add_argument( - "--max-seq-length", - type=int, - help="Maximum sequence length.", - ) - parser.add_argument( - "-c", - "--config", - type=str, - help="A YAML configuration file with the training options", - ) - parser.add_argument( - "--grad-checkpoint", - action="store_true", - help="Use gradient checkpointing to reduce memory use.", - default=None, - ) - parser.add_argument("--seed", type=int, help="The PRNG seed") - return parser - - -def train_model( - args, - model: nn.Module, - tokenizer: TokenizerWrapper, - train_set, - valid_set, - training_callback: TrainingCallback = None, -): - model.freeze() - if args.num_layers > len(model.layers): - raise ValueError( - f"Requested to train {args.num_layers} layers " - f"but the model only has {len(model.layers)} layers." - ) - - if args.fine_tune_type == "full": - for l in model.layers[-max(args.num_layers, 0) :]: - l.unfreeze() - elif args.fine_tune_type in ["lora", "dora"]: - # Convert linear layers to lora/dora layers and unfreeze in the process - linear_to_lora_layers( - model, - args.num_layers, - args.lora_parameters, - use_dora=(args.fine_tune_type == "dora"), - ) - else: - raise ValueError(f"Received unknown fine-tune-type {args.fine_tune_type}") - - # Resume from weights if provided - if args.resume_adapter_file is not None: - print(f"Loading fine-tuned weights from {args.resume_adapter_file}") - model.load_weights(args.resume_adapter_file, strict=False) - - print_trainable_parameters(model) - - adapter_path = Path(args.adapter_path) - adapter_path.mkdir(parents=True, exist_ok=True) - - adapter_file = adapter_path / "adapters.safetensors" - save_config(vars(args), adapter_path / "adapter_config.json") - - # init training args - training_args = TrainingArgs( - batch_size=args.batch_size, - iters=args.iters, - val_batches=args.val_batches, - steps_per_report=args.steps_per_report, - steps_per_eval=args.steps_per_eval, - steps_per_save=args.save_every, - adapter_file=adapter_file, - max_seq_length=args.max_seq_length, - grad_checkpoint=args.grad_checkpoint, - ) - - model.train() - - # Initialize the selected optimizer - lr = build_schedule(args.lr_schedule) if args.lr_schedule else args.learning_rate - - optimizer_name = args.optimizer.lower() - optimizer_config = args.optimizer_config.get(optimizer_name, {}) - - if optimizer_name == "adam": - opt_class = optim.Adam - elif optimizer_name == "adamw": - opt_class = optim.AdamW - else: - raise ValueError(f"Unsupported optimizer: {optimizer_name}") - - opt = opt_class(learning_rate=lr, **optimizer_config) - - # Train model - train( - model=model, - tokenizer=tokenizer, - args=training_args, - optimizer=opt, - train_dataset=train_set, - val_dataset=valid_set, - training_callback=training_callback, - ) - - -def evaluate_model(args, model: nn.Module, tokenizer: TokenizerWrapper, test_set): - model.eval() - - test_loss = evaluate( - model=model, - dataset=test_set, - tokenizer=tokenizer, - batch_size=args.batch_size, - num_batches=args.test_batches, - max_seq_length=args.max_seq_length, - ) - - test_ppl = math.exp(test_loss) - - print(f"Test loss {test_loss:.3f}, Test ppl {test_ppl:.3f}.") - - -def run(args, training_callback: TrainingCallback = None): - np.random.seed(args.seed) - - print("Loading pretrained model") - model, tokenizer = load(args.model) - - print("Loading datasets") - train_set, valid_set, test_set = load_dataset(args, tokenizer) - - if args.test and not args.train: - # Allow testing without LoRA layers by providing empty path - if args.adapter_path != "": - load_adapters(model, args.adapter_path) - - elif args.train: - print("Training") - train_model(args, model, tokenizer, train_set, valid_set, training_callback) - else: - raise ValueError("Must provide at least one of --train or --test") - - if args.test: - print("Testing") - evaluate_model(args, model, tokenizer, test_set) - - -def main(): - os.environ["TOKENIZERS_PARALLELISM"] = "true" - parser = build_parser() - args = parser.parse_args() - config = args.config - args = vars(args) - if config: - print("Loading configuration file", config) - with open(config, "r") as file: - config = yaml.load(file, yaml_loader) - # Prefer parameters from command-line arguments - for k, v in config.items(): - if args.get(k, None) is None: - args[k] = v - - # Update defaults for unspecified parameters - for k, v in CONFIG_DEFAULTS.items(): - if args.get(k, None) is None: - args[k] = v - run(types.SimpleNamespace(**args)) - - -if __name__ == "__main__": - main() diff --git a/llms/mlx_lm/manage.py b/llms/mlx_lm/manage.py deleted file mode 100644 index c06de6b3..00000000 --- a/llms/mlx_lm/manage.py +++ /dev/null @@ -1,139 +0,0 @@ -import argparse -from typing import List, Union - -from huggingface_hub import scan_cache_dir - - -def tabulate(rows: List[List[Union[str, int]]], headers: List[str]) -> str: - """ - Inspired by: - - stackoverflow.com/a/8356620/593036 - - stackoverflow.com/questions/9535954/printing-lists-as-tabular-data - """ - col_widths = [max(len(str(x)) for x in col) for col in zip(*rows, headers)] - row_format = ("{{:{}}} " * len(headers)).format(*col_widths) - lines = [] - lines.append(row_format.format(*headers)) - lines.append(row_format.format(*["-" * w for w in col_widths])) - for row in rows: - lines.append(row_format.format(*row)) - return "\n".join(lines) - - -def ask_for_confirmation(message: str) -> bool: - """Ask user for confirmation with Y/N prompt. - Returns True for Y/yes, False for N/no/empty.""" - y = ("y", "yes", "1") - n = ("n", "no", "0", "") - full_message = f"{message} (y/n) " - while True: - answer = input(full_message).lower() - if answer in y: - return True - if answer in n: - return False - print(f"Invalid input. Must be one of: yes/no/y/n or empty for no") - - -def main(): - parser = argparse.ArgumentParser(description="MLX Model Cache.") - parser.add_argument( - "--scan", - action="store_true", - help="Scan Hugging Face cache for mlx models.", - ) - parser.add_argument( - "--delete", - action="store_true", - help="Delete models matching the given pattern.", - ) - parser.add_argument( - "--pattern", - type=str, - help="Model repos contain the pattern.", - default="mlx", - ) - - args = parser.parse_args() - - if args.scan: - print(f'Scanning Hugging Face cache for models with pattern "{args.pattern}".') - hf_cache_info = scan_cache_dir() - print( - tabulate( - rows=[ - [ - repo.repo_id, - repo.repo_type, - "{:>12}".format(repo.size_on_disk_str), - repo.nb_files, - repo.last_accessed_str, - repo.last_modified_str, - str(repo.repo_path), - ] - for repo in sorted( - hf_cache_info.repos, key=lambda repo: repo.repo_path - ) - if args.pattern in repo.repo_id - ], - headers=[ - "REPO ID", - "REPO TYPE", - "SIZE ON DISK", - "NB FILES", - "LAST_ACCESSED", - "LAST_MODIFIED", - "LOCAL PATH", - ], - ) - ) - - if args.delete: - print(f'Deleting models matching pattern "{args.pattern}"') - hf_cache_info = scan_cache_dir() - - repos = [ - repo - for repo in sorted(hf_cache_info.repos, key=lambda repo: repo.repo_path) - if args.pattern in repo.repo_id - ] - if repos: - print("\nFound the following models:") - print( - tabulate( - rows=[ - [ - repo.repo_id, - repo.size_on_disk_str, # Added size information - str(repo.repo_path), - ] - for repo in repos - ], - headers=[ - "REPO ID", - "SIZE", # Added size header - "LOCAL PATH", - ], - ) - ) - - confirmed = ask_for_confirmation( - "\nAre you sure you want to delete these models?" - ) - if confirmed: - for model_info in repos: - print(f"\nDeleting {model_info.repo_id}...") - for revision in sorted( - model_info.revisions, key=lambda revision: revision.commit_hash - ): - strategy = hf_cache_info.delete_revisions(revision.commit_hash) - strategy.execute() - print("\nModel(s) deleted successfully.") - else: - print("\nDeletion cancelled - no changes made.") - else: - print(f'No models found matching pattern "{args.pattern}"') - - -if __name__ == "__main__": - main() diff --git a/llms/mlx_lm/merge.py b/llms/mlx_lm/merge.py deleted file mode 100644 index a009338e..00000000 --- a/llms/mlx_lm/merge.py +++ /dev/null @@ -1,172 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -import argparse -import glob -import shutil -from pathlib import Path -from typing import Optional - -import mlx.core as mx -import mlx.nn as nn -import numpy as np -import yaml -from mlx.utils import tree_flatten, tree_map - -from .utils import ( - fetch_from_hub, - get_model_path, - save_config, - save_weights, - upload_to_hub, -) - - -def configure_parser() -> argparse.ArgumentParser: - """ - Configures and returns the argument parser for the script. - - Returns: - argparse.ArgumentParser: Configured argument parser. - """ - parser = argparse.ArgumentParser(description="Merge multiple models.") - - parser.add_argument("--config", type=str, help="Path to the YAML config.") - parser.add_argument( - "--mlx-path", - type=str, - default="mlx_merged_model", - help="Path to save the MLX model.", - ) - parser.add_argument( - "--upload-repo", - help="The Hugging Face repo to upload the model to.", - type=str, - default=None, - ) - return parser - - -def slerp(t, w1, w2, eps=1e-5): - """ - Spherical linear interpolation - - Args: - t (float): Interpolation weight in [0.0, 1.0] - w1 (mx.array): First input - w2 (mx.array): Second input - eps (float): Constant for numerical stability - Returns: - mx.array: Interpolated result - """ - t = float(t) - if t == 0: - return w1 - elif t == 1: - return w2 - # Normalize - v1 = w1 / mx.linalg.norm(w1) - v2 = w2 / mx.linalg.norm(w2) - # Angle - dot = mx.clip((v1 * v2).sum(), 0.0, 1.0) - theta = mx.arccos(dot) - sin_theta = mx.sin(theta + eps) - s1 = mx.sin(theta * (1 - t)) / sin_theta - s2 = mx.sin(theta * t) / sin_theta - return s1 * w1 + s2 * w2 - - -def merge_models(base_model: nn.Module, model: nn.Module, config: dict): - method = config.get("method", None) - if method != "slerp": - raise ValueError(f"Merge method {method} not supported") - - num_layers = len(model.layers) - - def unpack_values(vals): - if isinstance(vals, (int, float)): - return np.full(num_layers, vals) - bins = len(vals) - 1 - sizes = [num_layers // bins] * bins - sizes[-1] = num_layers - sum(sizes[:-1]) - return np.concatenate( - [np.linspace(v1, v2, s) for v1, v2, s in zip(vals[:-1], vals[1:], sizes)] - ) - - param_list = config["parameters"]["t"] - params = {} - filter_keys = set() - for pl in param_list[:-1]: - params[pl["filter"]] = unpack_values(pl["value"]) - filter_keys.add(pl["filter"]) - default = unpack_values(param_list[-1]["value"]) - - for e in range(num_layers): - bl = base_model.layers[e] - l = model.layers[e] - base_weights = bl.parameters() - weights = l.parameters() - for k, w1 in base_weights.items(): - w2 = weights[k] - t = params.get(k, default)[e] - base_weights[k] = tree_map(lambda x, y: slerp(t, x, y), w1, w2) - base_model.update(base_weights) - - -def merge( - config: str, - mlx_path: str = "mlx_model", - upload_repo: Optional[str] = None, -): - with open(config, "r") as fid: - merge_conf = yaml.safe_load(fid) - print("[INFO] Loading") - - model_paths = merge_conf.get("models", []) - if len(model_paths) < 2: - raise ValueError(f"Expected at least 2 models, got {len(model_paths)}.") - - # Load all models - base_hf_path = model_paths[0] - base_path = get_model_path(base_hf_path) - base_model, base_config, tokenizer = fetch_from_hub(base_path, lazy=True) - models = [] - for mp in model_paths[1:]: - model, model_config, _ = fetch_from_hub(get_model_path(mp), lazy=True) - base_type = base_config["model_type"] - model_type = model_config["model_type"] - if base_type != model_type: - raise ValueError( - f"Can only merge models of the same type," - f" but got {base_type} and {model_type}." - ) - models.append(model) - - # Merge models into base model - for m in models: - merge_models(base_model, m, merge_conf) - - # Save base model - mlx_path = Path(mlx_path) - weights = dict(tree_flatten(base_model.parameters())) - del models, base_model - save_weights(mlx_path, weights, donate_weights=True) - py_files = glob.glob(str(base_path / "*.py")) - for file in py_files: - shutil.copy(file, mlx_path) - - tokenizer.save_pretrained(mlx_path) - - save_config(config, config_path=mlx_path / "config.json") - - if upload_repo is not None: - upload_to_hub(mlx_path, upload_repo, base_hf_path) - - -def main(): - parser = configure_parser() - args = parser.parse_args() - merge(**vars(args)) - - -if __name__ == "__main__": - main() diff --git a/llms/mlx_lm/models/__init__.py b/llms/mlx_lm/models/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/llms/mlx_lm/models/base.py b/llms/mlx_lm/models/base.py deleted file mode 100644 index 8b40effb..00000000 --- a/llms/mlx_lm/models/base.py +++ /dev/null @@ -1,120 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -import inspect -from dataclasses import dataclass -from typing import Any, Optional - -import mlx.core as mx -from mlx.utils import tree_map - -from .cache import QuantizedKVCache - - -@dataclass -class BaseModelArgs: - @classmethod - def from_dict(cls, params): - return cls( - **{ - k: v - for k, v in params.items() - if k in inspect.signature(cls).parameters - } - ) - - -def create_causal_mask( - N: int, - offset: int = 0, - window_size: Optional[int] = None, - lengths: Optional[mx.array] = None, -): - rinds = mx.arange(offset + N) - linds = mx.arange(offset, offset + N) if offset else rinds - linds = linds[:, None] - rinds = rinds[None] - mask = linds >= rinds - if window_size is not None: - mask = mask & (linds <= rinds + window_size) - if lengths is not None: - lengths = lengths[:, None, None, None] - mask = mask & (rinds < lengths) - return mask - - -def create_attention_mask(h: mx.array, cache: Optional[Any] = None): - T = h.shape[1] - if T > 1: - window_size = None - offset = 0 - if cache is not None and cache[0] is not None: - c = cache[0] - if hasattr(c, "max_size"): - offset = min(c.max_size, c.offset) - window_size = c.max_size - else: - offset = c.offset - mask = create_causal_mask(T, offset, window_size=window_size) - else: - mask = None - return mask - - -def quantized_scaled_dot_product_attention( - queries: mx.array, - q_keys: tuple[mx.array, mx.array, mx.array], - q_values: tuple[mx.array, mx.array, mx.array], - scale: float, - mask: Optional[mx.array], - group_size: int = 64, - bits: int = 8, -) -> mx.array: - B, n_q_heads, L, D = queries.shape - n_kv_heads = q_keys[0].shape[-3] - n_repeats = n_q_heads // n_kv_heads - - queries *= scale - - if n_repeats > 1: - queries = mx.reshape(queries, (B, n_kv_heads, n_repeats, L, D)) - q_keys = tree_map(lambda x: mx.expand_dims(x, axis=-3), q_keys) - q_values = tree_map(lambda x: mx.expand_dims(x, axis=-3), q_values) - - scores = mx.quantized_matmul( - queries, *q_keys, transpose=True, group_size=group_size, bits=bits - ) - if mask is not None: - scores += mask - scores = mx.softmax(scores, axis=-1, precise=True) - out = mx.quantized_matmul( - scores, *q_values, transpose=False, group_size=group_size, bits=bits - ) - - if n_repeats > 1: - out = mx.reshape(out, (B, n_q_heads, L, D)) - - return out - - -def scaled_dot_product_attention( - queries, - keys, - values, - cache, - scale: float, - mask: Optional[mx.array], -) -> mx.array: - if isinstance(cache, QuantizedKVCache): - return quantized_scaled_dot_product_attention( - queries, - keys, - values, - scale=scale, - mask=mask, - group_size=cache.group_size, - bits=cache.bits, - ) - else: - return mx.fast.scaled_dot_product_attention( - queries, keys, values, scale=scale, mask=mask - ) diff --git a/llms/mlx_lm/models/cache.py b/llms/mlx_lm/models/cache.py deleted file mode 100644 index 14026f0c..00000000 --- a/llms/mlx_lm/models/cache.py +++ /dev/null @@ -1,438 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -from typing import Any, Dict, List, Optional - -import mlx.core as mx -import mlx.nn as nn -from mlx.utils import tree_flatten, tree_map, tree_unflatten - - -def make_prompt_cache( - model: nn.Module, - max_kv_size: Optional[int] = None, -) -> List[Any]: - """ - Construct the model's cache for use when cgeneration. - - This function will defer the cache construction to the model if it has a - ``make_cache`` method, otherwise it will make a default KV cache. - - Args: - model (nn.Module): The language model. - max_kv_size (Optional[int]): If provided and the model does not have a - ``make_cache`` method, a ``RotatingKVCache`` is used with a maximum - size of ``max_kv_size`` - """ - if hasattr(model, "make_cache"): - return model.make_cache() - - num_layers = len(model.layers) - if max_kv_size is not None: - return [ - RotatingKVCache(max_size=max_kv_size, keep=4) for _ in range(num_layers) - ] - else: - return [KVCache() for _ in range(num_layers)] - - -def save_prompt_cache(file_name: str, cache: List[Any], metadata: Dict[str, str] = {}): - """ - Save a pre-computed prompt cache to a file. - - Args: - file_name (str): The ``.safetensors`` file name. - cache (List[Any]): The model state. - metadata (Dict[str, str]): Optional metadata to save along with model - state. - """ - cache_data = [c.state for c in cache] - cache_info = [c.meta_state for c in cache] - cache_data = dict(tree_flatten(cache_data)) - cache_classes = [type(c).__name__ for c in cache] - cache_metadata = [cache_info, metadata, cache_classes] - cache_metadata = dict(tree_flatten(cache_metadata)) - mx.save_safetensors(file_name, cache_data, cache_metadata) - - -def load_prompt_cache(file_name, return_metadata=False): - """ - Load a prompt cache from a file. - - Args: - file_name (str): The ``.safetensors`` file name. - return_metadata (bool): Whether or not to return metadata. - Default: ``False``. - - Returns: - List[Any] or Tuple[List[Any], Dict[str, str]]: The prompt cache and - the metadata if requested. - """ - arrays, cache_metadata = mx.load(file_name, return_metadata=True) - arrays = tree_unflatten(list(arrays.items())) - cache_metadata = tree_unflatten(list(cache_metadata.items())) - info, metadata, classes = cache_metadata - cache = [globals()[c]() for c in classes] - for c, state, meta_state in zip(cache, arrays, info): - c.state = state - c.meta_state = meta_state - if return_metadata: - return cache, metadata - return cache - - -def can_trim_prompt_cache(cache: List[Any]) -> bool: - """ - Check if model's cache can be trimmed. - """ - return all(c.is_trimmable() for c in cache) - - -def trim_prompt_cache(cache: List[Any], num_tokens: int) -> List[Any]: - """ - Trim the model's cache by the given number of tokens. - - This function will trim the cache if possible (in-place) and return the - number of tokens that were trimmed. - - Args: - cache (List[Any]): The model's cache. - num_tokens (int): The number of tokens to trim. - - Returns: - (int): The number of tokens that were trimmed. - """ - if not can_trim_prompt_cache(cache) or len(cache) == 0: - return 0 - return [c.trim(num_tokens) for c in cache][0] - - -class _BaseCache: - @property - def state(self): - return [] - - @state.setter - def state(self, v): - if v is not None and v: - raise ValueError("This cache has no state but a state was set.") - - @property - def meta_state(self): - return "" - - @meta_state.setter - def meta_state(self, v): - if v is not None and v: - raise ValueError("This cache has no meta_state but a meta_state was set.") - - def is_trimmable(self): - return False - - -class QuantizedKVCache(_BaseCache): - def __init__(self, group_size: int = 64, bits: int = 8): - self.keys = None - self.values = None - self.offset = 0 - self.step = 256 - self.group_size = group_size - self.bits = bits - - def update_and_fetch(self, keys, values): - B, n_kv_heads, num_steps, k_head_dim = keys.shape - v_head_dim = values.shape[-1] - prev = self.offset - - if self.keys is None or (prev + num_steps) > self.keys[0].shape[-2]: - el_per_int = 8 * mx.uint32.size // self.bits - new_steps = (self.step + num_steps - 1) // self.step * self.step - shape = (B, n_kv_heads, new_steps) - - def init_quant(dim): - return ( - mx.zeros((*shape, dim // el_per_int), dtype=mx.uint32), - mx.zeros((*shape, dim // self.group_size), dtype=keys.dtype), - mx.zeros((*shape, dim // self.group_size), dtype=keys.dtype), - ) - - def expand_quant(x): - new_x = mx.zeros((*shape, x.shape[-1]), dtype=x.dtype) - return mx.concatenate([x, new_x], axis=-2) - - if self.keys is not None: - if prev % self.step != 0: - self.keys, self.values = tree_map( - lambda x: x[..., :prev, :], (self.keys, self.values) - ) - - self.keys, self.values = tree_map( - expand_quant, (self.keys, self.values) - ) - else: - self.keys, self.values = init_quant(k_head_dim), init_quant(v_head_dim) - - self.offset += num_steps - - keys = mx.quantize(keys, group_size=self.group_size, bits=self.bits) - values = mx.quantize(values, group_size=self.group_size, bits=self.bits) - for i in range(len(self.keys)): - self.keys[i][..., prev : self.offset, :] = keys[i] - self.values[i][..., prev : self.offset, :] = values[i] - - return tree_map(lambda x: x[..., : self.offset, :], (self.keys, self.values)) - - @property - def state(self): - if self.offset == self.keys[0].shape[2]: - return self.keys, self.values - else: - return tree_map( - lambda x: x[..., : self.offset, :], (self.keys, self.values) - ) - - @state.setter - def state(self, v): - self.keys, self.values = v - - @property - def meta_state(self): - return tuple(map(str, (self.step, self.offset, self.group_size, self.bits))) - - @meta_state.setter - def meta_state(self, v): - self.step, self.offset, self.group_size, self.bits = map(int, v) - - def is_trimmable(self): - return True - - def trim(self, n): - n = min(self.offset, n) - self.offset -= n - return n - - -class KVCache(_BaseCache): - def __init__(self): - self.keys = None - self.values = None - self.offset = 0 - self.step = 256 - - def update_and_fetch(self, keys, values): - prev = self.offset - if self.keys is None or (prev + keys.shape[2]) > self.keys.shape[2]: - B, n_kv_heads, _, k_head_dim = keys.shape - v_head_dim = values.shape[3] - n_steps = (self.step + keys.shape[2] - 1) // self.step - k_shape = (B, n_kv_heads, n_steps * self.step, k_head_dim) - v_shape = (B, n_kv_heads, n_steps * self.step, v_head_dim) - new_k = mx.zeros(k_shape, keys.dtype) - new_v = mx.zeros(v_shape, values.dtype) - if self.keys is not None: - if prev % self.step != 0: - self.keys = self.keys[..., :prev, :] - self.values = self.values[..., :prev, :] - self.keys = mx.concatenate([self.keys, new_k], axis=2) - self.values = mx.concatenate([self.values, new_v], axis=2) - else: - self.keys, self.values = new_k, new_v - - self.offset += keys.shape[2] - self.keys[..., prev : self.offset, :] = keys - self.values[..., prev : self.offset, :] = values - return self.keys[..., : self.offset, :], self.values[..., : self.offset, :] - - @property - def state(self): - if self.offset == self.keys.shape[2]: - return self.keys, self.values - else: - return ( - self.keys[..., : self.offset, :], - self.values[..., : self.offset, :], - ) - - @state.setter - def state(self, v): - self.keys, self.values = v - self.offset = self.keys.shape[2] - - def is_trimmable(self): - return True - - def trim(self, n): - n = min(self.offset, n) - self.offset -= n - return n - - def to_quantized(self, group_size: int = 64, bits: int = 4) -> QuantizedKVCache: - quant_cache = QuantizedKVCache(group_size=group_size, bits=bits) - quant_cache.offset = self.offset - if self.keys is not None: - quant_cache.keys = mx.quantize(self.keys, group_size=group_size, bits=bits) - quant_cache.values = mx.quantize( - self.values, group_size=group_size, bits=bits - ) - return quant_cache - - -class RotatingKVCache(_BaseCache): - - def __init__(self, max_size=None, keep=0, step=256): - self.keep = keep - self.keys = None - self.values = None - self.offset = 0 - self.max_size = max_size - self.step = step - self._idx = 0 - - def _trim(self, trim_size, v, append=None): - to_cat = [] - if trim_size > 0: - to_cat = [v[..., : self.keep, :], v[..., trim_size + self.keep :, :]] - else: - to_cat = [v] - if append is not None: - to_cat.append(append) - return mx.concatenate(to_cat, axis=2) - - def _temporal_order(self, v): - """ - Rearrange the cache into temporal order, slicing off the end if unused. - """ - if self._idx == v.shape[2]: - return v - elif self._idx < self.offset: - return mx.concatenate( - [ - v[..., : self.keep, :], - v[..., self._idx :, :], - v[..., self.keep : self._idx, :], - ], - axis=2, - ) - else: - return v[..., : self._idx, :] - - def _update_concat(self, keys, values): - if self.keys is None: - self.keys = keys - self.values = values - else: - # Put the keys/values in temporal order to - # preserve context - self.keys = self._temporal_order(self.keys) - self.values = self._temporal_order(self.values) - - # The largest size is self.max_size + S to ensure - # every token gets at least self.max_size context - trim_size = self._idx - self.max_size - self.keys = self._trim(trim_size, self.keys, keys) - self.values = self._trim(trim_size, self.values, values) - self.offset += keys.shape[2] - self._idx = self.keys.shape[2] - return self.keys, self.values - - def _update_in_place(self, keys, values): - # May not have hit the max size yet, so potentially - # keep growing the cache - B, n_kv_heads, S, k_head_dim = keys.shape - prev = self.offset - if self.keys is None or ( - prev >= self.keys.shape[2] and self.keys.shape[2] < self.max_size - ): - v_head_dim = values.shape[3] - new_size = min(self.step, self.max_size - prev) - k_shape = (B, n_kv_heads, new_size, k_head_dim) - v_shape = (B, n_kv_heads, new_size, v_head_dim) - new_k = mx.zeros(k_shape, keys.dtype) - new_v = mx.zeros(v_shape, values.dtype) - if self.keys is not None: - self.keys = mx.concatenate([self.keys, new_k], axis=2) - self.values = mx.concatenate([self.values, new_v], axis=2) - else: - self.keys, self.values = new_k, new_v - self._idx = prev - - # Trim if needed - trim_size = self.keys.shape[2] - self.max_size - if trim_size > 0: - self.keys = self._trim(trim_size, self.keys) - self.values = self._trim(trim_size, self.values) - self._idx = self.max_size - - # Rotate - if self._idx == self.max_size: - self._idx = self.keep - - # Assign - self.keys[..., self._idx : self._idx + S, :] = keys - self.values[..., self._idx : self._idx + S, :] = values - self.offset += S - self._idx += S - - # If the buffer is not full, slice off the end - if self.offset < self.max_size: - return self.keys[..., : self.offset, :], self.values[..., : self.offset, :] - return self.keys, self.values - - def update_and_fetch(self, keys, values): - if keys.shape[2] == 1: - return self._update_in_place(keys, values) - return self._update_concat(keys, values) - - @property - def state(self): - if self.offset < self.keys.shape[2]: - return self.keys[..., : self.offset, :], self.values[..., : self.offset, :] - else: - return self.keys, self.values - - @state.setter - def state(self, v): - self.keys, self.values = v - - @property - def meta_state(self): - return tuple( - map(str, (self.keep, self.max_size, self.step, self.offset, self._idx)) - ) - - @meta_state.setter - def meta_state(self, v): - self.keep, self.max_size, self.step, self.offset, self._idx = map( - int, - v, - ) - - def is_trimmable(self): - return self.offset < self.max_size - - def trim(self, n): - n = min(self.offset, n) - self.offset -= n - self._idx -= n - return n - - def to_quantized(self, group_size: int = 64, bits: int = 4) -> QuantizedKVCache: - raise NotImplementedError("RotatingKVCache Quantization NYI") - - -class MambaCache(_BaseCache): - def __init__(self): - self.cache = [None, None] - - def __setitem__(self, idx, value): - self.cache[idx] = value - - def __getitem__(self, idx): - return self.cache[idx] - - @property - def state(self): - return self.cache - - @state.setter - def state(self, v): - self.cache = v diff --git a/llms/mlx_lm/models/cohere.py b/llms/mlx_lm/models/cohere.py deleted file mode 100644 index b2d16dd7..00000000 --- a/llms/mlx_lm/models/cohere.py +++ /dev/null @@ -1,195 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -from dataclasses import dataclass -from typing import Any, Optional, Tuple - -import mlx.core as mx -import mlx.nn as nn - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str - hidden_size: int = 8192 - num_hidden_layers: int = 40 - intermediate_size: int = 22528 - num_attention_heads: int = 64 - num_key_value_heads: int = 64 - rope_theta: float = 8000000.0 - vocab_size: int = 256000 - layer_norm_eps: float = 1e-05 - logit_scale: float = 0.0625 - attention_bias: bool = False - layer_norm_bias: bool = False - use_qk_norm: bool = False - - -class LayerNorm2D(nn.Module): - - def __init__(self, d1, d2, eps): - super().__init__() - self.weight = mx.zeros((d1, d2)) - self.eps = eps - - def __call__(self, x): - return self.weight * mx.fast.layer_norm(x, None, None, self.eps) - - -class Attention(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - - dim = args.hidden_size - self.n_heads = n_heads = args.num_attention_heads - self.n_kv_heads = n_kv_heads = args.num_key_value_heads - - head_dim = args.hidden_size // args.num_attention_heads - self.scale = head_dim**-0.5 - - attetion_bias = args.attention_bias - - self.q_proj = nn.Linear(dim, n_heads * head_dim, bias=attetion_bias) - self.k_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=attetion_bias) - self.v_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=attetion_bias) - self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=attetion_bias) - - self.use_qk_norm = args.use_qk_norm - if self.use_qk_norm: - self.q_norm = LayerNorm2D(self.n_heads, head_dim, eps=args.layer_norm_eps) - self.k_norm = LayerNorm2D( - self.n_kv_heads, head_dim, eps=args.layer_norm_eps - ) - - self.rope = nn.RoPE(head_dim, traditional=True, base=args.rope_theta) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - B, L, D = x.shape - - queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x) - - queries = queries.reshape(B, L, self.n_heads, -1) - keys = keys.reshape(B, L, self.n_kv_heads, -1) - if self.use_qk_norm: - queries = self.q_norm(queries) - keys = self.k_norm(keys) - - queries = queries.transpose(0, 2, 1, 3) - keys = keys.transpose(0, 2, 1, 3) - values = values.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - - if cache is not None: - queries = self.rope(queries, offset=cache.offset) - keys = self.rope(keys, offset=cache.offset) - keys, values = cache.update_and_fetch(keys, values) - else: - queries = self.rope(queries) - keys = self.rope(keys) - - output = scaled_dot_product_attention( - queries, keys, values, cache=cache, scale=self.scale, mask=mask - ) - - output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) - return self.o_proj(output) - - -class MLP(nn.Module): - def __init__(self, dim, hidden_dim): - super().__init__() - self.gate_proj = nn.Linear(dim, hidden_dim, bias=False) - self.up_proj = nn.Linear(dim, hidden_dim, bias=False) - self.down_proj = nn.Linear(hidden_dim, dim, bias=False) - - def __call__(self, x): - return self.down_proj(nn.silu(self.gate_proj(x)) * self.up_proj(x)) - - -class TransformerBlock(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.hidden_size = args.hidden_size - self.n_heads = args.num_attention_heads - - self.self_attn = Attention(args) - self.mlp = MLP(args.hidden_size, args.intermediate_size) - self.input_layernorm = nn.LayerNorm( - args.hidden_size, eps=args.layer_norm_eps, bias=args.layer_norm_bias - ) - self.args = args - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - h = self.input_layernorm(x) - attn_h = self.self_attn(h, mask, cache) - ff_h = self.mlp(h) - return attn_h + ff_h + x - - -class CohereModel(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.vocab_size = args.vocab_size - self.num_hidden_layers = args.num_hidden_layers - assert self.vocab_size > 0 - self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size) - self.layers = [ - TransformerBlock(args=args) for _ in range(args.num_hidden_layers) - ] - self.norm = nn.LayerNorm( - args.hidden_size, eps=args.layer_norm_eps, bias=args.layer_norm_bias - ) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - h = self.embed_tokens(inputs) - - if mask is None: - mask = create_attention_mask(h, cache) - - if cache is None: - cache = [None] * len(self.layers) - - for layer, c in zip(self.layers, cache): - h = layer(h, mask, c) - - return self.norm(h) - - -class Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.model_type = args.model_type - self.model = CohereModel(args) - self.args = args - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - out = self.model(inputs, mask, cache) - out = self.model.embed_tokens.as_linear(out) - out = out * self.model.args.logit_scale - return out - - @property - def layers(self): - return self.model.layers diff --git a/llms/mlx_lm/models/cohere2.py b/llms/mlx_lm/models/cohere2.py deleted file mode 100644 index 19bfa6b6..00000000 --- a/llms/mlx_lm/models/cohere2.py +++ /dev/null @@ -1,206 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -from dataclasses import dataclass -from typing import Optional, Tuple - -import mlx.core as mx -import mlx.nn as nn - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention -from .cache import KVCache, RotatingKVCache - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str - hidden_size: int = 4096 - head_dim: int = 128 - num_hidden_layers: int = 32 - intermediate_size: int = 14336 - num_attention_heads: int = 32 - num_key_value_heads: int = 8 - rope_theta: float = 50000.0 - vocab_size: int = 256000 - layer_norm_eps: float = 1e-05 - logit_scale: float = 0.0625 - attention_bias: bool = False - layer_norm_bias: bool = False - sliding_window: int = 4096 - sliding_window_pattern: int = 4 - - -class Attention(nn.Module): - def __init__(self, args: ModelArgs, layer_idx: int): - super().__init__() - self.args = args - self.layer_idx = layer_idx - - dim = args.hidden_size - self.n_heads = n_heads = args.num_attention_heads - self.n_kv_heads = n_kv_heads = args.num_key_value_heads - self.head_dim = head_dim = args.head_dim - if (head_dim * n_heads) != dim: - raise ValueError( - f"hidden_size must be divisible by num_heads (got `hidden_size`: {dim}" - f" and `num_heads`: {n_heads})." - ) - self.scale = head_dim**-0.5 - - attetion_bias = args.attention_bias - - self.q_proj = nn.Linear(dim, n_heads * head_dim, bias=attetion_bias) - self.k_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=attetion_bias) - self.v_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=attetion_bias) - self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=attetion_bias) - - self.rope = nn.RoPE(head_dim, traditional=True, base=args.rope_theta) - - self.use_sliding_window = (layer_idx + 1) % args.sliding_window_pattern != 0 - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Tuple[mx.array, mx.array]] = None, - ) -> mx.array: - B, L, D = x.shape - - queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x) - - queries = queries.reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) - keys = keys.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - values = values.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - - # Apply RoPE only if sliding window is enabled - if self.use_sliding_window: - if cache is None: - queries = self.rope(queries) - keys = self.rope(keys) - else: - queries = self.rope(queries, offset=cache.offset) - keys = self.rope(keys, offset=cache.offset) - - if cache is not None: - keys, values = cache.update_and_fetch(keys, values) - - if self.use_sliding_window and mask is not None: - key_len = keys.shape[-2] - if mask.shape[-1] != key_len: - mask = mask[..., -key_len:] - - output = scaled_dot_product_attention( - queries, keys, values, cache=cache, scale=self.scale, mask=mask - ) - - output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) - return self.o_proj(output) - - -class MLP(nn.Module): - def __init__(self, dim, hidden_dim): - super().__init__() - self.gate_proj = nn.Linear(dim, hidden_dim, bias=False) - self.up_proj = nn.Linear(dim, hidden_dim, bias=False) - self.down_proj = nn.Linear(hidden_dim, dim, bias=False) - - def __call__(self, x): - return self.down_proj(nn.silu(self.gate_proj(x)) * self.up_proj(x)) - - -class TransformerBlock(nn.Module): - def __init__(self, args: ModelArgs, layer_idx: int): - super().__init__() - self.hidden_size = args.hidden_size - self.n_heads = args.num_attention_heads - - self.self_attn = Attention(args, layer_idx) - self.mlp = MLP(args.hidden_size, args.intermediate_size) - self.input_layernorm = nn.LayerNorm( - args.hidden_size, eps=args.layer_norm_eps, bias=args.layer_norm_bias - ) - self.args = args - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Tuple[mx.array, mx.array]] = None, - ) -> mx.array: - h = self.input_layernorm(x) - attn_h = self.self_attn(h, mask, cache) - ff_h = self.mlp(h) - return attn_h + ff_h + x - - -class CohereModel(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.vocab_size = args.vocab_size - self.num_hidden_layers = args.num_hidden_layers - assert self.vocab_size > 0 - self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size) - self.layers = [ - TransformerBlock(args=args, layer_idx=i) - for i in range(args.num_hidden_layers) - ] - self.norm = nn.LayerNorm( - args.hidden_size, eps=args.layer_norm_eps, bias=args.layer_norm_bias - ) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - h = self.embed_tokens(inputs) - - if cache is None: - cache = [None] * len(self.layers) - - if mask is None: - j = self.args.sliding_window_pattern - mask = create_attention_mask(h, cache[j - 1 : j]) - - for layer, c in zip(self.layers, cache): - h = layer(h, mask, c) - - return self.norm(h) - - -class Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.model_type = args.model_type - self.model = CohereModel(args) - self.args = args - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - out = self.model(inputs, mask, cache) - out = self.model.embed_tokens.as_linear(out) - out = out * self.model.args.logit_scale - return out - - def make_cache(self): - caches = [] - for i in range(self.args.num_hidden_layers): - if ( - i % self.args.sliding_window_pattern - == self.args.sliding_window_pattern - 1 - ): - caches.append(KVCache()) - else: - caches.append( - RotatingKVCache(max_size=self.args.sliding_window, keep=0) - ) - return caches - - @property - def layers(self): - return self.model.layers diff --git a/llms/mlx_lm/models/dbrx.py b/llms/mlx_lm/models/dbrx.py deleted file mode 100644 index 886b5630..00000000 --- a/llms/mlx_lm/models/dbrx.py +++ /dev/null @@ -1,254 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -from dataclasses import dataclass -from typing import Any, Optional, Tuple - -import mlx.core as mx -import mlx.nn as nn -import numpy as np - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str - vocab_size: int - d_model: int - ffn_config: dict - attn_config: dict - n_layers: int - n_heads: int - - -class Attention(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.num_heads = args.n_heads - self.d_model = args.d_model - self.head_dim = args.d_model // args.n_heads - self.num_key_value_heads = args.attn_config["kv_n_heads"] - self.clip_qkv = args.attn_config["clip_qkv"] - self.rope_theta = args.attn_config["rope_theta"] - - self.scale = self.head_dim**-0.5 - - self.Wqkv = nn.Linear( - args.d_model, - (self.num_key_value_heads * 2 + self.num_heads) * self.head_dim, - bias=False, - ) - self.out_proj = nn.Linear(args.d_model, args.d_model, bias=False) - self.rope = nn.RoPE( - self.head_dim, - traditional=False, - base=self.rope_theta, - ) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - - qkv = self.Wqkv(x) - qkv = mx.clip(qkv, a_min=-self.clip_qkv, a_max=self.clip_qkv) - splits = [self.d_model, self.d_model + self.head_dim * self.num_key_value_heads] - queries, keys, values = mx.split(qkv, splits, axis=-1) - - B, L, D = x.shape - - # Prepare the queries, keys and values for the attention computation - queries = queries.reshape(B, L, self.num_heads, -1).transpose(0, 2, 1, 3) - keys = keys.reshape(B, L, self.num_key_value_heads, -1).transpose(0, 2, 1, 3) - values = values.reshape(B, L, self.num_key_value_heads, -1).transpose( - 0, 2, 1, 3 - ) - - if cache is not None: - queries = self.rope(queries, offset=cache.offset) - keys = self.rope(keys, offset=cache.offset) - keys, values = cache.update_and_fetch(keys, values) - else: - queries = self.rope(queries) - keys = self.rope(keys) - - output = scaled_dot_product_attention( - queries, keys, values, cache=cache, scale=self.scale, mask=mask - ) - output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) - return self.out_proj(output) - - -class NormAttnNorm(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.norm_1 = nn.LayerNorm(args.d_model, bias=False) - self.norm_2 = nn.LayerNorm(args.d_model, bias=False) - self.attn = Attention(args) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - h = self.attn(self.norm_1(x), mask=mask, cache=cache) - x = h + x - return x, self.norm_2(x) - - -class MLP(nn.Module): - def __init__(self, d_model: int, ffn_dim: int): - super().__init__() - self.v1 = nn.Linear(d_model, ffn_dim, bias=False) - self.w1 = nn.Linear(d_model, ffn_dim, bias=False) - self.w2 = nn.Linear(ffn_dim, d_model, bias=False) - self.act_fn = nn.silu - - def __call__(self, x: mx.array) -> mx.array: - current_hidden_states = self.act_fn(self.w1(x)) * self.v1(x) - current_hidden_states = self.w2(current_hidden_states) - return current_hidden_states - - -class Router(nn.Module): - def __init__(self, d_model: int, num_experts: int): - super().__init__() - self.layer = nn.Linear(d_model, num_experts, bias=False) - - def __call__(self, x: mx.array): - return self.layer(x) - - -class SparseMoeBlock(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.d_model = args.d_model - self.ffn_dim = args.ffn_config["ffn_hidden_size"] - self.num_experts = args.ffn_config["moe_num_experts"] - self.num_experts_per_tok = args.ffn_config["moe_top_k"] - - self.router = Router(self.d_model, self.num_experts) - self.experts = [ - MLP(self.d_model, self.ffn_dim) for _ in range(self.num_experts) - ] - - def __call__(self, x: mx.array) -> mx.array: - ne = self.num_experts_per_tok - orig_shape = x.shape - x = x.reshape(-1, x.shape[-1]) - - gates = self.router(x) - gates = mx.softmax(gates.astype(mx.float32), axis=-1) - - inds = mx.stop_gradient(mx.argpartition(-gates, kth=ne - 1, axis=-1)[:, :ne]) - scores = mx.take_along_axis(gates, inds, axis=-1) - scores = scores / mx.linalg.norm(scores, ord=1, axis=-1, keepdims=True) - scores = scores.astype(x.dtype) - - if self.training: - inds = np.array(inds) - y = mx.zeros((x.shape[0], ne, x.shape[-1]), x.dtype) - for e, expert in enumerate(self.experts): - idx1, idx2 = map(mx.array, np.where(inds == e)) - if idx1.size == 0: - continue - y[idx1, idx2] = expert(x[idx1]) - - y = (y * scores[:, :, None]).sum(axis=1) - else: - y = [] - for xt, st, it in zip(x, scores, inds.tolist()): - yt = mx.stack([self.experts[e](xt) for e in it], axis=-1) - yt = (yt * st).sum(axis=-1) - y.append(yt) - y = mx.stack(y, axis=0) - - return y.reshape(orig_shape) - - -class DecoderLayer(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.ffn = SparseMoeBlock(args) - self.norm_attn_norm = NormAttnNorm(args) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - r, h = self.norm_attn_norm(x, mask, cache) - out = self.ffn(h) + r - return out - - -class DBRX(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.vocab_size = args.vocab_size - self.wte = nn.Embedding(args.vocab_size, args.d_model) - self.blocks = [DecoderLayer(args=args) for _ in range(args.n_layers)] - self.norm_f = nn.LayerNorm(args.d_model, bias=False) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - h = self.wte(inputs) - - if mask is None: - mask = create_attention_mask(h, cache) - - if cache is None: - cache = [None] * len(self.blocks) - - for layer, c in zip(self.blocks, cache): - h = layer(h, mask, c) - - return self.norm_f(h) - - -class Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.model_type = args.model_type - self.transformer = DBRX(args) - self.lm_head = nn.Linear(args.d_model, args.vocab_size, bias=False) - self.args = args - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - out = self.transformer(inputs, mask, cache) - return self.lm_head(out) - - @property - def layers(self): - return self.transformer.blocks - - def sanitize(self, weights): - # Split experts into sub matrices - num_experts = self.args.ffn_config["moe_num_experts"] - dim = self.args.ffn_config["ffn_hidden_size"] - - pattern = "experts.mlp" - new_weights = {k: v for k, v in weights.items() if pattern not in k} - for k, v in weights.items(): - if pattern in k: - experts = [ - (k.replace(".mlp", f".{e}") + ".weight", sv) - for e, sv in enumerate(mx.split(v, num_experts, axis=0)) - ] - if k.endswith("w2"): - experts = [(s, sv.T) for s, sv in experts] - new_weights.update(experts) - return new_weights diff --git a/llms/mlx_lm/models/deepseek.py b/llms/mlx_lm/models/deepseek.py deleted file mode 100644 index ffc30c36..00000000 --- a/llms/mlx_lm/models/deepseek.py +++ /dev/null @@ -1,261 +0,0 @@ -from dataclasses import dataclass -from typing import Any, Dict, Optional - -import mlx.core as mx -import mlx.nn as nn - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention -from .switch_layers import SwitchGLU - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str = "deepseek" - vocab_size: int = 102400 - hidden_size: int = 4096 - intermediate_size: int = 11008 - moe_intermediate_size: int = 1407 - num_hidden_layers: int = 30 - num_attention_heads: int = 32 - num_key_value_heads: int = 32 - n_shared_experts: Optional[int] = None - n_routed_experts: Optional[int] = None - num_experts_per_tok: Optional[int] = None - moe_layer_freq: int = 1 - first_k_dense_replace: int = 0 - max_position_embeddings: int = 2048 - rms_norm_eps: float = 1e-6 - rope_theta: float = 10000.0 - rope_scaling: Optional[Dict] = None - attention_bias: bool = False - - -class DeepseekAttention(nn.Module): - def __init__(self, config: ModelArgs): - super().__init__() - self.config = config - self.hidden_size = config.hidden_size - self.num_attention_heads = config.num_attention_heads - self.num_kv_heads = config.num_key_value_heads - self.head_dim = config.hidden_size // config.num_attention_heads - self.scale = self.head_dim**-0.5 - - attention_bias = getattr(config, "attention_bias", False) - - self.q_proj = nn.Linear( - self.hidden_size, - config.num_attention_heads * self.head_dim, - bias=attention_bias, - ) - self.k_proj = nn.Linear( - self.hidden_size, - config.num_key_value_heads * self.head_dim, - bias=attention_bias, - ) - self.v_proj = nn.Linear( - self.hidden_size, - config.num_key_value_heads * self.head_dim, - bias=attention_bias, - ) - self.o_proj = nn.Linear( - self.hidden_size, - config.num_attention_heads * self.head_dim, - bias=attention_bias, - ) - - rope_scale = 1.0 - if config.rope_scaling and config.rope_scaling["type"] == "linear": - assert isinstance(config.rope_scaling["factor"], float) - rope_scale = 1 / config.rope_scaling["factor"] - self.rope = nn.RoPE( - self.head_dim, - base=config.rope_theta, - scale=rope_scale, - ) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - B, L, _ = x.shape - - queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x) - - queries = queries.reshape(B, L, self.num_attention_heads, -1).transpose( - 0, 2, 1, 3 - ) - keys = keys.reshape(B, L, self.num_kv_heads, -1).transpose(0, 2, 1, 3) - values = values.reshape(B, L, self.num_kv_heads, -1).transpose(0, 2, 1, 3) - - if cache is not None: - queries = self.rope(queries, offset=cache.offset) - keys = self.rope(keys, offset=cache.offset) - keys, values = cache.update_and_fetch(keys, values) - else: - queries = self.rope(queries) - keys = self.rope(keys) - - output = scaled_dot_product_attention( - queries, keys, values, cache=cache, scale=self.scale, mask=mask - ) - output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) - return self.o_proj(output) - - -class DeepseekMLP(nn.Module): - def __init__( - self, - config: ModelArgs, - hidden_size: Optional[int] = None, - intermediate_size: Optional[int] = None, - ): - super().__init__() - self.config = config - self.hidden_size = hidden_size or config.hidden_size - self.intermediate_size = intermediate_size or config.intermediate_size - self.gate_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False) - self.up_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False) - self.down_proj = nn.Linear(self.intermediate_size, self.hidden_size, bias=False) - self.act_fn = nn.silu - - def __call__(self, x: mx.array) -> mx.array: - return self.down_proj(self.act_fn(self.gate_proj(x)) * self.up_proj(x)) - - -class MoEGate(nn.Module): - def __init__(self, config: ModelArgs): - super().__init__() - self.config = config - self.top_k = config.num_experts_per_tok - self.n_routed_experts = config.n_routed_experts - self.weight = mx.zeros((self.n_routed_experts, config.hidden_size)) - - def __call__(self, x): - gates = x @ self.weight.T - scores = mx.softmax(gates, axis=-1, precise=True) - k = self.top_k - inds = mx.stop_gradient(mx.argpartition(-scores, kth=k - 1, axis=-1)[..., :k]) - scores = mx.take_along_axis(scores, inds, axis=-1) - return inds, scores - - -class DeepseekMoE(nn.Module): - def __init__(self, config: ModelArgs): - super().__init__() - self.config = config - self.switch_mlp = SwitchGLU( - config.hidden_size, config.moe_intermediate_size, config.n_routed_experts - ) - - self.gate = MoEGate(config) - if config.n_shared_experts is not None: - intermediate_size = config.moe_intermediate_size * config.n_shared_experts - self.shared_experts = DeepseekMLP( - config=config, intermediate_size=intermediate_size - ) - - def __call__(self, x): - inds, scores = self.gate(x) - y = self.switch_mlp(x, inds) - y = (y * scores[..., None]).sum(axis=-2) - if self.config.n_shared_experts is not None: - y = y + self.shared_experts(x) - - return y - - -class DeepseekDecoderLayer(nn.Module): - def __init__(self, config: ModelArgs, layer_idx: int): - super().__init__() - self.self_attn = DeepseekAttention(config) - self.mlp = ( - DeepseekMoE(config) - if ( - config.n_routed_experts is not None - and layer_idx >= config.first_k_dense_replace - and layer_idx % config.moe_layer_freq == 0 - ) - else DeepseekMLP(config) - ) - self.input_layernorm = nn.RMSNorm(config.hidden_size, eps=config.rms_norm_eps) - self.post_attention_layernorm = nn.RMSNorm( - config.hidden_size, eps=config.rms_norm_eps - ) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - r = self.self_attn(self.input_layernorm(x), mask, cache) - h = x + r - r = self.mlp(self.post_attention_layernorm(h)) - out = h + r - return out - - -class DeepseekModel(nn.Module): - def __init__(self, config: ModelArgs): - super().__init__() - self.config = config - self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size) - self.layers = [ - DeepseekDecoderLayer(config, idx) for idx in range(config.num_hidden_layers) - ] - self.norm = nn.RMSNorm(config.hidden_size, eps=config.rms_norm_eps) - - def __call__( - self, - x: mx.array, - cache: Optional[Any] = None, - mask: Optional[mx.array] = None, - ) -> mx.array: - h = self.embed_tokens(x) - if mask is None: - mask = create_attention_mask(h, cache) - - if cache is None: - cache = [None] * len(self.layers) - - for layer, c in zip(self.layers, cache): - h = layer(h, mask, c) - - return self.norm(h) - - -class Model(nn.Module): - def __init__(self, config: ModelArgs): - super().__init__() - self.args = config - self.model_type = config.model_type - self.model = DeepseekModel(config) - self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False) - - def __call__( - self, - inputs: mx.array, - cache: Optional[Any] = None, - mask: Optional[mx.array] = None, - ): - out = self.model(inputs, cache, mask) - return self.lm_head(out) - - def sanitize(self, weights): - for l in range(self.args.num_hidden_layers): - prefix = f"model.layers.{l}" - for m in ["gate_proj", "down_proj", "up_proj"]: - for k in ["weight", "scales", "biases"]: - if f"{prefix}.mlp.experts.0.{m}.{k}" in weights: - to_join = [ - weights.pop(f"{prefix}.mlp.experts.{e}.{m}.{k}") - for e in range(self.args.n_routed_experts) - ] - weights[f"{prefix}.mlp.switch_mlp.{m}.{k}"] = mx.stack(to_join) - return weights - - @property - def layers(self): - return self.model.layers diff --git a/llms/mlx_lm/models/deepseek_v2.py b/llms/mlx_lm/models/deepseek_v2.py deleted file mode 100644 index 7a5bdeb1..00000000 --- a/llms/mlx_lm/models/deepseek_v2.py +++ /dev/null @@ -1,462 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -import math -from dataclasses import dataclass -from typing import Any, Dict, Optional, Tuple - -import mlx.core as mx -import mlx.nn as nn - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention -from .switch_layers import SwitchGLU - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str = "deepseek_v2" - vocab_size: int = 102400 - hidden_size: int = 4096 - intermediate_size: int = 11008 - moe_intermediate_size: int = 1407 - num_hidden_layers: int = 30 - num_attention_heads: int = 32 - num_key_value_heads: int = 32 - n_shared_experts: Optional[int] = None - n_routed_experts: Optional[int] = None - routed_scaling_factor: float = 1.0 - kv_lora_rank: int = 512 - q_lora_rank: int = 1536 - qk_rope_head_dim: int = 64 - v_head_dim: int = 128 - qk_nope_head_dim: int = 128 - topk_method: str = "gready" - n_group: Optional[int] = None - topk_group: Optional[int] = None - num_experts_per_tok: Optional[int] = None - moe_layer_freq: int = 1 - first_k_dense_replace: int = 0 - max_position_embeddings: int = 2048 - rms_norm_eps: float = 1e-6 - rope_theta: float = 10000.0 - rope_scaling: Dict = None - attention_bias: bool = False - - -def yarn_find_correction_dim( - num_rotations, dim, base=10000, max_position_embeddings=2048 -): - return (dim * math.log(max_position_embeddings / (num_rotations * 2 * math.pi))) / ( - 2 * math.log(base) - ) - - -def yarn_find_correction_range( - low_rot, high_rot, dim, base=10000, max_position_embeddings=2048 -): - low = math.floor( - yarn_find_correction_dim(low_rot, dim, base, max_position_embeddings) - ) - high = math.ceil( - yarn_find_correction_dim(high_rot, dim, base, max_position_embeddings) - ) - return max(low, 0), min(high, dim - 1) - - -def yarn_get_mscale(scale=1, mscale=1): - if scale <= 1: - return 1.0 - return 0.1 * mscale * math.log(scale) + 1.0 - - -def yarn_linear_ramp_mask(min_val, max_val, dim): - if min_val == max_val: - max_val += 0.001 # Prevent singularity - - linear_func = (mx.arange(dim, dtype=mx.float32) - min_val) / (max_val - min_val) - return mx.clip(linear_func, 0, 1) - - -class DeepseekV2YarnRotaryEmbedding(nn.Module): - def __init__( - self, - dim, - max_position_embeddings=2048, - base=10000, - scaling_factor=1.0, - original_max_position_embeddings=4096, - beta_fast=32, - beta_slow=1, - mscale=1, - mscale_all_dim=0, - ): - super().__init__() - self.mscale = yarn_get_mscale(scaling_factor, mscale) / yarn_get_mscale( - scaling_factor, mscale_all_dim - ) - freq_extra = base ** (mx.arange(0, dim, 2, dtype=mx.float32) / dim) - freq_inter = scaling_factor * base ** ( - mx.arange(0, dim, 2, dtype=mx.float32) / dim - ) - low, high = yarn_find_correction_range( - beta_fast, - beta_slow, - dim, - base, - original_max_position_embeddings, - ) - freq_mask = 1.0 - yarn_linear_ramp_mask(low, high, dim // 2) - self._freqs = (freq_inter * freq_extra) / ( - freq_inter * freq_mask + freq_extra * (1 - freq_mask) - ) - - def __call__(self, x, offset=0): - if self.mscale != 1.0: - x = self.mscale * x - return mx.fast.rope( - x, - x.shape[-1], - traditional=True, - base=None, - scale=1.0, - offset=offset, - freqs=self._freqs, - ) - - -class DeepseekV2Attention(nn.Module): - def __init__(self, config: ModelArgs): - super().__init__() - self.config = config - self.hidden_size = config.hidden_size - self.num_heads = config.num_attention_heads - self.max_position_embeddings = config.max_position_embeddings - self.rope_theta = config.rope_theta - self.q_lora_rank = config.q_lora_rank - self.qk_rope_head_dim = config.qk_rope_head_dim - self.kv_lora_rank = config.kv_lora_rank - self.v_head_dim = config.v_head_dim - self.qk_nope_head_dim = config.qk_nope_head_dim - self.q_head_dim = config.qk_nope_head_dim + config.qk_rope_head_dim - - self.scale = self.q_head_dim**-0.5 - - if self.q_lora_rank is None: - self.q_proj = nn.Linear( - self.hidden_size, self.num_heads * self.q_head_dim, bias=False - ) - else: - self.q_a_proj = nn.Linear( - self.hidden_size, self.q_lora_rank, bias=config.attention_bias - ) - self.q_a_layernorm = nn.RMSNorm(self.q_lora_rank) - self.q_b_proj = nn.Linear( - self.q_lora_rank, self.num_heads * self.q_head_dim, bias=False - ) - - self.kv_a_proj_with_mqa = nn.Linear( - self.hidden_size, - self.kv_lora_rank + self.qk_rope_head_dim, - bias=config.attention_bias, - ) - self.kv_a_layernorm = nn.RMSNorm(self.kv_lora_rank) - self.kv_b_proj = nn.Linear( - self.kv_lora_rank, - self.num_heads - * (self.q_head_dim - self.qk_rope_head_dim + self.v_head_dim), - bias=False, - ) - - self.o_proj = nn.Linear( - self.num_heads * self.v_head_dim, - self.hidden_size, - bias=config.attention_bias, - ) - - mscale_all_dim = self.config.rope_scaling.get("mscale_all_dim", 0) - scaling_factor = self.config.rope_scaling["factor"] - if mscale_all_dim: - mscale = yarn_get_mscale(scaling_factor, mscale_all_dim) - self.scale = self.scale * mscale * mscale - - rope_kwargs = { - key: self.config.rope_scaling[key] - for key in [ - "original_max_position_embeddings", - "beta_fast", - "beta_slow", - "mscale", - "mscale_all_dim", - ] - if key in self.config.rope_scaling - } - self.rope = DeepseekV2YarnRotaryEmbedding( - dim=self.qk_rope_head_dim, - max_position_embeddings=self.max_position_embeddings, - scaling_factor=scaling_factor, - base=self.rope_theta, - **rope_kwargs, - ) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - B, L, D = x.shape - - if self.q_lora_rank is None: - q = self.q_proj(x) - else: - q = self.q_b_proj(self.q_a_layernorm(self.q_a_proj(x))) - - q = q.reshape(B, L, self.num_heads, self.q_head_dim).transpose(0, 2, 1, 3) - q_nope, q_pe = mx.split(q, [self.qk_nope_head_dim], axis=-1) - compressed_kv = self.kv_a_proj_with_mqa(x) - compressed_kv, k_pe = mx.split(compressed_kv, [self.kv_lora_rank], axis=-1) - k_pe = k_pe.reshape(B, L, 1, self.qk_rope_head_dim).transpose(0, 2, 1, 3) - kv = self.kv_b_proj(self.kv_a_layernorm(compressed_kv)) - kv = kv.reshape(B, L, self.num_heads, -1).transpose(0, 2, 1, 3) - - k_nope, values = mx.split(kv, [self.qk_nope_head_dim], axis=-1) - - if cache is not None: - q_pe = self.rope(q_pe, cache.offset) - k_pe = self.rope(k_pe, cache.offset) - k_pe = mx.repeat(k_pe, self.num_heads, axis=1) - keys, values = cache.update_and_fetch( - mx.concatenate([k_nope, k_pe], axis=-1), values - ) - else: - q_pe = self.rope(q_pe) - k_pe = self.rope(k_pe) - k_pe = mx.repeat(k_pe, self.num_heads, axis=1) - keys = mx.concatenate([k_nope, k_pe], axis=-1) - - queries = mx.concatenate([q_nope, q_pe], axis=-1) - - output = scaled_dot_product_attention( - queries, keys, values, cache=cache, scale=self.scale, mask=mask - ) - output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) - return self.o_proj(output) - - -class DeepseekV2MLP(nn.Module): - def __init__( - self, config: ModelArgs, hidden_size: int = None, intermediate_size: int = None - ): - super().__init__() - self.config = config - self.hidden_size = config.hidden_size if hidden_size is None else hidden_size - self.intermediate_size = ( - config.intermediate_size if intermediate_size is None else intermediate_size - ) - - self.gate_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False) - self.up_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False) - self.down_proj = nn.Linear(self.intermediate_size, self.hidden_size, bias=False) - - def __call__(self, x): - down_proj = self.down_proj(nn.silu(self.gate_proj(x)) * self.up_proj(x)) - return down_proj - - -class MoEGate(nn.Module): - def __init__(self, config: ModelArgs): - super().__init__() - self.config = config - self.top_k = config.num_experts_per_tok - self.n_routed_experts = config.n_routed_experts - self.routed_scaling_factor = config.routed_scaling_factor - self.topk_method = config.topk_method - self.n_group = config.n_group - self.topk_group = config.topk_group - self.weight = mx.zeros((self.n_routed_experts, config.hidden_size)) - - def __call__(self, x): - gates = x @ self.weight.T - - scores = mx.softmax(gates, axis=-1, precise=True) - - if self.topk_method == "group_limited_greedy": - bsz, seq_len = x.shape[:2] - scores = scores.reshape(bsz, seq_len, self.n_group, -1) - group_scores = scores.max(axis=-1, keepdims=True) - k = self.n_group - self.topk_group - group_idx = mx.argpartition(group_scores, kth=k - 1, axis=-2)[..., :k, :] - scores = mx.put_along_axis( - scores, group_idx, mx.array(0.0, scores.dtype), axis=-2 - ) - scores = scores.reshape(bsz, seq_len, -1) - - k = self.top_k - inds = mx.argpartition(-scores, kth=k - 1, axis=-1)[..., :k] - scores = mx.take_along_axis(scores, inds, axis=-1) - scores = scores * self.routed_scaling_factor - - return inds, scores - - -class DeepseekV2MoE(nn.Module): - def __init__(self, config: ModelArgs): - super().__init__() - self.config = config - self.num_experts_per_tok = config.num_experts_per_tok - self.switch_mlp = SwitchGLU( - config.hidden_size, config.moe_intermediate_size, config.n_routed_experts - ) - - self.gate = MoEGate(config) - if config.n_shared_experts is not None: - intermediate_size = config.moe_intermediate_size * config.n_shared_experts - self.shared_experts = DeepseekV2MLP( - config=config, intermediate_size=intermediate_size - ) - - def __call__(self, x): - inds, scores = self.gate(x) - y = self.switch_mlp(x, inds) - y = (y * scores[..., None]).sum(axis=-2) - if self.config.n_shared_experts is not None: - y = y + self.shared_experts(x) - - return y - - -class DeepseekV2DecoderLayer(nn.Module): - def __init__(self, config: ModelArgs, layer_idx: int): - super().__init__() - self.self_attn = DeepseekV2Attention(config) - self.mlp = ( - DeepseekV2MoE(config) - if ( - config.n_routed_experts is not None - and layer_idx >= config.first_k_dense_replace - and layer_idx % config.moe_layer_freq == 0 - ) - else DeepseekV2MLP(config) - ) - self.input_layernorm = nn.RMSNorm(config.hidden_size, eps=config.rms_norm_eps) - self.post_attention_layernorm = nn.RMSNorm( - config.hidden_size, eps=config.rms_norm_eps - ) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - r = self.self_attn(self.input_layernorm(x), mask, cache) - h = x + r - r = self.mlp(self.post_attention_layernorm(h)) - out = h + r - return out - - -class DeepseekV2Model(nn.Module): - def __init__(self, config: ModelArgs): - super().__init__() - self.vocab_size = config.vocab_size - self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size) - self.layers = [ - DeepseekV2DecoderLayer(config, idx) - for idx in range(config.num_hidden_layers) - ] - self.start_idx = 0 - self.end_idx = len(self.layers) - self.num_layers = self.end_idx - - self.norm = nn.RMSNorm(config.hidden_size, eps=config.rms_norm_eps) - - self.pipeline_rank = 0 - self.pipeline_size = 1 - - def pipeline(self, group): - # Split layers in reverse so rank=0 gets the last layers and - # rank=pipeline_size-1 gets the first - self.pipeline_rank = group.rank() - self.pipeline_size = group.size() - layers_per_rank = len(self.layers) // self.pipeline_size - extra = len(self.layers) - layers_per_rank * self.pipeline_size - if self.pipeline_rank < extra: - layers_per_rank += 1 - - self.start_idx = (self.pipeline_size - self.pipeline_rank - 1) * layers_per_rank - self.end_idx = self.start_idx + layers_per_rank - self.num_layers = layers_per_rank - self.layers = self.layers[: self.end_idx] - self.layers[: self.start_idx] = [None] * self.start_idx - self.num_layers = len(self.layers) - self.start_idx - - def __call__( - self, - x: mx.array, - cache: Optional[Any] = None, - mask: Optional[mx.array] = None, - ) -> mx.array: - h = self.embed_tokens(x) - - pipeline_rank = self.pipeline_rank - pipeline_size = self.pipeline_size - # Hack to avoid time-outs during prompt-processing - dist_stream = mx.cpu if h.shape[1] > 1 else mx.gpu - if mask is None: - mask = create_attention_mask(h, cache) - - if cache is None: - cache = [None] * self.num_layers - - # Receive from the previous process in the pipeline - if pipeline_rank < pipeline_size - 1: - h = mx.distributed.recv_like(h, (pipeline_rank + 1), stream=dist_stream) - - for i in range(self.num_layers): - h = self.layers[self.start_idx + i](h, mask, cache[i]) - - # Send to the next process in the pipeline - if pipeline_rank != 0: - h = mx.distributed.send( - h, (pipeline_rank - 1) % pipeline_size, stream=dist_stream - ) - - # Broadcast h while keeping it in the graph - h = mx.distributed.all_gather(h, stream=dist_stream)[: h.shape[0]] - - return self.norm(h) - - -class Model(nn.Module): - def __init__(self, config: ModelArgs): - super().__init__() - self.args = config - self.model_type = config.model_type - self.model = DeepseekV2Model(config) - self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False) - - def __call__( - self, - inputs: mx.array, - cache: Optional[Any] = None, - mask: Optional[mx.array] = None, - ): - out = self.model(inputs, cache, mask) - return self.lm_head(out) - - def sanitize(self, weights): - for l in range(self.args.num_hidden_layers): - prefix = f"model.layers.{l}" - for n, m in [("w1", "gate_proj"), ("w2", "down_proj"), ("w3", "up_proj")]: - for k in ["weight", "scales", "biases"]: - if f"{prefix}.mlp.experts.0.{m}.{k}" in weights: - to_join = [ - weights.pop(f"{prefix}.mlp.experts.{e}.{m}.{k}") - for e in range(self.args.n_routed_experts) - ] - weights[f"{prefix}.mlp.switch_mlp.{m}.{k}"] = mx.stack(to_join) - return weights - - @property - def layers(self): - return self.model.layers[self.model.start_idx : self.model.end_idx] diff --git a/llms/mlx_lm/models/deepseek_v3.py b/llms/mlx_lm/models/deepseek_v3.py deleted file mode 100644 index 5cd40a0d..00000000 --- a/llms/mlx_lm/models/deepseek_v3.py +++ /dev/null @@ -1,506 +0,0 @@ -# Copyright © 2024 Apple Inc. - -import math -from dataclasses import dataclass -from functools import partial -from typing import Any, Dict, Optional, Tuple - -import mlx.core as mx -import mlx.nn as nn - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention -from .switch_layers import SwitchGLU - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str = "deepseek_v3" - vocab_size: int = 102400 - hidden_size: int = 4096 - intermediate_size: int = 11008 - moe_intermediate_size: int = 1407 - num_hidden_layers: int = 30 - num_attention_heads: int = 32 - num_key_value_heads: int = 32 - n_shared_experts: Optional[int] = None - n_routed_experts: Optional[int] = None - routed_scaling_factor: float = 1.0 - kv_lora_rank: int = 512 - q_lora_rank: int = 1536 - qk_rope_head_dim: int = 64 - v_head_dim: int = 128 - qk_nope_head_dim: int = 128 - topk_method: str = "noaux_tc" - scoring_func: str = "sigmoid" - norm_topk_prob: bool = True - n_group: Optional[int] = None - topk_group: Optional[int] = None - num_experts_per_tok: Optional[int] = None - moe_layer_freq: int = 1 - first_k_dense_replace: int = 0 - max_position_embeddings: int = 2048 - rms_norm_eps: float = 1e-6 - rope_theta: float = 10000.0 - rope_scaling: Dict = None - attention_bias: bool = False - - -def yarn_find_correction_dim( - num_rotations, dim, base=10000, max_position_embeddings=2048 -): - return (dim * math.log(max_position_embeddings / (num_rotations * 2 * math.pi))) / ( - 2 * math.log(base) - ) - - -def yarn_find_correction_range( - low_rot, high_rot, dim, base=10000, max_position_embeddings=2048 -): - low = math.floor( - yarn_find_correction_dim(low_rot, dim, base, max_position_embeddings) - ) - high = math.ceil( - yarn_find_correction_dim(high_rot, dim, base, max_position_embeddings) - ) - return max(low, 0), min(high, dim - 1) - - -def yarn_get_mscale(scale=1, mscale=1): - if scale <= 1: - return 1.0 - return 0.1 * mscale * math.log(scale) + 1.0 - - -def yarn_linear_ramp_mask(min_val, max_val, dim): - if min_val == max_val: - max_val += 0.001 # Prevent singularity - - linear_func = (mx.arange(dim, dtype=mx.float32) - min_val) / (max_val - min_val) - return mx.clip(linear_func, 0, 1) - - -class DeepseekV3YarnRotaryEmbedding(nn.Module): - def __init__( - self, - dim, - max_position_embeddings=2048, - base=10000, - scaling_factor=1.0, - original_max_position_embeddings=4096, - beta_fast=32, - beta_slow=1, - mscale=1, - mscale_all_dim=0, - ): - super().__init__() - self.mscale = yarn_get_mscale(scaling_factor, mscale) / yarn_get_mscale( - scaling_factor, mscale_all_dim - ) - freq_extra = base ** (mx.arange(0, dim, 2, dtype=mx.float32) / dim) - freq_inter = scaling_factor * base ** ( - mx.arange(0, dim, 2, dtype=mx.float32) / dim - ) - low, high = yarn_find_correction_range( - beta_fast, - beta_slow, - dim, - base, - original_max_position_embeddings, - ) - freq_mask = 1.0 - yarn_linear_ramp_mask(low, high, dim // 2) - self._freqs = (freq_inter * freq_extra) / ( - freq_inter * freq_mask + freq_extra * (1 - freq_mask) - ) - - def __call__(self, x, offset=0): - if self.mscale != 1.0: - x = self.mscale * x - return mx.fast.rope( - x, - x.shape[-1], - traditional=True, - base=None, - scale=1.0, - offset=offset, - freqs=self._freqs, - ) - - -# A clipped silu to prevent fp16 from overflowing -@partial(mx.compile, shapeless=True) -def clipped_silu(x): - return mx.clip(x * mx.sigmoid(x), a_min=-100, a_max=100) - - -class DeepseekV3Attention(nn.Module): - def __init__(self, config: ModelArgs): - super().__init__() - self.config = config - self.hidden_size = config.hidden_size - self.num_heads = config.num_attention_heads - self.max_position_embeddings = config.max_position_embeddings - self.rope_theta = config.rope_theta - self.q_lora_rank = config.q_lora_rank - self.qk_rope_head_dim = config.qk_rope_head_dim - self.kv_lora_rank = config.kv_lora_rank - self.v_head_dim = config.v_head_dim - self.qk_nope_head_dim = config.qk_nope_head_dim - self.q_head_dim = config.qk_nope_head_dim + config.qk_rope_head_dim - - self.scale = self.q_head_dim**-0.5 - - if self.q_lora_rank is None: - self.q_proj = nn.Linear( - self.hidden_size, self.num_heads * self.q_head_dim, bias=False - ) - else: - self.q_a_proj = nn.Linear( - self.hidden_size, self.q_lora_rank, bias=config.attention_bias - ) - self.q_a_layernorm = nn.RMSNorm(self.q_lora_rank) - self.q_b_proj = nn.Linear( - self.q_lora_rank, self.num_heads * self.q_head_dim, bias=False - ) - - self.kv_a_proj_with_mqa = nn.Linear( - self.hidden_size, - self.kv_lora_rank + self.qk_rope_head_dim, - bias=config.attention_bias, - ) - self.kv_a_layernorm = nn.RMSNorm(self.kv_lora_rank) - self.kv_b_proj = nn.Linear( - self.kv_lora_rank, - self.num_heads - * (self.q_head_dim - self.qk_rope_head_dim + self.v_head_dim), - bias=False, - ) - - self.o_proj = nn.Linear( - self.num_heads * self.v_head_dim, - self.hidden_size, - bias=config.attention_bias, - ) - - if self.config.rope_scaling is not None: - mscale_all_dim = self.config.rope_scaling.get("mscale_all_dim", 0) - scaling_factor = self.config.rope_scaling["factor"] - if mscale_all_dim: - mscale = yarn_get_mscale(scaling_factor, mscale_all_dim) - self.scale = self.scale * mscale * mscale - - rope_kwargs = { - key: self.config.rope_scaling[key] - for key in [ - "original_max_position_embeddings", - "beta_fast", - "beta_slow", - "mscale", - "mscale_all_dim", - ] - if key in self.config.rope_scaling - } - self.rope = DeepseekV3YarnRotaryEmbedding( - dim=self.qk_rope_head_dim, - max_position_embeddings=self.max_position_embeddings, - scaling_factor=scaling_factor, - base=self.rope_theta, - **rope_kwargs, - ) - else: - self.rope = nn.RoPE( - dims=self.qk_rope_head_dim, - base=self.rope_theta, - traditional=True, - ) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - B, L, D = x.shape - - if self.q_lora_rank is None: - q = self.q_proj(x) - else: - q = self.q_b_proj(self.q_a_layernorm(self.q_a_proj(x))) - - q = q.reshape(B, L, self.num_heads, self.q_head_dim).transpose(0, 2, 1, 3) - q_nope, q_pe = mx.split(q, [self.qk_nope_head_dim], axis=-1) - compressed_kv = self.kv_a_proj_with_mqa(x) - compressed_kv, k_pe = mx.split(compressed_kv, [self.kv_lora_rank], axis=-1) - k_pe = k_pe.reshape(B, L, 1, self.qk_rope_head_dim).transpose(0, 2, 1, 3) - kv = self.kv_b_proj(self.kv_a_layernorm(compressed_kv)) - kv = kv.reshape(B, L, self.num_heads, -1).transpose(0, 2, 1, 3) - - k_nope, values = mx.split(kv, [self.qk_nope_head_dim], axis=-1) - - if cache is not None: - q_pe = self.rope(q_pe, cache.offset) - k_pe = self.rope(k_pe, cache.offset) - k_pe = mx.repeat(k_pe, self.num_heads, axis=1) - keys, values = cache.update_and_fetch( - mx.concatenate([k_nope, k_pe], axis=-1), values - ) - else: - q_pe = self.rope(q_pe) - k_pe = self.rope(k_pe) - k_pe = mx.repeat(k_pe, self.num_heads, axis=1) - keys = mx.concatenate([k_nope, k_pe], axis=-1) - - queries = mx.concatenate([q_nope, q_pe], axis=-1) - - output = scaled_dot_product_attention( - queries, keys, values, cache=cache, scale=self.scale, mask=mask - ) - output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) - return self.o_proj(output) - - -class DeepseekV3MLP(nn.Module): - def __init__( - self, config: ModelArgs, hidden_size: int = None, intermediate_size: int = None - ): - super().__init__() - self.config = config - self.hidden_size = config.hidden_size if hidden_size is None else hidden_size - self.intermediate_size = ( - config.intermediate_size if intermediate_size is None else intermediate_size - ) - - self.gate_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False) - self.up_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False) - self.down_proj = nn.Linear(self.intermediate_size, self.hidden_size, bias=False) - - def __call__(self, x): - down_proj = self.down_proj(nn.silu(self.gate_proj(x)) * self.up_proj(x)) - return down_proj - - -@mx.compile -def group_expert_select( - gates, - e_score_correction_bias, - top_k, - n_group, - topk_group, - routed_scaling_factor, - norm_topk_prob, -): - - k = top_k - scores = mx.sigmoid(gates.astype(mx.float32)) - scores = scores + e_score_correction_bias - scores = mx.unflatten(scores, axis=-1, shape=(n_group, -1)) - group_scores = mx.topk(scores, 2, axis=-1).sum(axis=-1, keepdims=True) - k = n_group - topk_group - group_idx = mx.argpartition(group_scores, kth=k - 1, axis=-2)[..., :k, :] - scores = mx.put_along_axis(scores, group_idx, mx.array(0.0), axis=-2) - scores = mx.flatten(scores, -2, -1) - - k = top_k - inds = mx.argpartition(-scores, kth=k - 1, axis=-1)[..., :k] - scores = mx.take_along_axis(scores, inds, axis=-1) - if top_k > 1 and norm_topk_prob: - denominator = scores.sum(axis=-1, keepdims=True) + 1e-20 - scores = scores / denominator - scores = scores * routed_scaling_factor - - return inds, scores - - -class MoEGate(nn.Module): - def __init__(self, config: ModelArgs): - super().__init__() - self.config = config - self.top_k = config.num_experts_per_tok - self.norm_topk_prob = config.norm_topk_prob - self.n_routed_experts = config.n_routed_experts - self.routed_scaling_factor = config.routed_scaling_factor - self.n_group = config.n_group - self.topk_group = config.topk_group - self.weight = mx.zeros((self.n_routed_experts, config.hidden_size)) - self.e_score_correction_bias = mx.zeros((self.n_routed_experts,)) - assert config.topk_method == "noaux_tc", "Unsupported topk method." - - def __call__(self, x): - return group_expert_select( - x @ self.weight.T, - self.e_score_correction_bias, - self.top_k, - self.n_group, - self.topk_group, - self.routed_scaling_factor, - self.norm_topk_prob, - ) - - -class DeepseekV3MoE(nn.Module): - def __init__(self, config: ModelArgs): - super().__init__() - self.config = config - self.num_experts_per_tok = config.num_experts_per_tok - self.switch_mlp = SwitchGLU( - config.hidden_size, - config.moe_intermediate_size, - config.n_routed_experts, - activation=clipped_silu, - ) - - self.gate = MoEGate(config) - if config.n_shared_experts is not None: - intermediate_size = config.moe_intermediate_size * config.n_shared_experts - self.shared_experts = DeepseekV3MLP( - config=config, intermediate_size=intermediate_size - ) - - def __call__(self, x): - inds, scores = self.gate(x) - y = self.switch_mlp(x, inds) - y = (y * scores[..., None]).sum(axis=-2).astype(y.dtype) - if self.config.n_shared_experts is not None: - y = y + self.shared_experts(x) - - return y - - -class DeepseekV3DecoderLayer(nn.Module): - def __init__(self, config: ModelArgs, layer_idx: int): - super().__init__() - self.self_attn = DeepseekV3Attention(config) - self.mlp = ( - DeepseekV3MoE(config) - if ( - config.n_routed_experts is not None - and layer_idx >= config.first_k_dense_replace - and layer_idx % config.moe_layer_freq == 0 - ) - else DeepseekV3MLP(config) - ) - self.input_layernorm = nn.RMSNorm(config.hidden_size, eps=config.rms_norm_eps) - self.post_attention_layernorm = nn.RMSNorm( - config.hidden_size, eps=config.rms_norm_eps - ) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - r = self.self_attn(self.input_layernorm(x), mask, cache) - h = x + r - r = self.mlp(self.post_attention_layernorm(h)) - return h + r - - -class DeepseekV3Model(nn.Module): - def __init__(self, config: ModelArgs): - super().__init__() - self.vocab_size = config.vocab_size - self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size) - self.layers = [ - DeepseekV3DecoderLayer(config, idx) - for idx in range(config.num_hidden_layers) - ] - self.start_idx = 0 - self.end_idx = len(self.layers) - self.num_layers = self.end_idx - - self.norm = nn.RMSNorm(config.hidden_size, eps=config.rms_norm_eps) - self.pipeline_rank = 0 - self.pipeline_size = 1 - - def pipeline(self, group): - # Split layers in reverse so rank=0 gets the last layers and - # rank=pipeline_size-1 gets the first - self.pipeline_rank = group.rank() - self.pipeline_size = group.size() - layers_per_rank = len(self.layers) // self.pipeline_size - extra = len(self.layers) - layers_per_rank * self.pipeline_size - if self.pipeline_rank < extra: - layers_per_rank += 1 - self.start_idx = (self.pipeline_size - self.pipeline_rank - 1) * layers_per_rank - self.end_idx = self.start_idx + layers_per_rank - self.layers = self.layers[: self.end_idx] - self.layers[: self.start_idx] = [None] * self.start_idx - self.num_layers = len(self.layers) - self.start_idx - - def __call__( - self, - x: mx.array, - cache: Optional[Any] = None, - mask: Optional[mx.array] = None, - ) -> mx.array: - h = self.embed_tokens(x) - - pipeline_rank = self.pipeline_rank - pipeline_size = self.pipeline_size - # Hack to avoid time-outs during prompt-processing - dist_stream = mx.cpu if h.shape[1] > 1 else mx.gpu - if mask is None: - mask = create_attention_mask(h, cache) - - if cache is None: - cache = [None] * self.num_layers - - # Receive from the previous process in the pipeline - - if pipeline_rank < pipeline_size - 1: - h = mx.distributed.recv_like(h, (pipeline_rank + 1), stream=dist_stream) - - for i in range(self.num_layers): - h = self.layers[self.start_idx + i](h, mask, cache[i]) - - # Send to the next process in the pipeline - if pipeline_rank != 0: - h = mx.distributed.send( - h, (pipeline_rank - 1) % pipeline_size, stream=dist_stream - ) - - # Broadcast h while keeping it in the graph - h = mx.distributed.all_gather(h, stream=dist_stream)[: h.shape[0]] - - return self.norm(h) - - -class Model(nn.Module): - def __init__(self, config: ModelArgs): - super().__init__() - self.args = config - self.model_type = config.model_type - self.model = DeepseekV3Model(config) - self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False) - - def __call__( - self, - inputs: mx.array, - cache: Optional[Any] = None, - mask: Optional[mx.array] = None, - ): - out = self.model(inputs, cache, mask) - return self.lm_head(out) - - def sanitize(self, weights): - for l in range(self.args.num_hidden_layers): - prefix = f"model.layers.{l}" - for n, m in [("w1", "gate_proj"), ("w2", "down_proj"), ("w3", "up_proj")]: - for k in ["weight", "scales", "biases"]: - if f"{prefix}.mlp.experts.0.{m}.{k}" in weights: - to_join = [ - weights.pop(f"{prefix}.mlp.experts.{e}.{m}.{k}") - for e in range(self.args.n_routed_experts) - ] - weights[f"{prefix}.mlp.switch_mlp.{m}.{k}"] = mx.stack(to_join) - - # Remove multi-token prediction layer and any unused precomputed rotary freqs - return { - k: v - for k, v in weights.items() - if not k.startswith("model.layers.61") and "rotary_emb.inv_freq" not in k - } - - @property - def layers(self): - return self.model.layers[self.model.start_idx : self.model.end_idx] diff --git a/llms/mlx_lm/models/exaone.py b/llms/mlx_lm/models/exaone.py deleted file mode 100644 index ee3ed1e8..00000000 --- a/llms/mlx_lm/models/exaone.py +++ /dev/null @@ -1,166 +0,0 @@ -# Copyright © 2024 Apple Inc. - -from dataclasses import dataclass -from typing import Any, Dict, Optional, Union - -import mlx.core as mx -import mlx.nn as nn - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention -from .rope_utils import initialize_rope - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str - hidden_size: int - num_layers: int - intermediate_size: int - num_attention_heads: int - vocab_size: int - rope_theta: float - layer_norm_epsilon: float - num_key_value_heads: int - head_dim: Optional[int] = None - max_position_embeddings: Optional[int] = None - rope_traditional: bool = False - rope_scaling: Optional[Dict[str, Union[float, str]]] = None - tie_word_embeddings: bool = True - attention_bias: bool = False - mlp_bias: bool = False - - -class AttentionModule(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - dim = args.hidden_size - self.n_heads = n_heads = args.num_attention_heads - self.n_kv_heads = n_kv_heads = args.num_key_value_heads - self.head_dim = head_dim = args.head_dim or (dim // n_heads) - self.scale = head_dim**-0.5 - - self.q_proj = nn.Linear(dim, n_heads * head_dim, bias=args.attention_bias) - self.k_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=args.attention_bias) - self.v_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=args.attention_bias) - self.out_proj = nn.Linear(n_heads * head_dim, dim, bias=args.attention_bias) - - self.rope = initialize_rope( - self.head_dim, - args.rope_theta, - args.rope_traditional, - args.rope_scaling, - args.max_position_embeddings, - ) - - def __call__( - self, x: mx.array, mask: Optional[mx.array] = None, cache: Optional[Any] = None - ) -> mx.array: - B, L, D = x.shape - q = self.q_proj(x).reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) - k = self.k_proj(x).reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - v = self.v_proj(x).reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - - if cache is not None: - q = self.rope(q, offset=cache.offset) - k = self.rope(k, offset=cache.offset) - k, v = cache.update_and_fetch(k, v) - else: - q = self.rope(q) - k = self.rope(k) - - out = scaled_dot_product_attention( - q, k, v, cache=cache, scale=self.scale, mask=mask - ) - out = out.transpose(0, 2, 1, 3).reshape(B, L, D) - return self.out_proj(out) - - -class Attention(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.attention = AttentionModule(args) - - -class MLP(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - dim = args.hidden_size - hidden_dim = args.intermediate_size - self.c_fc_0 = nn.Linear(dim, hidden_dim, bias=args.mlp_bias) - self.c_fc_1 = nn.Linear(dim, hidden_dim, bias=args.mlp_bias) - self.c_proj = nn.Linear(hidden_dim, dim, bias=args.mlp_bias) - - def __call__(self, x: mx.array) -> mx.array: - return self.c_proj(nn.silu(self.c_fc_0(x)) * self.c_fc_1(x)) - - -class TransformerBlock(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.ln_1 = nn.RMSNorm(args.hidden_size, eps=args.layer_norm_epsilon) - self.attn = Attention(args) - self.ln_2 = nn.RMSNorm(args.hidden_size, eps=args.layer_norm_epsilon) - self.mlp = MLP(args) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - h = x + self.attn.attention(self.ln_1(x), mask, cache) - out = h + self.mlp(self.ln_2(h)) - return out - - -class ExaoneModel(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.wte = nn.Embedding(args.vocab_size, args.hidden_size) - self.h = [TransformerBlock(args) for _ in range(args.num_layers)] - self.ln_f = nn.RMSNorm(args.hidden_size, eps=args.layer_norm_epsilon) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - h = self.wte(inputs) - if mask is None: - mask = create_attention_mask(h, cache) - - if cache is None: - cache = [None] * len(self.h) - - for layer, c in zip(self.h, cache): - h = layer(h, mask, cache=c) - - return self.ln_f(h) - - -class Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.model_type = args.model_type - self.transformer = ExaoneModel(args) - if not args.tie_word_embeddings: - self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=False) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - out = self.transformer(inputs, mask, cache) - if self.args.tie_word_embeddings: - out = self.transformer.wte.as_linear(out) - else: - out = self.lm_head(out) - return out - - @property - def layers(self): - return self.transformer.h diff --git a/llms/mlx_lm/models/gemma.py b/llms/mlx_lm/models/gemma.py deleted file mode 100644 index 0860ddeb..00000000 --- a/llms/mlx_lm/models/gemma.py +++ /dev/null @@ -1,178 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -from dataclasses import dataclass -from typing import Any, Optional, Tuple - -import mlx.core as mx -import mlx.nn as nn - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str - hidden_size: int - num_hidden_layers: int - intermediate_size: int - num_attention_heads: int - head_dim: int - rms_norm_eps: float - vocab_size: int - num_key_value_heads: int - rope_theta: float = 10000 - rope_traditional: bool = False - - -class RMSNorm(nn.Module): - def __init__(self, dims: int, eps: float = 1e-5): - super().__init__() - self.weight = mx.ones((dims,)) - self.eps = eps - - def __call__(self, x): - return mx.fast.rms_norm(x, 1.0 + self.weight, self.eps) - - -class Attention(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - - dim = args.hidden_size - self.n_heads = n_heads = args.num_attention_heads - self.n_kv_heads = n_kv_heads = args.num_key_value_heads - self.head_dim = head_dim = args.head_dim - - self.scale = head_dim**-0.5 - - self.q_proj = nn.Linear(dim, n_heads * head_dim, bias=False) - self.k_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=False) - self.v_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=False) - self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=False) - - self.rope = nn.RoPE( - head_dim, - traditional=args.rope_traditional, - base=args.rope_theta, - ) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - B, L, D = x.shape - - queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x) - - # Prepare the queries, keys and values for the attention computation - queries = queries.reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) - keys = keys.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - values = values.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - - if cache is not None: - queries = self.rope(queries, offset=cache.offset) - keys = self.rope(keys, offset=cache.offset) - keys, values = cache.update_and_fetch(keys, values) - else: - queries = self.rope(queries) - keys = self.rope(keys) - - output = scaled_dot_product_attention( - queries, keys, values, cache=cache, scale=self.scale, mask=mask - ) - - output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) - return self.o_proj(output) - - -class MLP(nn.Module): - def __init__(self, dim, hidden_dim): - super().__init__() - self.gate_proj = nn.Linear(dim, hidden_dim, bias=False) - self.down_proj = nn.Linear(hidden_dim, dim, bias=False) - self.up_proj = nn.Linear(dim, hidden_dim, bias=False) - - def __call__(self, x) -> mx.array: - return self.down_proj(nn.gelu(self.gate_proj(x)) * self.up_proj(x)) - - -class TransformerBlock(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.num_attention_heads = args.num_attention_heads - self.hidden_size = args.hidden_size - self.self_attn = Attention(args) - self.mlp = MLP(args.hidden_size, args.intermediate_size) - self.input_layernorm = RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - self.post_attention_layernorm = RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - self.args = args - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - r = self.self_attn(self.input_layernorm(x), mask, cache) - h = x + r - r = self.mlp(self.post_attention_layernorm(h)) - out = h + r - return out - - -class GemmaModel(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.vocab_size = args.vocab_size - self.num_hidden_layers = args.num_hidden_layers - assert self.vocab_size > 0 - self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size) - self.layers = [ - TransformerBlock(args=args) for _ in range(args.num_hidden_layers) - ] - self.norm = RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - h = self.embed_tokens(inputs) - h = h * (self.args.hidden_size**0.5) - - if mask is None: - mask = create_attention_mask(h, cache) - - if cache is None: - cache = [None] * len(self.layers) - - for layer, c in zip(self.layers, cache): - h = layer(h, mask, c) - - return self.norm(h) - - -class Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.model_type = args.model_type - self.model = GemmaModel(args) - self.args = args - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - out = self.model(inputs, mask, cache) - out = self.model.embed_tokens.as_linear(out) - return out - - @property - def layers(self): - return self.model.layers diff --git a/llms/mlx_lm/models/gemma2.py b/llms/mlx_lm/models/gemma2.py deleted file mode 100644 index 321a58ff..00000000 --- a/llms/mlx_lm/models/gemma2.py +++ /dev/null @@ -1,203 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -from dataclasses import dataclass -from typing import Any, Optional, Tuple - -import mlx.core as mx -import mlx.nn as nn - -from .base import BaseModelArgs, create_attention_mask - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str - hidden_size: int - num_hidden_layers: int - intermediate_size: int - num_attention_heads: int - head_dim: int - rms_norm_eps: float - vocab_size: int - num_key_value_heads: int - rope_theta: float = 10000 - rope_traditional: bool = False - attn_logit_softcapping: float = 50.0 - final_logit_softcapping: float = 30.0 - query_pre_attn_scalar: float = 144.0 - - -class RMSNorm(nn.Module): - def __init__(self, dims: int, eps: float = 1e-5): - super().__init__() - self.weight = mx.ones((dims,)) - self.eps = eps - - def __call__(self, x): - return mx.fast.rms_norm(x, 1.0 + self.weight, self.eps) - - -class Attention(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - - dim = args.hidden_size - self.n_heads = n_heads = args.num_attention_heads - self.n_kv_heads = n_kv_heads = args.num_key_value_heads - self.repeats = n_heads // n_kv_heads - self.head_dim = head_dim = args.head_dim - - self.scale = 1.0 / (args.query_pre_attn_scalar**0.5) - - self.q_proj = nn.Linear(dim, n_heads * head_dim, bias=False) - self.k_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=False) - self.v_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=False) - self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=False) - self.attn_logit_softcapping = args.attn_logit_softcapping - self.rope = nn.RoPE( - head_dim, - traditional=args.rope_traditional, - base=args.rope_theta, - ) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - B, L, D = x.shape - queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x) - queries = queries.reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) - keys = keys.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - values = values.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - - if cache is not None: - queries = self.rope(queries, offset=cache.offset) - keys = self.rope(keys, offset=cache.offset) - keys, values = cache.update_and_fetch(keys, values) - else: - queries = self.rope(queries) - keys = self.rope(keys) - - queries = queries * self.scale - - if self.repeats > 1: - queries = queries.reshape( - B, self.n_kv_heads, self.repeats, L, self.head_dim - ) - keys = mx.expand_dims(keys, 2) - values = mx.expand_dims(values, 2) - - scores = queries @ keys.swapaxes(-1, -2) - scores = mx.tanh(scores / self.attn_logit_softcapping) - scores *= self.attn_logit_softcapping - - if mask is not None: - scores = scores + mask - scores = mx.softmax(scores, precise=True, axis=-1) - output = scores @ values - if self.repeats > 1: - output = output.reshape(B, self.n_heads, L, self.head_dim) - output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) - return self.o_proj(output) - - -class MLP(nn.Module): - def __init__(self, dim, hidden_dim): - super().__init__() - self.gate_proj = nn.Linear(dim, hidden_dim, bias=False) - self.down_proj = nn.Linear(hidden_dim, dim, bias=False) - self.up_proj = nn.Linear(dim, hidden_dim, bias=False) - - def __call__(self, x) -> mx.array: - return self.down_proj(nn.gelu_approx(self.gate_proj(x)) * self.up_proj(x)) - - -class TransformerBlock(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.num_attention_heads = args.num_attention_heads - self.hidden_size = args.hidden_size - self.self_attn = Attention(args) - self.mlp = MLP(args.hidden_size, args.intermediate_size) - self.input_layernorm = RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - self.pre_feedforward_layernorm = RMSNorm( - args.hidden_size, eps=args.rms_norm_eps - ) - self.post_feedforward_layernorm = RMSNorm( - args.hidden_size, eps=args.rms_norm_eps - ) - self.post_attention_layernorm = RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - self.args = args - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - r = self.self_attn(self.input_layernorm(x), mask, cache) - h = x + self.post_attention_layernorm(r) - r = self.mlp(self.pre_feedforward_layernorm(h)) - out = h + self.post_feedforward_layernorm(r) - return out - - -class GemmaModel(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.vocab_size = args.vocab_size - self.num_hidden_layers = args.num_hidden_layers - assert self.vocab_size > 0 - self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size) - self.layers = [ - TransformerBlock(args=args) for _ in range(args.num_hidden_layers) - ] - self.norm = RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - h = self.embed_tokens(inputs) - h = h * (self.args.hidden_size**0.5) - - if mask is None: - mask = create_attention_mask(h, cache) - - if cache is None: - cache = [None] * len(self.layers) - - for layer, c in zip(self.layers, cache): - h = layer(h, mask, c) - - return self.norm(h) - - -class Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.model_type = args.model_type - self.final_logit_softcapping = args.final_logit_softcapping - self.model = GemmaModel(args) - self.args = args - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - out = self.model(inputs, mask, cache) - out = self.model.embed_tokens.as_linear(out) - out = mx.tanh(out / self.final_logit_softcapping) - out = out * self.final_logit_softcapping - return out - - @property - def layers(self): - return self.model.layers diff --git a/llms/mlx_lm/models/gemma3_text.py b/llms/mlx_lm/models/gemma3_text.py deleted file mode 100644 index be71f461..00000000 --- a/llms/mlx_lm/models/gemma3_text.py +++ /dev/null @@ -1,238 +0,0 @@ -# Copyright © 2025 Apple Inc. - -from dataclasses import dataclass -from typing import Any, Optional - -import mlx.core as mx -import mlx.nn as nn - -from .base import BaseModelArgs, create_attention_mask -from .cache import KVCache, RotatingKVCache - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str - hidden_size: int = 1152 - num_hidden_layers: int = 26 - intermediate_size: int = 6912 - num_attention_heads: int = 4 - head_dim: int = 256 - rms_norm_eps: float = 1.0e-6 - vocab_size: int = 262144 - num_key_value_heads: int = 1 - rope_global_base_freq: float = 1_000_000.0 - rope_local_base_freq: float = 10_000.0 - rope_traditional: bool = False - query_pre_attn_scalar: float = 256 - sliding_window: int = 512 - sliding_window_pattern: int = 6 - - -class Attention(nn.Module): - def __init__(self, args: ModelArgs, layer_idx: int): - super().__init__() - - dim = args.hidden_size - self.n_heads = n_heads = args.num_attention_heads - self.n_kv_heads = n_kv_heads = args.num_key_value_heads - self.repeats = n_heads // n_kv_heads - self.head_dim = head_dim = args.head_dim - self.layer_idx = layer_idx - - self.scale = args.query_pre_attn_scalar**-0.5 - - self.q_proj = nn.Linear(dim, n_heads * head_dim, bias=False) - self.k_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=False) - self.v_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=False) - self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=False) - - self.q_norm = RMSNorm(dims=head_dim, eps=args.rms_norm_eps) - self.k_norm = RMSNorm(dims=head_dim, eps=args.rms_norm_eps) - self.is_sliding = (layer_idx + 1) % args.sliding_window_pattern != 0 - - self.rope = nn.RoPE( - head_dim, - traditional=args.rope_traditional, - base=( - args.rope_local_base_freq - if self.is_sliding - else args.rope_global_base_freq - ), - ) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - B, L, _ = x.shape - queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x) - queries = queries.reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) - - keys = keys.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - values = values.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - - queries = self.q_norm(queries) - keys = self.k_norm(keys) - - if cache is not None: - queries = self.rope(queries, offset=cache.offset) - keys = self.rope(keys, offset=cache.offset) - keys, values = cache.update_and_fetch(keys, values) - else: - queries = self.rope(queries) - keys = self.rope(keys) - - # Sliding window - if mask is not None and mask.shape[-1] != keys.shape[-2]: - mask = mask[..., -keys.shape[-2] :] - - output = mx.fast.scaled_dot_product_attention( - queries, keys, values, scale=self.scale, mask=mask - ) - output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) - return self.o_proj(output) - - -class RMSNorm(nn.Module): - def __init__(self, dims: int, eps: float = 1e-5): - super().__init__() - self.weight = mx.ones((dims,)) - self.eps = eps - - def __call__(self, x): - return mx.fast.rms_norm(x, 1.0 + self.weight, self.eps) - - -class MLP(nn.Module): - def __init__(self, dim, hidden_dim): - super().__init__() - self.gate_proj = nn.Linear(dim, hidden_dim, bias=False) - self.down_proj = nn.Linear(hidden_dim, dim, bias=False) - self.up_proj = nn.Linear(dim, hidden_dim, bias=False) - - def __call__(self, x) -> mx.array: - return self.down_proj(nn.gelu_approx(self.gate_proj(x)) * self.up_proj(x)) - - -class TransformerBlock(nn.Module): - def __init__(self, args: ModelArgs, layer_idx: int): - super().__init__() - self.num_attention_heads = args.num_attention_heads - self.hidden_size = args.hidden_size - self.self_attn = Attention(args, layer_idx) - self.mlp = MLP(args.hidden_size, args.intermediate_size) - self.input_layernorm = RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - self.post_attention_layernorm = RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - self.pre_feedforward_layernorm = RMSNorm( - args.hidden_size, eps=args.rms_norm_eps - ) - self.post_feedforward_layernorm = RMSNorm( - args.hidden_size, eps=args.rms_norm_eps - ) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - r = self.self_attn(self.input_layernorm(x), mask, cache) - h = x + self.post_attention_layernorm(r) - r = self.mlp(self.pre_feedforward_layernorm(h)) - out = h + self.post_feedforward_layernorm(r) - return out - - -class Gemma3Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.vocab_size = args.vocab_size - self.num_hidden_layers = args.num_hidden_layers - assert self.vocab_size > 0 - self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size) - self.layers = [ - TransformerBlock(args=args, layer_idx=layer_idx) - for layer_idx in range(args.num_hidden_layers) - ] - self.norm = RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - - h = self.embed_tokens(inputs) - h *= mx.array(self.args.hidden_size**0.5, mx.bfloat16).astype(h.dtype) - - if cache is None: - cache = [None] * len(self.layers) - - if mask is None: - j = self.args.sliding_window_pattern - full_mask = create_attention_mask(h, cache[j - 1 : j]) - sliding_window_mask = create_attention_mask(h, cache) - - for i, (layer, c) in enumerate(zip(self.layers, cache)): - is_sliding = ( - i % self.args.sliding_window_pattern - == self.args.sliding_window_pattern - 1 - ) - - if mask is None and is_sliding: - mask = sliding_window_mask - elif mask is None: - mask = full_mask - - h = layer(h, mask, c) - - return self.norm(h) - - -class Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.model_type = args.model_type - self.model = Gemma3Model(args) - self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=False) - - def __call__( - self, - inputs: mx.array, - cache=None, - mask: Optional[mx.array] = None, - ): - out = self.model(inputs, mask, cache) - out = self.lm_head(out) - return out - - def sanitize(self, weights): - if "lm_head.weight" not in weights: - weights["lm_head.weight"] = weights["model.embed_tokens.weight"] - return { - k: v for k, v in weights.items() if "self_attn.rotary_emb.inv_freq" not in k - } - - @property - def layers(self): - return self.model.layers - - def make_cache(self): - caches = [] - for i in range(self.args.num_hidden_layers): - if ( - i % self.args.sliding_window_pattern - == self.args.sliding_window_pattern - 1 - ): - caches.append(KVCache()) - else: - caches.append( - RotatingKVCache(max_size=self.args.sliding_window, keep=0) - ) - return caches diff --git a/llms/mlx_lm/models/gpt2.py b/llms/mlx_lm/models/gpt2.py deleted file mode 100644 index 5b277734..00000000 --- a/llms/mlx_lm/models/gpt2.py +++ /dev/null @@ -1,201 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -from dataclasses import dataclass -from typing import Any, Dict, Optional, Tuple, Union - -import mlx.core as mx -import mlx.nn as nn -import numpy as np - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str - n_ctx: int - n_embd: int - n_head: int - n_layer: int - n_positions: int - layer_norm_epsilon: float - vocab_size: int - num_key_value_heads: int = None - - def __post_init__(self): - if self.num_key_value_heads is None: - self.num_key_value_heads = self.n_head - - -class Attention(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - - assert args.n_embd % args.n_head == 0, "n_embd must be divisible by n_head" - - self.n_embd = args.n_embd - self.n_head = args.n_head - self.head_dim = self.n_embd // self.n_head - - self.scale = self.head_dim**-0.5 - - self.c_attn = nn.Linear(self.n_embd, 3 * self.n_embd, bias=True) - self.c_proj = nn.Linear(self.n_embd, self.n_embd, bias=True) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - B, L, D = x.shape - - qkv = self.c_attn(x) - queries, keys, values = mx.split(qkv, 3, axis=-1) - - # Prepare the queries, keys and values for the attention computation - queries = queries.reshape(B, L, self.n_head, -1).transpose(0, 2, 1, 3) - keys = keys.reshape(B, L, self.n_head, -1).transpose(0, 2, 1, 3) - values = values.reshape(B, L, self.n_head, -1).transpose(0, 2, 1, 3) - - if cache is not None: - keys, values = cache.update_and_fetch(keys, values) - - output = scaled_dot_product_attention( - queries, keys, values, cache=cache, scale=self.scale, mask=mask - ) - - output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) - return self.c_proj(output) - - -class MLP(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - - self.n_embd = args.n_embd - self.c_fc = nn.Linear(self.n_embd, 4 * self.n_embd) - self.c_proj = nn.Linear(4 * self.n_embd, self.n_embd) - - def __call__(self, x) -> mx.array: - return self.c_proj(nn.gelu_approx(self.c_fc(x))) - - -class TransformerBlock(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - - self.n_head = args.n_head - self.n_embd = args.n_embd - self.layer_norm_epsilon = args.layer_norm_epsilon - self.attn = Attention(args) - self.mlp = MLP(args) - self.ln_1 = nn.LayerNorm( - self.n_embd, - eps=self.layer_norm_epsilon, - ) - self.ln_2 = nn.LayerNorm(self.n_embd, eps=self.layer_norm_epsilon) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - r = self.attn(self.ln_1(x), mask, cache) - h = x + r - r = self.mlp(self.ln_2(h)) - out = h + r - return out - - -class GPT2Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.n_embd = args.n_embd - self.n_positions = args.n_positions - self.vocab_size = args.vocab_size - self.n_layer = args.n_layer - self.layer_norm_epsilon = args.layer_norm_epsilon - assert self.vocab_size > 0 - self.wte = nn.Embedding(self.vocab_size, self.n_embd) - self.wpe = nn.Embedding(self.n_positions, self.n_embd) - self.h = [TransformerBlock(args=args) for _ in range(self.n_layer)] - self.ln_f = nn.LayerNorm(self.n_embd, eps=self.layer_norm_epsilon) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - _, L = inputs.shape - - hidden_states = self.wte(inputs) - - mask = None - if hidden_states.shape[1] > 1: - - position_ids = mx.array(np.arange(L)) - hidden_states += self.wpe(position_ids) - - if mask is None: - mask = create_attention_mask(hidden_states, cache) - - if cache is None: - cache = [None] * len(self.h) - - for layer, c in zip(self.h, cache): - hidden_states = layer(hidden_states, mask, cache=c) - - return self.ln_f(hidden_states) - - -class Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.model_type = args.model_type - self.model = GPT2Model(args) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - out = self.model(inputs, mask, cache) - out = self.model.wte.as_linear(out) - return out - - def sanitize(self, weights): - new_weights = {} - for i in range(self.args.n_layer): - if f"h.{i}.attn.bias" in weights: - del weights[f"h.{i}.attn.bias"] - if f"h.{i}.attn.c_attn.weight" in weights: - weights[f"h.{i}.attn.c_attn.weight"] = weights[ - f"h.{i}.attn.c_attn.weight" - ].transpose(1, 0) - if f"h.{i}.attn.c_proj.weight" in weights: - weights[f"h.{i}.attn.c_proj.weight"] = weights[ - f"h.{i}.attn.c_proj.weight" - ].transpose(1, 0) - if f"h.{i}.mlp.c_fc.weight" in weights: - weights[f"h.{i}.mlp.c_fc.weight"] = weights[ - f"h.{i}.mlp.c_fc.weight" - ].transpose(1, 0) - if f"h.{i}.mlp.c_proj.weight" in weights: - weights[f"h.{i}.mlp.c_proj.weight"] = weights[ - f"h.{i}.mlp.c_proj.weight" - ].transpose(1, 0) - for weight in weights: - if not weight.startswith("model."): - new_weights[f"model.{weight}"] = weights[weight] - else: - new_weights[weight] = weights[weight] - return new_weights - - @property - def layers(self): - return self.model.h diff --git a/llms/mlx_lm/models/gpt_bigcode.py b/llms/mlx_lm/models/gpt_bigcode.py deleted file mode 100644 index 1d9794b6..00000000 --- a/llms/mlx_lm/models/gpt_bigcode.py +++ /dev/null @@ -1,189 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -from dataclasses import dataclass -from typing import Any, Dict, Optional, Tuple, Union - -import mlx.core as mx -import mlx.nn as nn -import numpy as np - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str - n_embd: int - n_layer: int - n_inner: int - n_head: int - n_positions: int - layer_norm_epsilon: float - vocab_size: int - num_key_value_heads: int = None - multi_query: bool = True - attention_bias: bool = True - mlp_bias: bool = True - tie_word_embeddings: bool = True - - def __post_init__(self): - if self.num_key_value_heads is None: - self.num_key_value_heads = 1 if self.multi_query else self.n_head - - -class Attention(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - - self.dim = dim = args.n_embd - self.n_heads = n_heads = args.n_head - self.n_kv_heads = n_kv_heads = 1 if args.multi_query else args.n_head - - self.head_dim = head_dim = dim // n_heads - - self.kv_dim = n_kv_heads * head_dim - - self.scale = head_dim**-0.5 - - if hasattr(args, "attention_bias"): - attention_bias = args.attention_bias - else: - attention_bias = False - - self.c_attn = nn.Linear(dim, dim + 2 * self.kv_dim, bias=attention_bias) - self.c_proj = nn.Linear(dim, dim, bias=attention_bias) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - B, L, D = x.shape - - qkv = self.c_attn(x) - queries, keys, values = mx.split( - qkv, [self.dim, self.dim + self.kv_dim], axis=-1 - ) - - # Prepare the queries, keys and values for the attention computation - queries = queries.reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) - keys = keys.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - values = values.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - - if cache is not None: - keys, values = cache.update_and_fetch(keys, values) - - output = scaled_dot_product_attention( - queries, keys, values, cache=cache, scale=self.scale, mask=mask - ) - output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) - return self.c_proj(output) - - -class MLP(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - - dim = args.n_embd - hidden_dim = args.n_inner - if hasattr(args, "mlp_bias"): - mlp_bias = args.mlp_bias - else: - mlp_bias = False - - self.c_fc = nn.Linear(dim, hidden_dim, bias=mlp_bias) - self.c_proj = nn.Linear(hidden_dim, dim, bias=mlp_bias) - - def __call__(self, x) -> mx.array: - return self.c_proj(nn.gelu(self.c_fc(x))) - - -class TransformerBlock(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.n_head = args.n_head - self.n_embd = args.n_embd - self.attn = Attention(args) - self.mlp = MLP(args) - self.ln_1 = nn.LayerNorm(args.n_embd, eps=args.layer_norm_epsilon) - self.ln_2 = nn.LayerNorm(args.n_embd, eps=args.layer_norm_epsilon) - self.args = args - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - r = self.attn(self.ln_1(x), mask, cache) - h = x + r - r = self.mlp(self.ln_2(h)) - out = h + r - return out - - -class GPTBigCodeModel(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.vocab_size = args.vocab_size - assert self.vocab_size > 0 - self.wte = nn.Embedding(args.vocab_size, args.n_embd) - self.wpe = nn.Embedding(args.n_positions, args.n_embd) - self.h = [TransformerBlock(args=args) for _ in range(args.n_layer)] - self.ln_f = nn.LayerNorm(args.n_embd, eps=args.layer_norm_epsilon) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - B, L = inputs.shape - - hidden_states = self.wte(inputs) - - mask = None - if mask is not None and hidden_states.shape[1] > 1: - mask = create_attention_mask(hidden_states, cache) - - if cache is None: - cache = [None] * len(self.h) - position_ids = mx.array(np.arange(L)) - else: - position_ids = mx.array(np.arange(cache[0].offset, cache[0].offset + L)) - - hidden_states += self.wpe(position_ids) - - for layer, c in zip(self.h, cache): - hidden_states = layer(hidden_states, mask, cache=c) - - return self.ln_f(hidden_states) - - -class Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.model_type = args.model_type - self.transformer = GPTBigCodeModel(args) - if not args.tie_word_embeddings: - self.lm_head = nn.Linear(args.n_embd, args.vocab_size, bias=False) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - out = self.transformer(inputs, mask, cache) - if self.args.tie_word_embeddings: - out = self.transformer.wte.as_linear(out) - else: - out = self.lm_head(out) - return out - - @property - def layers(self): - return self.transformer.h diff --git a/llms/mlx_lm/models/gpt_neox.py b/llms/mlx_lm/models/gpt_neox.py deleted file mode 100644 index 5e124a67..00000000 --- a/llms/mlx_lm/models/gpt_neox.py +++ /dev/null @@ -1,219 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -from dataclasses import dataclass -from typing import Any, Dict, Optional, Tuple, Union - -import mlx.core as mx -import mlx.nn as nn -import numpy as np - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention - -# Based on the transformers implementation at: -# https://github.com/huggingface/transformers/blob/main/src/transformers/models/gpt_neox/modeling_gpt_neox.py - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str - max_position_embeddings: int - hidden_size: int - num_attention_heads: int - num_hidden_layers: int - layer_norm_eps: float - vocab_size: int - rotary_emb_base: int - rotary_pct: float - num_key_value_heads: int = None - - def __post_init__(self): - if self.num_key_value_heads is None: - self.num_key_value_heads = self.num_attention_heads - - -class Attention(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - - assert ( - args.hidden_size % args.num_attention_heads == 0 - ), "hidden_size must be divisible by num_attention_heads" - - self.hidden_size = args.hidden_size - self.num_attention_heads = args.num_attention_heads - self.head_dim = self.hidden_size // self.num_attention_heads - - self.rope = nn.RoPE( - dims=int(self.head_dim * args.rotary_pct), - traditional=False, - base=args.rotary_emb_base, - ) - - self.scale = self.head_dim**-0.5 - - self.query_key_value = nn.Linear( - self.hidden_size, 3 * self.hidden_size, bias=True - ) - self.dense = nn.Linear(self.hidden_size, self.hidden_size, bias=True) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - B, L, D = x.shape - - qkv = self.query_key_value(x) - - new_qkv_shape = qkv.shape[:-1] + (self.num_attention_heads, 3 * self.head_dim) - qkv = qkv.reshape(*new_qkv_shape) - - queries, keys, values = [x.transpose(0, 2, 1, 3) for x in qkv.split(3, -1)] - - if cache is not None: - queries = self.rope(queries, offset=cache.offset) - keys = self.rope(keys, offset=cache.offset) - keys, values = cache.update_and_fetch(keys, values) - else: - queries = self.rope(queries) - keys = self.rope(keys) - - output = scaled_dot_product_attention( - queries, keys, values, cache=cache, scale=self.scale, mask=mask - ) - - output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) - return self.dense(output) - - -class MLP(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - - self.hidden_size = args.hidden_size - self.dense_h_to_4h = nn.Linear(self.hidden_size, 4 * self.hidden_size) - self.dense_4h_to_h = nn.Linear(4 * self.hidden_size, self.hidden_size) - - def __call__(self, x) -> mx.array: - # gelu_approx corresponds to FastGELUActivation in transformers. - return self.dense_4h_to_h(nn.gelu_approx(self.dense_h_to_4h(x))) - - -class TransformerBlock(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - - self.hidden_size = args.hidden_size - self.layer_norm_eps = args.layer_norm_eps - self.attention = Attention(args) - self.mlp = MLP(args) - self.input_layernorm = nn.LayerNorm( - self.hidden_size, - eps=self.layer_norm_eps, - ) - self.post_attention_layernorm = nn.LayerNorm( - self.hidden_size, eps=self.layer_norm_eps - ) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - residual = x - # NeoX runs attention and feedforward network in parallel. - attn = self.attention(self.input_layernorm(x), mask, cache) - ffn = self.mlp(self.post_attention_layernorm(x)) - out = attn + ffn + residual - return out - - -class GPTNeoXModel(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.hidden_size = args.hidden_size - self.vocab_size = args.vocab_size - self.num_hidden_layers = args.num_hidden_layers - self.layer_norm_eps = args.layer_norm_eps - assert self.vocab_size > 0 - self.embed_in = nn.Embedding(self.vocab_size, self.hidden_size) - self.embed_out = nn.Linear(args.hidden_size, args.vocab_size, bias=False) - self.h = [TransformerBlock(args=args) for _ in range(self.num_hidden_layers)] - self.final_layer_norm = nn.LayerNorm(self.hidden_size, eps=self.layer_norm_eps) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - _, L = inputs.shape - - hidden_states = self.embed_in(inputs) - - if mask is None: - mask = create_attention_mask(hidden_states, cache) - - if cache is None: - cache = [None] * len(self.h) - - for layer, c in zip(self.h, cache): - hidden_states = layer(hidden_states, mask, cache=c) - - out = self.final_layer_norm(hidden_states) - out = self.embed_out(out) - - return out - - -class Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.model_type = args.model_type - self.model = GPTNeoXModel(args) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - out = self.model(inputs, mask, cache) - return out - - def sanitize(self, weights): - new_weights = {} - - for w_key, w_value in weights.items(): - # Created through register_buffer in Pytorch, not needed here. - ignore_suffixes = [ - ".attention.bias", - ".attention.masked_bias", - ".attention.rotary_emb.inv_freq", - ] - - skip_weight = False - for ignored_suffix in ignore_suffixes: - if w_key.endswith(ignored_suffix): - skip_weight = True - break - - if skip_weight: - continue - - if not w_key.startswith("model."): - w_key = f"model.{w_key}" - - w_key = w_key.replace(".gpt_neox.layers.", ".h.") - w_key = w_key.replace(".gpt_neox.", ".") - - new_weights[w_key] = w_value - - return new_weights - - @property - def layers(self): - return self.model.h diff --git a/llms/mlx_lm/models/granite.py b/llms/mlx_lm/models/granite.py deleted file mode 100644 index 43597d99..00000000 --- a/llms/mlx_lm/models/granite.py +++ /dev/null @@ -1,195 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -from dataclasses import dataclass -from typing import Any, Dict, Optional, Union - -import mlx.core as mx -import mlx.nn as nn - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention -from .rope_utils import initialize_rope - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str - hidden_size: int - num_hidden_layers: int - intermediate_size: int - num_attention_heads: int - rms_norm_eps: float - vocab_size: int - logits_scaling: float - attention_multiplier: float - embedding_multiplier: float - residual_multiplier: float - max_position_embeddings: int - num_key_value_heads: int - attention_bias: bool - mlp_bias: bool - rope_theta: float - rope_scaling: Optional[Dict[str, Union[float, str]]] = None - tie_word_embeddings: bool = True - - -class Attention(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - - dim = args.hidden_size - self.n_heads = n_heads = args.num_attention_heads - self.n_kv_heads = n_kv_heads = args.num_key_value_heads - - self.head_dim = head_dim = args.hidden_size // n_heads - - self.scale = args.attention_multiplier - attention_bias = args.attention_bias - self.q_proj = nn.Linear(dim, n_heads * head_dim, bias=attention_bias) - self.k_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=attention_bias) - self.v_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=attention_bias) - self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=attention_bias) - - self.rope = initialize_rope( - self.head_dim, - args.rope_theta, - False, - args.rope_scaling, - args.max_position_embeddings, - ) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - B, L, D = x.shape - - queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x) - - # Prepare the queries, keys and values for the attention computation - queries = queries.reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) - keys = keys.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - values = values.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - - if cache is not None: - queries = self.rope(queries, offset=cache.offset) - keys = self.rope(keys, offset=cache.offset) - keys, values = cache.update_and_fetch(keys, values) - else: - queries = self.rope(queries) - keys = self.rope(keys) - - output = scaled_dot_product_attention( - queries, keys, values, cache=cache, scale=self.scale, mask=mask - ) - - output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) - return self.o_proj(output) - - -class MLP(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - - dim = args.hidden_size - hidden_dim = args.intermediate_size - if hasattr(args, "mlp_bias"): - mlp_bias = args.mlp_bias - else: - mlp_bias = False - - self.gate_proj = nn.Linear(dim, hidden_dim, bias=mlp_bias) - self.down_proj = nn.Linear(hidden_dim, dim, bias=mlp_bias) - self.up_proj = nn.Linear(dim, hidden_dim, bias=mlp_bias) - - def __call__(self, x) -> mx.array: - return self.down_proj(nn.silu(self.gate_proj(x)) * self.up_proj(x)) - - -class TransformerBlock(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.num_attention_heads = args.num_attention_heads - self.hidden_size = args.hidden_size - self.self_attn = Attention(args) - self.mlp = MLP(args) - self.input_layernorm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - self.post_attention_layernorm = nn.RMSNorm( - args.hidden_size, eps=args.rms_norm_eps - ) - self.residual_multiplier = args.residual_multiplier - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - r = self.self_attn(self.input_layernorm(x), mask, cache) - h = x + r * self.residual_multiplier - r = self.mlp(self.post_attention_layernorm(h)) - out = h + r * self.residual_multiplier - return out - - -class GraniteModel(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.vocab_size = args.vocab_size - self.num_hidden_layers = args.num_hidden_layers - assert self.vocab_size > 0 - self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size) - self.layers = [ - TransformerBlock(args=args) for _ in range(args.num_hidden_layers) - ] - self.norm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - self.embedding_multiplier = args.embedding_multiplier - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - h = self.embed_tokens(inputs) * self.embedding_multiplier - - if mask is None: - mask = create_attention_mask(h, cache) - - if cache is None: - cache = [None] * len(self.layers) - - for layer, c in zip(self.layers, cache): - h = layer(h, mask, cache=c) - - return self.norm(h) - - -class Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.model_type = args.model_type - self.model = GraniteModel(args) - if not args.tie_word_embeddings: - self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=False) - self.logits_scaling = args.logits_scaling - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - out = self.model(inputs, mask, cache) - if self.args.tie_word_embeddings: - out = self.model.embed_tokens.as_linear(out) - else: - out = self.lm_head(out) - return out / self.logits_scaling - - @property - def layers(self): - return self.model.layers diff --git a/llms/mlx_lm/models/helium.py b/llms/mlx_lm/models/helium.py deleted file mode 100644 index ff551bca..00000000 --- a/llms/mlx_lm/models/helium.py +++ /dev/null @@ -1,185 +0,0 @@ -# Copyright © 2025 Apple Inc. - -from dataclasses import dataclass -from typing import Any, Optional, Tuple - -import mlx.core as mx -import mlx.nn as nn - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention - - -@dataclass -class ModelArgs(BaseModelArgs): - hidden_size: int - num_hidden_layers: int - intermediate_size: int - num_attention_heads: int - num_key_value_heads: int - rms_norm_eps: float - vocab_size: int - attention_bias: bool - head_dim: int - max_position_embeddings: int - mlp_bias: bool - model_type: str - rope_theta: float - tie_word_embeddings: bool - - -class HeliumAttention(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - - dim = args.hidden_size - self.n_heads = n_heads = args.num_attention_heads - assert args.num_key_value_heads is not None - self.n_kv_heads = n_kv_heads = args.num_key_value_heads - - head_dim = args.hidden_size // n_heads - self.scale = head_dim**-0.5 - - self.q_proj = nn.Linear(dim, n_heads * head_dim, bias=args.attention_bias) - self.k_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=args.attention_bias) - self.v_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=args.attention_bias) - self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=False) - self.rope = nn.RoPE(head_dim, traditional=True, base=args.rope_theta) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - B, L, D = x.shape - - queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x) - - # Prepare the queries, keys and values for the attention computation - queries = queries.reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) - keys = keys.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - values = values.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - - if cache is not None: - queries = self.rope(queries, offset=cache.offset) - keys = self.rope(keys, offset=cache.offset) - keys, values = cache.update_and_fetch(keys, values) - else: - queries = self.rope(queries) - keys = self.rope(keys) - - output = scaled_dot_product_attention( - queries, keys, values, cache=cache, scale=self.scale, mask=mask - ) - output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) - return self.o_proj(output) - - -class HeliumMLP(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.hidden_size = args.hidden_size - self.intermediate_size = args.intermediate_size - - self.gate_proj = nn.Linear( - self.hidden_size, self.intermediate_size, bias=args.mlp_bias - ) - self.up_proj = nn.Linear( - self.hidden_size, self.intermediate_size, bias=args.mlp_bias - ) - self.down_proj = nn.Linear( - self.intermediate_size, self.hidden_size, bias=args.mlp_bias - ) - - def __call__(self, x: mx.array) -> mx.array: - return self.down_proj(nn.silu(self.gate_proj(x)) * self.up_proj(x)) - - -class HeliumDecoderLayer(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.hidden_size = args.hidden_size - - self.self_attn = HeliumAttention(args) - self.mlp = HeliumMLP(args) - self.input_layernorm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - self.post_attention_layernorm = nn.RMSNorm( - args.hidden_size, eps=args.rms_norm_eps - ) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - r = self.self_attn(self.input_layernorm(x), mask, cache) - h = x + r - r = self.mlp(self.post_attention_layernorm(h)) - out = h + r - return out - - -class HeliumModel(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.num_hidden_layers = args.num_hidden_layers - self.vocab_size = args.vocab_size - - assert self.vocab_size > 0 - self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size) - - self.layers = [HeliumDecoderLayer(args) for _ in range(args.num_hidden_layers)] - - self.norm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ) -> mx.array: - h = self.embed_tokens(inputs) - - if mask is None: - mask = create_attention_mask(h, cache) - - if cache is None: - cache = [None] * len(self.layers) - - for layer, c in zip(self.layers, cache): - h = layer(h, mask, c) - - return self.norm(h) - - -class Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.model_type = args.model_type - - self.model = HeliumModel(args) - - self.vocab_size = args.vocab_size - self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=False) - - if not args.tie_word_embeddings: - self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=False) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ) -> mx.array: - out = self.model(inputs, mask, cache) - if self.args.tie_word_embeddings: - out = self.model.embed_tokens.as_linear(out) - else: - out = self.lm_head(out) - return out - - @property - def layers(self): - return self.model.layers diff --git a/llms/mlx_lm/models/hunyuan.py b/llms/mlx_lm/models/hunyuan.py deleted file mode 100644 index 122cebda..00000000 --- a/llms/mlx_lm/models/hunyuan.py +++ /dev/null @@ -1,321 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -import math -from dataclasses import dataclass -from typing import Any, Dict, Optional, Tuple, Union - -import mlx.core as mx -import mlx.nn as nn - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention -from .switch_layers import SwitchGLU - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str - vocab_size: int - hidden_size: int - num_hidden_layers: int - intermediate_size: int - num_attention_heads: int - num_key_value_heads: int - attention_bias: bool - moe_topk: int - num_experts: int - num_shared_expert: int - use_mixed_mlp_moe: bool - use_qk_norm: bool - rms_norm_eps: float - rope_theta: float - use_cla: bool - cla_share_factor: 2 - rope_scaling: Optional[Dict[str, Union[float, str]]] = None - tie_word_embeddings: bool = False - - def __post_init__(self): - - if self.rope_scaling: - required_keys = {"factor", "type"} - if not all(key in self.rope_scaling for key in required_keys): - raise ValueError(f"rope_scaling must contain keys {required_keys}") - - -class DynamicNTKAlphaRoPE(nn.Module): - def __init__( - self, - dims: int, - base: float = 10000, - scaling_alpha: float = 1.0, - ): - super().__init__() - self.dims = dims - base = base * scaling_alpha ** (dims / (dims - 2)) - self._freqs = base ** (mx.arange(0, self.dims, 2) / self.dims) - - def __call__(self, x, offset: int = 0): - return mx.fast.rope( - x, - self.dims, - traditional=False, - base=None, - scale=1.0, - offset=offset, - freqs=self._freqs, - ) - - -class Attention(nn.Module): - def __init__(self, kv_proj: bool, args: ModelArgs): - super().__init__() - - dim = args.hidden_size - self.n_heads = n_heads = args.num_attention_heads - assert args.num_key_value_heads is not None - self.n_kv_heads = n_kv_heads = args.num_key_value_heads - - head_dim = args.hidden_size // n_heads - self.scale = head_dim**-0.5 - self.q_proj = nn.Linear(dim, n_heads * head_dim, bias=args.attention_bias) - if kv_proj: - self.k_proj = nn.Linear( - dim, n_kv_heads * head_dim, bias=args.attention_bias - ) - self.v_proj = nn.Linear( - dim, n_kv_heads * head_dim, bias=args.attention_bias - ) - self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=args.attention_bias) - self.use_qk_norm = args.use_qk_norm - if self.use_qk_norm: - self.query_layernorm = nn.RMSNorm(head_dim, args.rms_norm_eps) - self.key_layernorm = nn.RMSNorm(head_dim, args.rms_norm_eps) - - self.rope = DynamicNTKAlphaRoPE( - head_dim, - base=args.rope_theta, - scaling_alpha=args.rope_scaling["alpha"], - ) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - kv_states=None, - ) -> mx.array: - B, L, D = x.shape - - queries = self.q_proj(x) - if kv_states is None: - keys, values = self.k_proj(x), self.v_proj(x) - kv_states = keys, values - else: - keys, values = kv_states - - # Prepare the queries, keys and values for the attention computation - queries = queries.reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) - keys = keys.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - values = values.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - - offset = cache.offset if cache else 0 - queries = self.rope(queries, offset=offset) - keys = self.rope(keys, offset=offset) - if self.use_qk_norm: - queries = self.query_layernorm(queries) - keys = self.key_layernorm(keys) - - if cache is not None: - keys, values = cache.update_and_fetch(keys, values) - - output = scaled_dot_product_attention( - queries, keys, values, cache=cache, scale=self.scale, mask=mask - ) - output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) - return self.o_proj(output), kv_states - - -class MLP(nn.Module): - def __init__(self, dim, hidden_dim): - super().__init__() - self.gate_proj = nn.Linear(dim, hidden_dim, bias=False) - self.down_proj = nn.Linear(hidden_dim, dim, bias=False) - self.up_proj = nn.Linear(dim, hidden_dim, bias=False) - - def __call__(self, x) -> mx.array: - return self.down_proj(nn.silu(self.gate_proj(x)) * self.up_proj(x)) - - -class Gate(nn.Module): - def __init__(self, dim, num_experts): - super().__init__() - self.wg = nn.Linear(dim, num_experts, bias=False) - - def __call__(self, x) -> mx.array: - return self.wg(x) - - -class MoeBlock(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - dim = args.hidden_size - intermediate_size = args.intermediate_size - self.use_shared_mlp = args.use_mixed_mlp_moe - - if args.use_mixed_mlp_moe: - self.shared_mlp = MLP(dim, intermediate_size * args.num_shared_expert) - - self.num_experts = num_experts = args.num_experts - self.top_k = args.moe_topk - - self.gate = Gate(dim, num_experts) - self.switch_mlp = SwitchGLU(dim, intermediate_size, num_experts) - - def __call__( - self, - x: mx.array, - ): - gates = self.gate(x) - gates = mx.softmax(gates, axis=-1, precise=True) - - k = self.top_k - inds = mx.stop_gradient(mx.argpartition(-gates, kth=k - 1, axis=-1)[..., :k]) - scores = mx.take_along_axis(gates, inds, axis=-1) - - y = self.switch_mlp(x, inds) - y = (y * scores[..., None]).sum(axis=-2) - - if self.use_shared_mlp: - shared_expert_output = self.shared_mlp(x) - y = y + shared_expert_output - - return y - - -class DecoderLayer(nn.Module): - def __init__(self, args: ModelArgs, kv_proj: bool): - super().__init__() - self.hidden_size = args.hidden_size - self.self_attn = Attention(kv_proj, args) - if args.num_experts == 1: - self.mlp = MLP(args.hidden_size, args.intermediate_size) - else: - self.mlp = MoeBlock(args) - - self.input_layernorm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - self.post_attention_layernorm = nn.RMSNorm( - args.hidden_size, eps=args.rms_norm_eps - ) - self.args = args - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - shared_kv_states: Optional[Tuple[mx.array, mx.array]] = None, - ): - r, shared_kv_states = self.self_attn( - self.input_layernorm(x), mask, cache, shared_kv_states - ) - h = x + r - r = self.mlp(self.post_attention_layernorm(h)) - out = h + r - return out, shared_kv_states - - -class HunYuanModel(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.vocab_size = args.vocab_size - self.num_hidden_layers = args.num_hidden_layers - assert self.vocab_size > 0 - self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size) - self.layers = [ - DecoderLayer( - args=args, - kv_proj=(not args.use_cla) or (i % args.cla_share_factor) == 0, - ) - for i in range(args.num_hidden_layers) - ] - self.norm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - h = self.embed_tokens(inputs) - - if mask is None: - mask = create_attention_mask(h, cache) - - if cache is None: - cache = [None] * len(self.layers) - - for i, (layer, c) in enumerate(zip(self.layers, cache)): - if (not self.args.use_cla) or i % self.args.cla_share_factor == 0: - shared_kv_states = None - h, shared_kv_states = layer(h, mask, c, shared_kv_states) - - return self.norm(h) - - -class Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.model_type = args.model_type - self.model = HunYuanModel(args) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - out = self.model(inputs, mask, cache) - return self.model.embed_tokens.as_linear(out) - - def sanitize(self, weights): - - if "model.layers.0.mlp.gate_and_up_proj.weight" in weights: - new_weights = {} - D = self.args.hidden_size - n_kv_heads = self.args.num_key_value_heads - n_kv_groups = self.args.num_attention_heads // n_kv_heads - head_dim = D // self.args.num_attention_heads - for k, v in weights.items(): - if "qkv_proj" in k: - v = v.reshape(n_kv_heads, n_kv_groups + 2, head_dim, -1) - splits = v.split([n_kv_groups, n_kv_groups + 1], axis=1) - for k_up, v_new in zip(["q_proj", "k_proj", "v_proj"], splits): - k_new = k.replace("qkv_proj", k_up) - new_weights[k_new] = mx.flatten(v_new, 0, 2) - elif "gate_and_up_proj" in k: - splits = v.split(2, axis=0) - for k_up, v_new in zip(["up_proj", "gate_proj"], splits): - k_new = k.replace("gate_and_up_proj", k_up) - new_weights[k_new] = v_new - else: - new_weights[k] = v - weights = new_weights - - if "model.layers.0.mlp.experts.0.up_proj.weight" not in weights: - return weights - for l in range(self.args.num_hidden_layers): - prefix = f"model.layers.{l}" - for n in ["up_proj", "down_proj", "gate_proj"]: - for k in ["weight", "scales", "biases"]: - if f"{prefix}.mlp.experts.0.{n}.{k}" in weights: - to_join = [ - weights.pop(f"{prefix}.mlp.experts.{e}.{n}.{k}") - for e in range(self.args.num_experts) - ] - weights[f"{prefix}.mlp.switch_mlp.{n}.{k}"] = mx.stack(to_join) - return weights - - @property - def layers(self): - return self.model.layers diff --git a/llms/mlx_lm/models/internlm2.py b/llms/mlx_lm/models/internlm2.py deleted file mode 100644 index 28a095e1..00000000 --- a/llms/mlx_lm/models/internlm2.py +++ /dev/null @@ -1,241 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -from dataclasses import dataclass -from typing import Any, Dict, Optional, Tuple, Union - -import mlx.core as mx -import mlx.nn as nn - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str - hidden_size: int - num_hidden_layers: int - intermediate_size: int - num_attention_heads: int - rms_norm_eps: float - vocab_size: int - bias: bool = True - max_position_embeddings: int = 32768 - num_key_value_heads: int = None - rope_theta: float = 10000 - rope_traditional: bool = False - rope_scaling: Optional[Dict[str, Union[float, str]]] = None - tie_word_embeddings: bool = False - - def __post_init__(self): - if self.num_key_value_heads is None: - self.num_key_value_heads = self.num_attention_heads - - if self.rope_scaling: - required_keys = {"factor", "type"} - if not all(key in self.rope_scaling for key in required_keys): - raise ValueError(f"rope_scaling must contain keys {required_keys}") - - if self.rope_scaling["type"] not in ["linear", "dynamic"]: - raise ValueError( - "rope_scaling 'type' currently only supports 'linear' or 'dynamic" - ) - - -class DynamicNTKScalingRoPE(nn.Module): - """Implements the rotary positional encoding with Dynamic NTK scaling.""" - - def __init__( - self, - dims: int, - max_position_embeddings: int = 2048, - traditional: bool = False, - base: float = 10000, - scale: float = 1.0, - ): - super().__init__() - self.max_position_embeddings = max_position_embeddings - self.original_base = base - self.dims = dims - self.traditional = traditional - self.scale = scale - - def extra_repr(self): - return f"{self.dims}, traditional={self.traditional}, max_position_embeddings={self.max_position_embeddings}, scaling_factor={self.scaling_factor}" - - def __call__(self, x, offset: int = 0): - seq_len = x.shape[1] + offset - if seq_len > self.max_position_embeddings: - base = self.original_base * ( - (self.scale * seq_len / self.max_position_embeddings) - (self.scale - 1) - ) ** (self.dims / (self.dims - 2)) - else: - base = self.original_base - - return mx.fast.rope( - x, - self.dims, - traditional=self.traditional, - base=base, - scale=self.scale, - offset=offset, - ) - - -class Attention(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - - dim = args.hidden_size - self.n_heads = n_heads = args.num_attention_heads - self.n_kv_heads = n_kv_heads = args.num_key_value_heads - self.n_kv_groups = n_heads // args.num_key_value_heads - - self.head_dim = head_dim = args.hidden_size // n_heads - self.scale = head_dim**-0.5 - - self.wqkv = nn.Linear( - dim, (n_heads + 2 * n_kv_heads) * head_dim, bias=args.bias - ) - self.wo = nn.Linear(n_heads * head_dim, dim, bias=args.bias) - - rope_scale = ( - 1 / args.rope_scaling["factor"] - if args.rope_scaling is not None and args.rope_scaling["type"] == "linear" - else 2.0 - ) - - self.rope = DynamicNTKScalingRoPE( - head_dim, - max_position_embeddings=args.max_position_embeddings, - traditional=args.rope_traditional, - base=args.rope_theta, - scale=rope_scale, - ) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - B, L, D = x.shape - - qkv_states = self.wqkv(x) - qkv_states = qkv_states.reshape(B, L, -1, 2 + self.n_kv_groups, self.head_dim) - - queries = qkv_states[..., : self.n_kv_groups, :] - queries = queries.reshape(B, L, -1, self.head_dim) - keys = qkv_states[..., -2, :] - values = qkv_states[..., -1, :] - - # Prepare the queries, keys and values for the attention computation - queries = queries.reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) - keys = keys.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - values = values.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - - if cache is not None: - queries = self.rope(queries, offset=cache.offset) - keys = self.rope(keys, offset=cache.offset) - keys, values = cache.update_and_fetch(keys, values) - else: - queries = self.rope(queries) - keys = self.rope(keys) - - output = scaled_dot_product_attention( - queries, keys, values, cache=cache, scale=self.scale, mask=mask - ) - output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) - return self.wo(output) - - -class MLP(nn.Module): - def __init__(self, dim, hidden_dim): - super().__init__() - self.w1 = nn.Linear(dim, hidden_dim, bias=False) - self.w2 = nn.Linear(hidden_dim, dim, bias=False) - self.w3 = nn.Linear(dim, hidden_dim, bias=False) - - def __call__(self, x) -> mx.array: - return self.w2(nn.silu(self.w1(x)) * self.w3(x)) - - -class TransformerBlock(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.attention = Attention(args) - self.feed_forward = MLP(args.hidden_size, args.intermediate_size) - self.attention_norm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - self.ffn_norm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - r = self.attention(self.attention_norm(x), mask, cache) - h = x + r - r = self.feed_forward(self.ffn_norm(h)) - out = h + r - return out - - -class InternLM2Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - assert args.vocab_size > 0 - self.tok_embeddings = nn.Embedding(args.vocab_size, args.hidden_size) - self.layers = [ - TransformerBlock(args=args) for _ in range(args.num_hidden_layers) - ] - self.norm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - h = self.tok_embeddings(inputs) - - if mask is None: - mask = create_attention_mask(h, cache) - - if cache is None: - cache = [None] * len(self.layers) - - for layer, c in zip(self.layers, cache): - h = layer(h, mask, cache=c) - - return self.norm(h) - - -class Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.model_type = args.model_type - self.model = InternLM2Model(args) - if not args.tie_word_embeddings: - self.output = nn.Linear(args.hidden_size, args.vocab_size, bias=False) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - out = self.model(inputs, mask, cache) - if self.args.tie_word_embeddings: - out = self.model.tok_embeddings.as_linear(out) - else: - out = self.output(out) - return out - - def sanitize(self, weights): - # Remove unused precomputed rotary freqs - return {k: v for k, v in weights.items() if "attention.rope.inv_freq" not in k} - - @property - def layers(self): - return self.model.layers diff --git a/llms/mlx_lm/models/internlm3.py b/llms/mlx_lm/models/internlm3.py deleted file mode 100644 index 3be6f536..00000000 --- a/llms/mlx_lm/models/internlm3.py +++ /dev/null @@ -1,241 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -from dataclasses import dataclass -from typing import Any, Dict, Optional, Tuple, Union - -import mlx.core as mx -import mlx.nn as nn - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str - hidden_size: int - num_hidden_layers: int - intermediate_size: int - num_attention_heads: int - rms_norm_eps: float - vocab_size: int - bias: bool = False - qkv_bias: bool = False - max_position_embeddings: int = 32768 - num_key_value_heads: int = None - rope_theta: float = 10000 - rope_traditional: bool = False - rope_scaling: Optional[Dict[str, Union[float, str]]] = None - tie_word_embeddings: bool = False - - def __post_init__(self): - if self.num_key_value_heads is None: - self.num_key_value_heads = self.num_attention_heads - - if self.rope_scaling: - required_keys = {"factor", "rope_type"} - if not all(key in self.rope_scaling for key in required_keys): - raise ValueError(f"rope_scaling must contain keys {required_keys}") - - if self.rope_scaling["rope_type"] not in ["linear", "dynamic"]: - raise ValueError( - "rope_scaling 'rope_type' currently only supports 'linear' or 'dynamic" - ) - - -class DynamicNTKScalingRoPE(nn.Module): - """Implements the rotary positional encoding with Dynamic NTK scaling.""" - - def __init__( - self, - dims: int, - max_position_embeddings: int = 2048, - traditional: bool = False, - base: float = 10000, - scale: float = 1.0, - ): - super().__init__() - self.max_position_embeddings = max_position_embeddings - self.original_base = base - self.dims = dims - self.traditional = traditional - self.scale = scale - - def extra_repr(self): - return f"{self.dims}, traditional={self.traditional}, max_position_embeddings={self.max_position_embeddings}, scaling_factor={self.scaling_factor}" - - def __call__(self, x, offset: int = 0): - seq_len = x.shape[1] + offset - if seq_len > self.max_position_embeddings: - base = self.original_base * ( - (self.scale * seq_len / self.max_position_embeddings) - (self.scale - 1) - ) ** (self.dims / (self.dims - 2)) - else: - base = self.original_base - - return mx.fast.rope( - x, - self.dims, - traditional=self.traditional, - base=base, - scale=self.scale, - offset=offset, - ) - - -class Attention(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - - dim = args.hidden_size - qkv_bias = args.qkv_bias - self.n_heads = n_heads = args.num_attention_heads - self.n_kv_heads = n_kv_heads = args.num_key_value_heads - self.n_kv_groups = n_heads // args.num_key_value_heads - - self.head_dim = head_dim = args.hidden_size // n_heads - self.scale = head_dim**-0.5 - - self.q_proj = nn.Linear(dim, n_heads * head_dim, bias=qkv_bias) - self.k_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=qkv_bias) - self.v_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=qkv_bias) - self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=qkv_bias) - - rope_scale = ( - 1 / args.rope_scaling["factor"] - if args.rope_scaling is not None - and args.rope_scaling["rope_type"] == "linear" - else 2.0 - ) - - self.rope = DynamicNTKScalingRoPE( - head_dim, - max_position_embeddings=args.max_position_embeddings, - traditional=args.rope_traditional, - base=args.rope_theta, - scale=rope_scale, - ) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - B, L, D = x.shape - - queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x) - - # Prepare the queries, keys and values for the attention computation - queries = queries.reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) - keys = keys.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - values = values.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - - if cache is not None: - queries = self.rope(queries, offset=cache.offset) - keys = self.rope(keys, offset=cache.offset) - keys, values = cache.update_and_fetch(keys, values) - else: - queries = self.rope(queries) - keys = self.rope(keys) - - output = scaled_dot_product_attention( - queries, keys, values, cache=cache, scale=self.scale, mask=mask - ) - - output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) - return self.o_proj(output) - - -class MLP(nn.Module): - def __init__(self, dim, hidden_dim, bias): - super().__init__() - self.gate_proj = nn.Linear(dim, hidden_dim, bias=bias) - self.down_proj = nn.Linear(hidden_dim, dim, bias=bias) - self.up_proj = nn.Linear(dim, hidden_dim, bias=bias) - - def __call__(self, x) -> mx.array: - return self.down_proj(nn.silu(self.gate_proj(x)) * self.up_proj(x)) - - -class TransformerBlock(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.self_attn = Attention(args) - self.mlp = MLP(args.hidden_size, args.intermediate_size, args.bias) - self.input_layernorm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - self.post_attention_layernorm = nn.RMSNorm( - args.hidden_size, eps=args.rms_norm_eps - ) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - r = self.self_attn(self.input_layernorm(x), mask, cache) - h = x + r - r = self.mlp(self.post_attention_layernorm(h)) - out = h + r - return out - - -class InternLM2Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - assert args.vocab_size > 0 - self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size) - self.layers = [ - TransformerBlock(args=args) for _ in range(args.num_hidden_layers) - ] - self.norm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - h = self.embed_tokens(inputs) - - if mask is None: - mask = create_attention_mask(h, cache) - - if cache is None: - cache = [None] * len(self.layers) - - for layer, c in zip(self.layers, cache): - h = layer(h, mask, cache=c) - - return self.norm(h) - - -class Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.model_type = args.model_type - self.model = InternLM2Model(args) - if not args.tie_word_embeddings: - self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=False) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - out = self.model(inputs, mask, cache) - if self.args.tie_word_embeddings: - out = self.model.embed_tokens.as_linear(out) - else: - out = self.lm_head(out) - return out - - def sanitize(self, weights): - # Remove unused precomputed rotary freqs - return {k: v for k, v in weights.items() if "attention.rope.inv_freq" not in k} - - @property - def layers(self): - return self.model.layers diff --git a/llms/mlx_lm/models/llama.py b/llms/mlx_lm/models/llama.py deleted file mode 100644 index 117adf0f..00000000 --- a/llms/mlx_lm/models/llama.py +++ /dev/null @@ -1,208 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -from dataclasses import dataclass -from typing import Any, Dict, Optional, Union - -import mlx.core as mx -import mlx.nn as nn - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention -from .rope_utils import initialize_rope - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str - hidden_size: int - num_hidden_layers: int - intermediate_size: int - num_attention_heads: int - rms_norm_eps: float - vocab_size: int - head_dim: Optional[int] = None - max_position_embeddings: Optional[int] = None - num_key_value_heads: Optional[int] = None - attention_bias: bool = False - mlp_bias: bool = False - rope_theta: float = 10000 - rope_traditional: bool = False - rope_scaling: Optional[Dict[str, Union[float, str]]] = None - tie_word_embeddings: bool = True - - def __post_init__(self): - if self.num_key_value_heads is None: - self.num_key_value_heads = self.num_attention_heads - - -class Attention(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - - dim = args.hidden_size - self.n_heads = n_heads = args.num_attention_heads - self.n_kv_heads = n_kv_heads = args.num_key_value_heads - - self.head_dim = head_dim = args.head_dim or args.hidden_size // n_heads - - self.scale = head_dim**-0.5 - if hasattr(args, "attention_bias"): - attention_bias = args.attention_bias - else: - attention_bias = False - - self.q_proj = nn.Linear(dim, n_heads * head_dim, bias=attention_bias) - self.k_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=attention_bias) - self.v_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=attention_bias) - self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=attention_bias) - - self.rope = initialize_rope( - self.head_dim, - args.rope_theta, - args.rope_traditional, - args.rope_scaling, - args.max_position_embeddings, - ) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - B, L, D = x.shape - - queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x) - - # Prepare the queries, keys and values for the attention computation - queries = queries.reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) - keys = keys.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - values = values.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - - if cache is not None: - queries = self.rope(queries, offset=cache.offset) - keys = self.rope(keys, offset=cache.offset) - keys, values = cache.update_and_fetch(keys, values) - else: - queries = self.rope(queries) - keys = self.rope(keys) - - output = scaled_dot_product_attention( - queries, keys, values, cache=cache, scale=self.scale, mask=mask - ) - - output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) - return self.o_proj(output) - - -class MLP(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - - dim = args.hidden_size - hidden_dim = args.intermediate_size - if hasattr(args, "mlp_bias"): - mlp_bias = args.mlp_bias - else: - mlp_bias = False - - self.gate_proj = nn.Linear(dim, hidden_dim, bias=mlp_bias) - self.down_proj = nn.Linear(hidden_dim, dim, bias=mlp_bias) - self.up_proj = nn.Linear(dim, hidden_dim, bias=mlp_bias) - - def __call__(self, x) -> mx.array: - return self.down_proj(nn.silu(self.gate_proj(x)) * self.up_proj(x)) - - -class TransformerBlock(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.num_attention_heads = args.num_attention_heads - self.hidden_size = args.hidden_size - self.self_attn = Attention(args) - self.mlp = MLP(args) - self.input_layernorm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - self.post_attention_layernorm = nn.RMSNorm( - args.hidden_size, eps=args.rms_norm_eps - ) - self.args = args - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - r = self.self_attn(self.input_layernorm(x), mask, cache) - h = x + r - r = self.mlp(self.post_attention_layernorm(h)) - out = h + r - return out - - -class LlamaModel(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.vocab_size = args.vocab_size - self.num_hidden_layers = args.num_hidden_layers - assert self.vocab_size > 0 - self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size) - self.layers = [ - TransformerBlock(args=args) for _ in range(args.num_hidden_layers) - ] - self.norm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - h = self.embed_tokens(inputs) - - if mask is None: - mask = create_attention_mask(h, cache) - - if cache is None: - cache = [None] * len(self.layers) - - for layer, c in zip(self.layers, cache): - h = layer(h, mask, cache=c) - - return self.norm(h) - - -class Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.model_type = args.model_type - self.model = LlamaModel(args) - if not args.tie_word_embeddings: - self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=False) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - out = self.model(inputs, mask, cache) - if self.args.tie_word_embeddings: - out = self.model.embed_tokens.as_linear(out) - else: - out = self.lm_head(out) - return out - - def sanitize(self, weights): - # Remove unused precomputed rotary freqs - weights = { - k: v for k, v in weights.items() if "self_attn.rotary_emb.inv_freq" not in k - } - if self.args.tie_word_embeddings: - weights.pop("lm_head.weight", None) - return weights - - @property - def layers(self): - return self.model.layers diff --git a/llms/mlx_lm/models/mamba.py b/llms/mlx_lm/models/mamba.py deleted file mode 100644 index 93cc616e..00000000 --- a/llms/mlx_lm/models/mamba.py +++ /dev/null @@ -1,242 +0,0 @@ -# Copyright © 2024-2025 Apple Inc. - -import math -from dataclasses import dataclass - -import mlx.core as mx -import mlx.nn as nn - -from .base import BaseModelArgs -from .cache import MambaCache - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str - vocab_size: int - hidden_size: int - intermediate_size: int - state_size: int - num_hidden_layers: int - conv_kernel: int - use_bias: bool - use_conv_bias: bool - time_step_rank: int - tie_word_embeddings: bool = True - use_bcdt_rms: bool = False - mixer_rms_eps: float = 1e-6 - - def __post_init__(self): - if not hasattr(self, "hidden_size") and hasattr(self, "d_model"): - self.hidden_size = self.d_model - if not hasattr(self, "intermediate_size") and hasattr(self, "d_inner"): - self.intermediate_size = self.d_inner - if not hasattr(self, "state_size") and hasattr(self, "d_state"): - self.state_size = self.d_state - if not hasattr(self, "num_hidden_layers") and hasattr(self, "n_layer"): - self.num_hidden_layers = self.n_layer - if not hasattr(self, "num_hidden_layers") and hasattr(self, "n_layers"): - self.num_hidden_layers = self.n_layers - if not hasattr(self, "conv_kernel") and hasattr(self, "d_conv"): - self.conv_kernel = self.d_conv - if not hasattr(self, "use_bias") and hasattr(self, "bias"): - self.use_bias = self.bias - if not hasattr(self, "use_conv_bias") and hasattr(self, "conv_bias"): - self.use_conv_bias = self.conv_bias - - if self.time_step_rank == "auto": - self.time_step_rank = math.ceil(self.hidden_size / 16) - if self.model_type == "falcon_mamba": - self.use_bcdt_rms = True - - -class DepthWiseConv1d(nn.Module): - def __init__(self, channels, kernel_size, bias=True, padding=0): - super().__init__() - self.channels = channels - self.kernel_size = kernel_size - self.padding = padding - self.weight = mx.random.normal((self.channels, kernel_size, 1)) - self.bias = mx.zeros((channels,)) if bias else None - - def __call__(self, x, cache=None): - B, L, C = x.shape - groups, K, _ = self.weight.shape - - if cache is not None: - x = mx.concatenate([cache, x], axis=1) - else: - x = mx.pad(x, [(0, 0), (K - 1, 0), (0, 0)]) - - y = mx.conv_general(x, self.weight, groups=groups) - - if self.bias is not None: - y = y + self.bias - - return y, x[:, -K + 1 :, :] - - -class MambaBlock(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - - self.hidden_size = args.hidden_size - self.ssm_state_size = args.state_size - self.conv_kernel_size = args.conv_kernel - self.intermediate_size = args.intermediate_size - self.time_step_rank = int(args.time_step_rank) - self.use_conv_bias = args.use_conv_bias - self.use_bcdt_rms = args.use_bcdt_rms - if self.use_bcdt_rms: - self.mixer_norm = lambda x: mx.fast.rms_norm( - x, mx.ones(x.shape[-1], x.dtype), eps=args.mixer_rms_eps - ) - - self.in_proj = nn.Linear( - self.hidden_size, self.intermediate_size * 2, bias=args.use_bias - ) - - self.conv1d = DepthWiseConv1d( - channels=self.intermediate_size, - kernel_size=self.conv_kernel_size, - bias=self.use_conv_bias, - padding=self.conv_kernel_size - 1, - ) - - self.x_proj = nn.Linear( - self.intermediate_size, - self.time_step_rank + 2 * self.ssm_state_size, - bias=False, - ) - self.dt_proj = nn.Linear(self.time_step_rank, self.intermediate_size, bias=True) - - A = mx.repeat( - mx.arange(1.0, self.ssm_state_size + 1.0).reshape([1, self.ssm_state_size]), - repeats=self.intermediate_size, - axis=0, - ) - self.A_log = mx.log(A) - self.D = mx.ones([self.intermediate_size]) - - self.out_proj = nn.Linear( - self.intermediate_size, self.hidden_size, bias=args.use_bias - ) - - def ssm_step(self, x, A, state=None): - D = self.D - deltaBC = self.x_proj(x) - delta, B, C = map( - self.mixer_norm if self.use_bcdt_rms else lambda x: x, - mx.split( - deltaBC, - [self.time_step_rank, self.time_step_rank + self.ssm_state_size], - axis=-1, - ), - ) - if self.use_bcdt_rms: - delta, B, C = map(self.mixer_norm, (delta, B, C)) - delta = nn.softplus(self.dt_proj(delta)) - new_state = mx.expand_dims(delta * x, -1) * mx.expand_dims(B, 1) - if state is not None: - new_state += state * mx.exp(mx.expand_dims(delta, -1) * A) - y = (new_state @ mx.expand_dims(C, -1)).squeeze(2) - y = y + D * x - return y, new_state - - def _process_sequence(self, x, conv_cache, state_cache): - B, T, D = x.shape - xz = self.in_proj(x) - x, z = xz.split(indices_or_sections=2, axis=-1) - - conv_out, new_conv_cache = self.conv1d(x, conv_cache) - x = nn.silu(conv_out) - - A = -mx.exp(self.A_log) - - outputs = [] - current_state = state_cache - y = [] - for t in range(T): - y_t, current_state = self.ssm_step(x[:, t], A, current_state) - y.append(y_t) - y = mx.stack(y, axis=1) - z = self.out_proj(nn.silu(z) * y) - return z, (new_conv_cache, current_state) - - def __call__(self, x, cache): - if cache is None: - conv_cache, state_cache = None, None - else: - conv_cache, state_cache = cache[0], cache[1] - - output, (new_conv_cache, new_state_cache) = self._process_sequence( - x, conv_cache, state_cache - ) - - if isinstance(cache, MambaCache): - cache[0] = new_conv_cache - cache[1] = new_state_cache - - return output - - -class ResidualBlock(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.mixer = MambaBlock(args) - self.norm = nn.RMSNorm(args.hidden_size) - - def __call__(self, x: mx.array, cache): - return self.mixer(self.norm(x), cache) + x - - -class Mamba(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.embeddings = nn.Embedding(args.vocab_size, args.hidden_size) - self.layers = [ResidualBlock(args) for _ in range(args.num_hidden_layers)] - self.norm_f = nn.RMSNorm(args.hidden_size) - - def __call__(self, x: mx.array, cache): - x = self.embeddings(x) - if cache is None: - cache = [None] * len(self.layers) - for layer, c in zip(self.layers, cache): - x = layer(x, c) - return self.norm_f(x) - - -class Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.model_type = args.model_type - self.backbone = Mamba(args) - if not args.tie_word_embeddings: - self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=False) - - def __call__(self, inputs: mx.array, cache=None): - B, T = inputs.shape - - x = self.backbone(inputs, cache) - - if self.args.tie_word_embeddings: - logits = self.backbone.embeddings.as_linear(x) - else: - logits = self.lm_head(x) - - return logits - - def sanitize(self, weights): - for k, v in weights.items(): - if "conv1d.weight" in k and v.shape[-1] != 1: - weights[k] = v.moveaxis(2, 1) - return weights - - def make_cache(self): - return [MambaCache() for _ in range(len(self.layers))] - - @property - def layers(self): - return self.backbone.layers diff --git a/llms/mlx_lm/models/minicpm.py b/llms/mlx_lm/models/minicpm.py deleted file mode 100644 index 7140c577..00000000 --- a/llms/mlx_lm/models/minicpm.py +++ /dev/null @@ -1,210 +0,0 @@ -# Copyright © 2023-2025 Apple Inc. - -from dataclasses import dataclass -from typing import Any, Dict, Optional, Tuple, Union - -import mlx.core as mx -import mlx.nn as nn -import numpy as np - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str - hidden_size: int - dim_model_base: int - num_hidden_layers: int - intermediate_size: int - num_attention_heads: int - rms_norm_eps: float - vocab_size: int - num_key_value_heads: int - scale_depth: float - scale_emb: float - rope_theta: float = 1000000.0 - rope_traditional: bool = False - rope_scaling: Optional[Dict[str, Union[str, float]]] = None - tie_word_embeddings: bool = False - - -class MLP(nn.Module): - def __init__(self, args): - super().__init__() - self.gate_proj = nn.Linear(args.hidden_size, args.intermediate_size, bias=False) - self.up_proj = nn.Linear(args.hidden_size, args.intermediate_size, bias=False) - self.down_proj = nn.Linear(args.intermediate_size, args.hidden_size, bias=False) - - def __call__(self, x): - return self.down_proj(nn.silu(self.gate_proj(x)) * self.up_proj(x)) - - -class Attention(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - - self.hidden_size = args.hidden_size - self.num_heads = n_heads = args.num_attention_heads - self.rope_theta = args.rope_theta - - self.head_dim = head_dim = args.hidden_size // n_heads - self.scale = head_dim**-0.5 - - self.num_key_value_heads = args.num_key_value_heads - self.num_key_value_groups = self.num_heads // self.num_key_value_heads - - self.q_proj = nn.Linear( - self.hidden_size, self.num_heads * self.head_dim, bias=False - ) - self.k_proj = nn.Linear( - self.hidden_size, self.num_key_value_heads * self.head_dim, bias=False - ) - self.v_proj = nn.Linear( - self.hidden_size, self.num_key_value_heads * self.head_dim, bias=False - ) - self.o_proj = nn.Linear( - self.num_heads * self.head_dim, self.hidden_size, bias=False - ) - - rope_scale = ( - 1 / args.rope_scaling["factor"] - if args.rope_scaling is not None and args.rope_scaling["type"] == "linear" - else 1 - ) - - self.rope = nn.RoPE( - dims=self.head_dim, - traditional=args.rope_traditional, - base=self.rope_theta, - scale=rope_scale, - ) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ): - B, L, _ = x.shape - - queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x) - - queries = queries.reshape(B, L, self.num_heads, -1).transpose(0, 2, 1, 3) - keys = keys.reshape(B, L, self.num_key_value_heads, -1).transpose(0, 2, 1, 3) - values = values.reshape(B, L, self.num_key_value_heads, -1).transpose( - 0, 2, 1, 3 - ) - - if cache is not None: - queries = self.rope(queries, offset=cache.offset) - keys = self.rope(keys, offset=cache.offset) - keys, values = cache.update_and_fetch(keys, values) - else: - queries = self.rope(queries) - keys = self.rope(keys) - - attn_output = scaled_dot_product_attention( - queries, keys, values, cache=cache, scale=self.scale, mask=mask - ) - - attn_output = attn_output.transpose(0, 2, 1, 3).reshape(B, L, -1) - - return self.o_proj(attn_output) - - -class DecoderLayer(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.hidden_size = args.hidden_size - self.num_hidden_layers = args.num_hidden_layers - - self.self_attn = Attention(args) - self.mlp = MLP(args) - self.input_layernorm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - self.post_attention_layernorm = nn.RMSNorm( - args.hidden_size, eps=args.rms_norm_eps - ) - - self.scale_depth = args.scale_depth - self.num_hidden_layers = args.num_hidden_layers - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - r = self.self_attn(self.input_layernorm(x), mask, cache) - h = x + r * (self.scale_depth / np.sqrt(self.num_hidden_layers)) - r = self.mlp(self.post_attention_layernorm(h)) - out = h + r * (self.scale_depth / np.sqrt(self.num_hidden_layers)) - return out - - -class MiniCPMModel(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.vocab_size = args.vocab_size - assert self.vocab_size > 0 - - self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size) - self.layers = [DecoderLayer(args) for _ in range(args.num_hidden_layers)] - self.norm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - h = self.embed_tokens(inputs) * self.args.scale_emb - - if mask is None: - mask = create_attention_mask(h, cache) - - if cache is None: - cache = [None] * len(self.layers) - - for layer, c in zip(self.layers, cache): - h = layer(h, mask, c) - - return self.norm(h) - - -class Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.model_type = args.model_type - self.model = MiniCPMModel(args) - - if not self.args.tie_word_embeddings: - self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=False) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - out = self.model(inputs, mask, cache) - - if not self.args.tie_word_embeddings: - out = self.lm_head(out / (self.args.hidden_size / self.args.dim_model_base)) - else: - out = out @ self.model.embed_tokens.weight.T - - return out - - def sanitize(self, weights): - if "lm_head.weight" not in weights: - weights["lm_head.weight"] = weights["model.embed_tokens.weight"] - return weights - - @property - def layers(self): - return self.model.layers diff --git a/llms/mlx_lm/models/mixtral.py b/llms/mlx_lm/models/mixtral.py deleted file mode 100644 index 0afd1235..00000000 --- a/llms/mlx_lm/models/mixtral.py +++ /dev/null @@ -1,220 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -import math -from dataclasses import dataclass -from typing import Any, Dict, Optional, Tuple, Union - -import mlx.core as mx -import mlx.nn as nn - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention -from .switch_layers import SwitchGLU - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str - vocab_size: int = 32000 - hidden_size: int = 4096 - intermediate_size: int = 14336 - num_hidden_layers: int = 32 - num_attention_heads: int = 32 - num_experts_per_tok: int = 2 - num_key_value_heads: int = 8 - num_local_experts: int = 8 - rms_norm_eps: float = 1e-5 - rope_theta: float = 1e6 - rope_traditional: bool = False - rope_scaling: Optional[Dict[str, Union[float, str]]] = None - - def __post_init__(self): - if self.num_key_value_heads is None: - self.num_key_value_heads = self.num_attention_heads - - -class MixtralAttention(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.hidden_size = args.hidden_size - self.num_heads = args.num_attention_heads - self.head_dim = self.hidden_size // self.num_heads - self.num_key_value_heads = args.num_key_value_heads - self.rope_theta = args.rope_theta - - self.scale = self.head_dim**-0.5 - - self.q_proj = nn.Linear( - self.hidden_size, self.num_heads * self.head_dim, bias=False - ) - self.k_proj = nn.Linear( - self.hidden_size, self.num_key_value_heads * self.head_dim, bias=False - ) - self.v_proj = nn.Linear( - self.hidden_size, self.num_key_value_heads * self.head_dim, bias=False - ) - self.o_proj = nn.Linear( - self.num_heads * self.head_dim, self.hidden_size, bias=False - ) - - self.rope = nn.RoPE( - self.head_dim, - traditional=args.rope_traditional, - base=args.rope_theta, - ) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - B, L, D = x.shape - - queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x) - - # Prepare the queries, keys and values for the attention computation - queries = queries.reshape(B, L, self.num_heads, -1).transpose(0, 2, 1, 3) - keys = keys.reshape(B, L, self.num_key_value_heads, -1).transpose(0, 2, 1, 3) - values = values.reshape(B, L, self.num_key_value_heads, -1).transpose( - 0, 2, 1, 3 - ) - - if cache is not None: - queries = self.rope(queries, offset=cache.offset) - keys = self.rope(keys, offset=cache.offset) - keys, values = cache.update_and_fetch(keys, values) - else: - queries = self.rope(queries) - keys = self.rope(keys) - - output = scaled_dot_product_attention( - queries, keys, values, cache=cache, scale=self.scale, mask=mask - ) - output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) - return self.o_proj(output) - - -class MixtralSparseMoeBlock(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.hidden_dim = args.hidden_size - self.ffn_dim = args.intermediate_size - self.num_experts = args.num_local_experts - self.num_experts_per_tok = args.num_experts_per_tok - - # gating - self.gate = nn.Linear(self.hidden_dim, self.num_experts, bias=False) - - self.switch_mlp = SwitchGLU(self.hidden_dim, self.ffn_dim, self.num_experts) - - def __call__(self, x: mx.array) -> mx.array: - gates = self.gate(x) - - k = self.num_experts_per_tok - inds = mx.stop_gradient(mx.argpartition(-gates, kth=k - 1, axis=-1)[..., :k]) - scores = mx.take_along_axis(gates, inds, axis=-1) - scores = mx.softmax(scores, axis=-1, precise=True) - - y = self.switch_mlp(x, inds) - y = (y * scores[..., None]).sum(axis=-2) - - return y - - -class MixtralDecoderLayer(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.hidden_size = args.hidden_size - - self.self_attn = MixtralAttention(args) - - self.block_sparse_moe = MixtralSparseMoeBlock(args) - self.input_layernorm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - self.post_attention_layernorm = nn.RMSNorm( - args.hidden_size, eps=args.rms_norm_eps - ) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - r = self.self_attn(self.input_layernorm(x), mask, cache) - h = x + r - r = self.block_sparse_moe(self.post_attention_layernorm(h)) - out = h + r - return out - - -class MixtralModel(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.vocab_size = args.vocab_size - self.num_hidden_layers = args.num_hidden_layers - - self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size) - self.layers = [ - MixtralDecoderLayer(args=args) for _ in range(args.num_hidden_layers) - ] - self.norm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - h = self.embed_tokens(inputs) - - if mask is None: - mask = create_attention_mask(h, cache) - - if cache is None: - cache = [None] * len(self.layers) - - for layer, c in zip(self.layers, cache): - h = layer(h, mask, c) - - return self.norm(h) - - -class Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.model_type = args.model_type - self.model = MixtralModel(args) - self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=False) - self.args = args - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - out = self.model(inputs, mask, cache) - return self.lm_head(out) - - def sanitize(self, weights): - if "model.layers.0.block_sparse_moe.experts.0.w1.weight" not in weights: - return weights - for l in range(self.args.num_hidden_layers): - prefix = f"model.layers.{l}" - for n, m in [("w1", "gate_proj"), ("w2", "down_proj"), ("w3", "up_proj")]: - for k in ["weight", "scales", "biases"]: - if f"{prefix}.block_sparse_moe.experts.0.{n}.{k}" in weights: - to_join = [ - weights.pop( - f"{prefix}.block_sparse_moe.experts.{e}.{n}.{k}" - ) - for e in range(self.args.num_local_experts) - ] - weights[f"{prefix}.block_sparse_moe.switch_mlp.{m}.{k}"] = ( - mx.stack(to_join) - ) - return weights - - @property - def layers(self): - return self.model.layers diff --git a/llms/mlx_lm/models/nemotron.py b/llms/mlx_lm/models/nemotron.py deleted file mode 100644 index eabfac8c..00000000 --- a/llms/mlx_lm/models/nemotron.py +++ /dev/null @@ -1,220 +0,0 @@ -# Copyright © 2024 Apple Inc. - -from dataclasses import dataclass -from functools import partial -from typing import Any, Dict, Optional, Union - -import mlx.core as mx -import mlx.nn as nn - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str - hidden_size: int - hidden_act: str - num_hidden_layers: int - intermediate_size: int - num_attention_heads: int - norm_eps: float - vocab_size: int - num_key_value_heads: int - head_dim: Optional[int] = None - max_position_embeddings: Optional[int] = None - attention_bias: bool = False - mlp_bias: bool = False - partial_rotary_factor: float = 0.5 - rope_theta: float = 10000.0 - rope_traditional: bool = False - rope_scaling: Optional[Dict[str, Union[float, str]]] = None - tie_word_embeddings: bool = False - - def __post_init__(self): - if self.rope_scaling: - if not "factor" in self.rope_scaling: - raise ValueError(f"rope_scaling must contain 'factor'") - rope_type = self.rope_scaling.get("type") or self.rope_scaling.get( - "rope_type" - ) - if rope_type is None: - raise ValueError( - f"rope_scaling must contain either 'type' or 'rope_type'" - ) - if rope_type not in ["linear"]: - raise ValueError("rope_scaling 'type' currently only supports 'linear'") - - -@partial(mx.compile, shapeless=True) -def relu_squared(x): - return nn.relu(x).square() - - -class NemotronLayerNorm1P(nn.LayerNorm): - def __call__(self, x): - weight = self.weight + 1 if "weight" in self else None - bias = self.bias if "bias" in self else None - return mx.fast.layer_norm(x, weight, bias, self.eps) - - -class Attention(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - - dim = args.hidden_size - self.n_heads = n_heads = args.num_attention_heads - self.n_kv_heads = n_kv_heads = args.num_key_value_heads - - self.head_dim = head_dim = args.head_dim or args.hidden_size // n_heads - self.partial_rotary_factor = args.partial_rotary_factor - - self.scale = head_dim**-0.5 - if hasattr(args, "attention_bias"): - attention_bias = args.attention_bias - else: - attention_bias = False - - self.q_proj = nn.Linear(dim, n_heads * head_dim, bias=attention_bias) - self.k_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=attention_bias) - self.v_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=attention_bias) - self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=attention_bias) - - rope_scale = 1.0 - if args.rope_scaling and args.rope_scaling["type"] == "linear": - assert isinstance(args.rope_scaling["factor"], float) - rope_scale = 1 / args.rope_scaling["factor"] - self.rope = nn.RoPE( - int(self.partial_rotary_factor * self.head_dim), - base=args.rope_theta, - scale=rope_scale, - ) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - B, L, _ = x.shape - - queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x) - - # Prepare the queries, keys and values for the attention computation - queries = queries.reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) - keys = keys.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - values = values.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - - if cache is not None: - queries = self.rope(queries, offset=cache.offset) - keys = self.rope(keys, offset=cache.offset) - keys, values = cache.update_and_fetch(keys, values) - else: - queries = self.rope(queries) - keys = self.rope(keys) - - output = scaled_dot_product_attention( - queries, keys, values, cache=cache, scale=self.scale, mask=mask - ) - output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) - return self.o_proj(output) - - -class MLP(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - - dim = args.hidden_size - hidden_dim = args.intermediate_size - mlp_bias = args.mlp_bias - - self.down_proj = nn.Linear(hidden_dim, dim, bias=mlp_bias) - self.up_proj = nn.Linear(dim, hidden_dim, bias=mlp_bias) - - def __call__(self, x) -> mx.array: - return self.down_proj(relu_squared(self.up_proj(x))) - - -class TransformerBlock(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.num_attention_heads = args.num_attention_heads - self.hidden_size = args.hidden_size - self.self_attn = Attention(args) - self.mlp = MLP(args) - self.input_layernorm = NemotronLayerNorm1P(args.hidden_size, eps=args.norm_eps) - self.post_attention_layernorm = NemotronLayerNorm1P( - args.hidden_size, eps=args.norm_eps - ) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - r = self.self_attn(self.input_layernorm(x), mask, cache) - h = x + r - r = self.mlp(self.post_attention_layernorm(h)) - out = h + r - return out - - -class NemotronModel(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.vocab_size = args.vocab_size - self.num_hidden_layers = args.num_hidden_layers - assert self.vocab_size > 0 - self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size) - self.layers = [ - TransformerBlock(args=args) for _ in range(args.num_hidden_layers) - ] - self.norm = NemotronLayerNorm1P(args.hidden_size, eps=args.norm_eps) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - h = self.embed_tokens(inputs) - - if mask is None: - mask = create_attention_mask(h, cache) - - if cache is None: - cache = [None] * len(self.layers) - - for layer, c in zip(self.layers, cache): - h = layer(h, mask, cache=c) - - return self.norm(h) - - -class Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.model_type = args.model_type - self.model = NemotronModel(args) - if not args.tie_word_embeddings: - self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=False) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - out = self.model(inputs, mask, cache) - if self.args.tie_word_embeddings: - out = self.model.embed_tokens.as_linear(out) - else: - out = self.lm_head(out) - return out - - @property - def layers(self): - return self.model.layers diff --git a/llms/mlx_lm/models/olmo.py b/llms/mlx_lm/models/olmo.py deleted file mode 100644 index 4273b0ec..00000000 --- a/llms/mlx_lm/models/olmo.py +++ /dev/null @@ -1,180 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -import sys -from dataclasses import dataclass -from typing import Any, Optional, Tuple - -import mlx.core as mx -import mlx.nn as nn - -from .base import BaseModelArgs, create_attention_mask - -try: - import hf_olmo -except ImportError: - print("To run olmo install ai2-olmo: pip install ai2-olmo") - sys.exit(1) - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str - d_model: int - n_layers: int - mlp_hidden_size: int - n_heads: int - vocab_size: int - embedding_size: int - rope_theta: float = 10000 - rope_traditional: bool = False - mlp_ratio: int = 4 - weight_tying: bool = False - - def __post_init__(self): - self.mlp_hidden_size = ( - self.mlp_hidden_size - if self.mlp_hidden_size is not None - else self.mlp_ratio * self.d_model - ) - - -class TransformerBlock(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.n_heads = args.n_heads - dim = args.d_model - - self.ff_proj = nn.Linear(dim, args.mlp_hidden_size, bias=False) - self.ff_out = nn.Linear(args.mlp_hidden_size // 2, dim, bias=False) - - self.att_norm = nn.LayerNorm(dim, affine=False) - self.ff_norm = nn.LayerNorm(dim, affine=False) - - head_dim = dim // self.n_heads - self.scale = head_dim**-0.5 - - self.att_proj = nn.Linear(dim, 3 * dim, bias=False) - self.attn_out = nn.Linear(dim, dim, bias=False) - - self.rope = nn.RoPE( - head_dim, - traditional=args.rope_traditional, - base=args.rope_theta, - ) - - self.args = args - - def attend( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - B, L, D = x.shape - - queries, keys, values = mx.split(self.att_proj(x), 3, axis=-1) - - # Prepare the queries, keys and values for the attention computation - queries = queries.reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) - keys = keys.reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) - values = values.reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) - - if cache is not None: - queries = self.rope(queries, offset=cache.offset) - keys = self.rope(keys, offset=cache.offset) - keys, values = cache.update_and_fetch(keys, values) - else: - queries = self.rope(queries) - keys = self.rope(keys) - - scores = (queries * self.scale) @ keys.transpose(0, 1, 3, 2) - if mask is not None: - scores += mask - scores = mx.softmax(scores.astype(mx.float32), axis=-1).astype(scores.dtype) - output = (scores @ values).transpose(0, 2, 1, 3).reshape(B, L, -1) - return self.attn_out(output) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - r = self.attend(self.att_norm(x), mask, cache) - h = x + r - - x1, x2 = mx.split(self.ff_proj(self.ff_norm(h)), 2, axis=-1) - - out = h + self.ff_out(nn.silu(x2) * x1) - return out - - -class Transformer(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.n_layers = args.n_layers - self.weight_tying = args.weight_tying - - self.wte = nn.Embedding(args.embedding_size, args.d_model) - self.blocks = [TransformerBlock(args=args) for _ in range(args.n_layers)] - if not self.weight_tying: - self.ff_out = nn.Linear(args.d_model, args.embedding_size, bias=False) - self.norm = nn.LayerNorm(args.d_model, affine=False) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - h = self.wte(inputs) - - if mask is None: - mask = create_attention_mask(h, cache) - - if cache is None: - cache = [None] * len(self.blocks) - - for block, c in zip(self.blocks, cache): - h = block(h, mask, c) - - h = self.norm(h) - - if self.weight_tying: - return self.wte.as_linear(h), cache - - return self.ff_out(h) - - -class OlmoModel(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.transformer = Transformer(args) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - return self.transformer(inputs, mask, cache) - - -class Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.model_type = args.model_type - self.model = OlmoModel(args) - self.args = args - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - return self.model(inputs, mask, cache) - - @property - def layers(self): - return self.model.transformer.blocks diff --git a/llms/mlx_lm/models/olmo2.py b/llms/mlx_lm/models/olmo2.py deleted file mode 100644 index 510ff882..00000000 --- a/llms/mlx_lm/models/olmo2.py +++ /dev/null @@ -1,212 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -from dataclasses import dataclass -from typing import Any, Dict, Optional, Union - -import mlx.core as mx -import mlx.nn as nn - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention -from .rope_utils import initialize_rope - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str - hidden_size: int - num_hidden_layers: int - intermediate_size: int - num_attention_heads: int - rms_norm_eps: float - vocab_size: int - head_dim: Optional[int] = None - max_position_embeddings: Optional[int] = None - num_key_value_heads: Optional[int] = None - attention_bias: bool = False - mlp_bias: bool = False - rope_theta: float = 10000 - rope_traditional: bool = False - rope_scaling: Optional[Dict[str, Union[float, str]]] = None - tie_word_embeddings: bool = True - - def __post_init__(self): - if self.num_key_value_heads is None: - self.num_key_value_heads = self.num_attention_heads - - -class Attention(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - - dim = args.hidden_size - self.n_heads = n_heads = args.num_attention_heads - self.n_kv_heads = n_kv_heads = args.num_key_value_heads - - self.head_dim = head_dim = args.head_dim or args.hidden_size // n_heads - - self.scale = head_dim**-0.5 - if hasattr(args, "attention_bias"): - attention_bias = args.attention_bias - else: - attention_bias = False - - self.q_proj = nn.Linear(dim, n_heads * head_dim, bias=attention_bias) - self.k_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=attention_bias) - self.v_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=attention_bias) - self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=attention_bias) - - self.rope = initialize_rope( - self.head_dim, - args.rope_theta, - args.rope_traditional, - args.rope_scaling, - args.max_position_embeddings, - ) - - self.q_norm = nn.RMSNorm(n_heads * head_dim, args.rms_norm_eps) - self.k_norm = nn.RMSNorm(n_kv_heads * head_dim, args.rms_norm_eps) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - B, L, D = x.shape - - queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x) - queries = self.q_norm(queries) - keys = self.k_norm(keys) - - # Prepare the queries, keys and values for the attention computation - queries = queries.reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) - keys = keys.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - values = values.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - - if cache is not None: - queries = self.rope(queries, offset=cache.offset) - keys = self.rope(keys, offset=cache.offset) - keys, values = cache.update_and_fetch(keys, values) - else: - queries = self.rope(queries) - keys = self.rope(keys) - - output = scaled_dot_product_attention( - queries, keys, values, cache=cache, scale=self.scale, mask=mask - ) - - output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) - return self.o_proj(output) - - -class MLP(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - - dim = args.hidden_size - hidden_dim = args.intermediate_size - if hasattr(args, "mlp_bias"): - mlp_bias = args.mlp_bias - else: - mlp_bias = False - - self.gate_proj = nn.Linear(dim, hidden_dim, bias=mlp_bias) - self.down_proj = nn.Linear(hidden_dim, dim, bias=mlp_bias) - self.up_proj = nn.Linear(dim, hidden_dim, bias=mlp_bias) - - def __call__(self, x) -> mx.array: - return self.down_proj(nn.silu(self.gate_proj(x)) * self.up_proj(x)) - - -class TransformerBlock(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.num_attention_heads = args.num_attention_heads - self.hidden_size = args.hidden_size - self.self_attn = Attention(args) - self.mlp = MLP(args) - self.post_attention_layernorm = nn.RMSNorm( - args.hidden_size, eps=args.rms_norm_eps - ) - self.post_feedforward_layernorm = nn.RMSNorm( - args.hidden_size, eps=args.rms_norm_eps - ) - self.args = args - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - r = self.post_attention_layernorm(self.self_attn(x, mask, cache)) - h = x + r - r = self.post_feedforward_layernorm(self.mlp(h)) - out = h + r - return out - - -class LlamaModel(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.vocab_size = args.vocab_size - self.num_hidden_layers = args.num_hidden_layers - assert self.vocab_size > 0 - self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size) - self.layers = [ - TransformerBlock(args=args) for _ in range(args.num_hidden_layers) - ] - self.norm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - - def __call__( - self, - inputs: mx.array, - cache=None, - mask=None, - ): - h = self.embed_tokens(inputs) - - if mask is None: - mask = create_attention_mask(h, cache) - - if cache is None: - cache = [None] * len(self.layers) - - for layer, c in zip(self.layers, cache): - h = layer(h, mask, cache=c) - - return self.norm(h) - - -class Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.model_type = args.model_type - self.model = LlamaModel(args) - if not args.tie_word_embeddings: - self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=False) - - def __call__( - self, - inputs: mx.array, - cache=None, - mask=None, - ): - out = self.model(inputs, cache, mask) - if self.args.tie_word_embeddings: - out = self.model.embed_tokens.as_linear(out) - else: - out = self.lm_head(out) - return out - - def sanitize(self, weights): - # Remove unused precomputed rotary freqs - return { - k: v for k, v in weights.items() if "self_attn.rotary_emb.inv_freq" not in k - } - - @property - def layers(self): - return self.model.layers diff --git a/llms/mlx_lm/models/olmoe.py b/llms/mlx_lm/models/olmoe.py deleted file mode 100644 index b9c0fc69..00000000 --- a/llms/mlx_lm/models/olmoe.py +++ /dev/null @@ -1,217 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -from dataclasses import dataclass -from typing import Any, Dict, Optional, Union - -import mlx.core as mx -import mlx.nn as nn - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention -from .rope_utils import initialize_rope -from .switch_layers import SwitchGLU - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str - hidden_size: int - num_hidden_layers: int - intermediate_size: int - num_attention_heads: int - rms_norm_eps: float - vocab_size: int - num_experts: int - num_experts_per_tok: int - norm_topk_prob: bool = False - head_dim: Optional[int] = None - max_position_embeddings: Optional[int] = None - num_key_value_heads: Optional[int] = None - attention_bias: bool = False - mlp_bias: bool = False - rope_theta: float = 10000 - rope_traditional: bool = False - rope_scaling: Optional[Dict[str, Union[float, str]]] = None - tie_word_embeddings: bool = True - - def __post_init__(self): - if self.num_key_value_heads is None: - self.num_key_value_heads = self.num_attention_heads - - -class Attention(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - - dim = args.hidden_size - self.n_heads = n_heads = args.num_attention_heads - self.n_kv_heads = n_kv_heads = args.num_key_value_heads - - self.head_dim = head_dim = args.head_dim or args.hidden_size // n_heads - - self.scale = head_dim**-0.5 - - self.q_proj = nn.Linear(dim, n_heads * head_dim, bias=args.attention_bias) - self.k_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=args.attention_bias) - self.v_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=args.attention_bias) - self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=args.attention_bias) - - self.rope = initialize_rope( - self.head_dim, - args.rope_theta, - args.rope_traditional, - args.rope_scaling, - args.max_position_embeddings, - ) - - self.q_norm = nn.RMSNorm(n_heads * head_dim, args.rms_norm_eps) - self.k_norm = nn.RMSNorm(n_kv_heads * head_dim, args.rms_norm_eps) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - B, L, D = x.shape - queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x) - queries = self.q_norm(queries) - keys = self.k_norm(keys) - queries = queries.reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) - keys = keys.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - values = values.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - if cache is not None: - queries = self.rope(queries, offset=cache.offset) - keys = self.rope(keys, offset=cache.offset) - keys, values = cache.update_and_fetch(keys, values) - else: - queries = self.rope(queries) - keys = self.rope(keys) - output = scaled_dot_product_attention( - queries, keys, values, cache=cache, scale=self.scale, mask=mask - ) - output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) - return self.o_proj(output) - - -class OlmoeSparseMoeBlock(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.num_experts = args.num_experts - self.top_k = args.num_experts_per_tok - self.norm_topk_prob = args.norm_topk_prob - - self.gate = nn.Linear(args.hidden_size, self.num_experts, bias=False) - self.switch_mlp = SwitchGLU( - args.hidden_size, - args.intermediate_size, - self.num_experts, - bias=args.mlp_bias, - ) - - def __call__(self, x: mx.array) -> mx.array: - B, L, D = x.shape - x_flat = x.reshape(-1, D) - router_logits = self.gate(x_flat) - routing_weights = mx.softmax(router_logits, axis=1, precise=True) - k = self.top_k - indices = mx.stop_gradient( - mx.argpartition(-routing_weights, kth=k - 1, axis=-1)[..., :k] - ) - scores = mx.take_along_axis(routing_weights, indices, axis=-1) - if self.norm_topk_prob: - scores = scores / scores.sum(axis=-1, keepdims=True) - y = self.switch_mlp(x_flat, indices) - y = (y * scores[..., None]).sum(axis=-2) - return y.reshape(B, L, D) - - -class TransformerBlock(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.self_attn = Attention(args) - self.mlp = OlmoeSparseMoeBlock(args) - self.input_layernorm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - self.post_attention_layernorm = nn.RMSNorm( - args.hidden_size, eps=args.rms_norm_eps - ) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - x = x + self.self_attn(self.input_layernorm(x), mask, cache) - x = x + self.mlp(self.post_attention_layernorm(x)) - return x - - -class OlmoeModel(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.vocab_size = args.vocab_size - self.num_hidden_layers = args.num_hidden_layers - assert self.vocab_size > 0 - self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size) - self.layers = [ - TransformerBlock(args=args) for _ in range(args.num_hidden_layers) - ] - self.norm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - - def __call__( - self, - inputs: mx.array, - cache=None, - mask=None, - ): - h = self.embed_tokens(inputs) - if mask is None: - mask = create_attention_mask(h, cache) - if cache is None: - cache = [None] * len(self.layers) - for layer, c in zip(self.layers, cache): - h = layer(h, mask, cache=c) - return self.norm(h) - - -class Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.model_type = args.model_type - self.model = OlmoeModel(args) - if not args.tie_word_embeddings: - self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=False) - - def __call__( - self, - inputs: mx.array, - cache=None, - mask=None, - ): - out = self.model(inputs, cache, mask) - if self.args.tie_word_embeddings: - out = self.model.embed_tokens.as_linear(out) - else: - out = self.lm_head(out) - return out - - def sanitize(self, weights): - if "model.layers.0.mlp.experts.0.up_proj.weight" not in weights: - return weights - for l in range(self.args.num_hidden_layers): - prefix = f"model.layers.{l}" - for n in ["up_proj", "down_proj", "gate_proj"]: - for k in ["weight", "scales", "biases"]: - if f"{prefix}.mlp.experts.0.{n}.{k}" in weights: - to_join = [ - weights.pop(f"{prefix}.mlp.experts.{e}.{n}.{k}") - for e in range(self.args.num_experts) - ] - weights[f"{prefix}.mlp.switch_mlp.{n}.{k}"] = mx.stack(to_join) - return weights - - @property - def layers(self): - return self.model.layers diff --git a/llms/mlx_lm/models/openelm.py b/llms/mlx_lm/models/openelm.py deleted file mode 100644 index 504fe95c..00000000 --- a/llms/mlx_lm/models/openelm.py +++ /dev/null @@ -1,223 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -from dataclasses import dataclass -from typing import Any, Dict, List, Optional, Tuple, Union - -import mlx.core as mx -import mlx.nn as nn - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str - head_dim: int - num_transformer_layers: int - model_dim: int - vocab_size: int - ffn_dim_divisor: int - num_query_heads: List - num_kv_heads: List - ffn_multipliers: List - ffn_with_glu: bool = True - normalize_qk_projections: bool = True - share_input_output_layers: bool = True - rms_norm_eps: float = 1e-6 - rope_freq_constant: float = 10000 - - -def make_divisible( - v: Union[float, int], - divisor: Optional[int] = 8, - min_value: Optional[Union[float, int]] = None, -) -> Union[float, int]: - """ - This function is taken from the original tf repo. - It ensures that all layers have a channel number that is divisible by the divisor - It can be seen at: - https://github.com/tensorflow/models/blob/2cfc99eff5e5eb729c6793d2f3d03aa1c9be2b15/research/slim/nets/mobilenet/mobilenet.py#L62 - Args: - v: input value - divisor: default to 8 - min_value: minimum divisor value - Returns: - new_v: new divisible value - """ - if min_value is None: - min_value = divisor - new_v = max(min_value, int(v + divisor / 2) // divisor * divisor) - # Make sure that round down does not go down by more than 10%. - if new_v < 0.9 * v: - new_v += divisor - return new_v - - -class Attention(nn.Module): - def __init__(self, args: ModelArgs, layer_id: int): - super().__init__() - self.head_dim = head_dim = args.head_dim - self.layer_id = layer_id - self.model_dim = model_dim = args.model_dim - - self.n_heads = n_heads = args.num_query_heads[layer_id] - self.n_kv_heads = n_kv_heads = args.num_kv_heads[layer_id] - self.scale = head_dim**-0.5 - - op_size = (n_heads + (n_kv_heads * 2)) * head_dim - self.qkv_proj = nn.Linear(model_dim, op_size, bias=False) - self.out_proj = nn.Linear(n_heads * head_dim, model_dim, bias=False) - - self.normalize_qk_projections = args.normalize_qk_projections - - if self.normalize_qk_projections: - self.q_norm = nn.RMSNorm(head_dim, eps=args.rms_norm_eps) - self.k_norm = nn.RMSNorm(head_dim, eps=args.rms_norm_eps) - - self.rope = nn.RoPE(head_dim, traditional=False, base=args.rope_freq_constant) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - B, L, D = x.shape - - qkv = self.qkv_proj(x) - - qkv = qkv.reshape( - B, L, self.n_heads + (self.n_kv_heads * 2), self.head_dim - ).transpose(0, 2, 1, 3) - - queries, keys, values = mx.split( - qkv, [self.n_heads, self.n_heads + self.n_kv_heads], axis=1 - ) - - # Prepare the queries, keys and values for the attention computation - if self.normalize_qk_projections: - queries = self.q_norm(queries) - keys = self.k_norm(keys) - - if cache is not None: - queries = self.rope(queries, offset=cache.offset) - keys = self.rope(keys, offset=cache.offset) - keys, values = cache.update_and_fetch(keys, values) - else: - queries = self.rope(queries) - keys = self.rope(keys) - - output = scaled_dot_product_attention( - queries, keys, values, cache=cache, scale=self.scale, mask=mask - ) - - output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) - - return self.out_proj(output) - - -class MLP(nn.Module): - def __init__(self, args: ModelArgs, layer_id: int): - super().__init__() - self.args = args - dim = args.model_dim - ffn_multiplier = args.ffn_multipliers[layer_id] - - intermediate_dim = int( - make_divisible( - ffn_multiplier * args.model_dim, - divisor=args.ffn_dim_divisor, - ) - ) - - self.proj_1 = nn.Linear(dim, 2 * intermediate_dim, bias=False) - self.proj_2 = nn.Linear(intermediate_dim, dim, bias=False) - - def __call__(self, x) -> mx.array: - x = self.proj_1(x) - gate, x = mx.split(x, 2, axis=-1) - return self.proj_2(nn.silu(gate) * x) - - -class TransformerBlock(nn.Module): - def __init__(self, args: ModelArgs, layer_id: int): - super().__init__() - dim = args.model_dim - self.attn = Attention(args, layer_id=layer_id) - self.ffn = MLP(args, layer_id=layer_id) - self.ffn_norm = nn.RMSNorm(dim, eps=args.rms_norm_eps) - self.attn_norm = nn.RMSNorm(dim, eps=args.rms_norm_eps) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - r = self.attn(self.attn_norm(x), mask, cache) - h = x + r - r = self.ffn(self.ffn_norm(h)) - out = h + r - return out - - -class OpenELMModel(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.vocab_size = args.vocab_size - self.num_transformer_layers = args.num_transformer_layers - assert self.vocab_size > 0 - self.token_embeddings = nn.Embedding(args.vocab_size, args.model_dim) - self.layers = [ - TransformerBlock(args, layer_id=layer_id) - for layer_id in range(self.num_transformer_layers) - ] - self.norm = nn.RMSNorm(args.model_dim, eps=args.rms_norm_eps) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - h = self.token_embeddings(inputs) - - if mask is None: - mask = create_attention_mask(h, cache) - - if cache is None: - cache = [None] * len(self.layers) - - for layer, c in zip(self.layers, cache): - h = layer(h, mask, cache=c) - - return self.norm(h) - - -class Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.model_type = args.model_type - self.transformer = OpenELMModel(args) - if not args.share_input_output_layers: - self.lm_head = nn.Linear(args.model_dim, args.vocab_size, bias=False) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - out = self.transformer(inputs, mask, cache) - if self.args.share_input_output_layers: - out = self.transformer.token_embeddings.as_linear(out) - else: - out = self.lm_head(out) - - return out - - @property - def layers(self): - return self.transformer.layers diff --git a/llms/mlx_lm/models/phi.py b/llms/mlx_lm/models/phi.py deleted file mode 100644 index e9724691..00000000 --- a/llms/mlx_lm/models/phi.py +++ /dev/null @@ -1,179 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -import math -from dataclasses import dataclass -from typing import Tuple - -import mlx.core as mx -import mlx.nn as nn - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str = "phi" - max_position_embeddings: int = 2048 - vocab_size: int = 51200 - hidden_size: int = 2560 - num_attention_heads: int = 32 - num_hidden_layers: int = 32 - num_key_value_heads: int = 32 - partial_rotary_factor: float = 0.4 - intermediate_size: int = 10240 - layer_norm_eps: float = 1e-5 - rope_theta: float = 10000.0 - - def __post_init__(self): - if self.num_key_value_heads is None: - self.num_key_value_heads = self.num_attention_heads - - -class PhiAttention(nn.Module): - def __init__(self, config: ModelArgs): - super().__init__() - - self.hidden_size = config.hidden_size - self.num_heads = config.num_attention_heads - self.head_dim = self.hidden_size // self.num_heads - self.num_key_value_heads = config.num_key_value_heads - self.repeats = self.num_heads // self.num_key_value_heads - self.rope_theta = config.rope_theta - self.partial_rotary_factor = config.partial_rotary_factor - - if (self.head_dim * self.num_heads) != self.hidden_size: - raise ValueError( - f"hidden_size must be divisible by num_heads (got `hidden_size`: {self.hidden_size}" - f" and `num_heads`: {self.num_heads})." - ) - - self.q_proj = nn.Linear( - self.hidden_size, self.num_heads * self.head_dim, bias=True - ) - self.k_proj = nn.Linear( - self.hidden_size, self.num_key_value_heads * self.head_dim, bias=True - ) - self.v_proj = nn.Linear( - self.hidden_size, self.num_key_value_heads * self.head_dim, bias=True - ) - self.dense = nn.Linear( - self.num_heads * self.head_dim, self.hidden_size, bias=True - ) - - self.rope = nn.RoPE( - int(self.partial_rotary_factor * self.head_dim), - traditional=False, - base=self.rope_theta, - ) - - def __call__(self, x, mask=None, cache=None): - queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x) - - # Extract some shapes - B, L, D = queries.shape - n_heads, n_kv_heads = self.num_heads, self.num_key_value_heads - - # Prepare the queries, keys and values for the attention computation - queries = queries.reshape( - B, - L, - n_heads, - -1, - ).moveaxis(1, 2) - keys = keys.reshape(B, L, n_kv_heads, -1).moveaxis(1, 2) - values = values.reshape(B, L, n_kv_heads, -1).moveaxis(1, 2) - - # Add RoPE to the queries and keys and combine them with the cache - if cache is not None: - queries = self.rope(queries, offset=cache.offset) - keys = self.rope(keys, offset=cache.offset) - keys, values = cache.update_and_fetch(keys, values) - else: - queries = self.rope(queries) - keys = self.rope(keys) - - scale = math.sqrt(1 / queries.shape[-1]) - output = scaled_dot_product_attention( - queries.astype(mx.float32), - keys, - values, - cache=cache, - scale=scale, - mask=mask, - ).astype(values.dtype) - - output = output.moveaxis(2, 1).reshape(B, L, -1) - - return self.dense(output) - - -class PhiMLP(nn.Module): - def __init__(self, config: ModelArgs): - super().__init__() - self.fc1 = nn.Linear(config.hidden_size, config.intermediate_size) - self.fc2 = nn.Linear(config.intermediate_size, config.hidden_size) - self.act = nn.GELU(approx="precise") - - def __call__(self, x) -> mx.array: - return self.fc2(self.act(self.fc1(x))) - - -class PhiDecoderLayer(nn.Module): - def __init__(self, config: ModelArgs): - super().__init__() - self.self_attn = PhiAttention(config=config) - self.input_layernorm = nn.LayerNorm( - config.hidden_size, eps=config.layer_norm_eps - ) - self.mlp = PhiMLP(config) - - def __call__(self, x, mask, cache): - h = self.input_layernorm(x) - attn_h = self.self_attn(h, mask, cache) - ff_h = self.mlp(h) - return attn_h + ff_h + x - - -class PhiModel(nn.Module): - def __init__(self, config: ModelArgs): - super().__init__() - self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size) - self.layers = [PhiDecoderLayer(config) for i in range(config.num_hidden_layers)] - self.final_layernorm = nn.LayerNorm( - config.hidden_size, eps=config.layer_norm_eps - ) - - def __call__(self, x, mask, cache): - x = self.embed_tokens(x) - - if mask is None: - mask = create_attention_mask(x, cache) - - if cache is None: - cache = [None] * len(self.layers) - - for layer, c in zip(self.layers, cache): - x = layer(x, mask, c) - return self.final_layernorm(x) - - -class Model(nn.Module): - def __init__(self, config: ModelArgs): - super().__init__() - self.model_type = config.model_type - self.model = PhiModel(config) - self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=True) - self.args = config - - def __call__( - self, - x: mx.array, - mask: mx.array = None, - cache=None, - ) -> mx.array: - y = self.model(x, mask, cache) - return self.lm_head(y) - - @property - def layers(self): - return self.model.layers diff --git a/llms/mlx_lm/models/phi3.py b/llms/mlx_lm/models/phi3.py deleted file mode 100644 index 63e985de..00000000 --- a/llms/mlx_lm/models/phi3.py +++ /dev/null @@ -1,215 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -from dataclasses import dataclass -from typing import Any, Dict, List, Optional, Tuple, Union - -import mlx.core as mx -import mlx.nn as nn - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention -from .su_rope import SuScaledRotaryEmbedding - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str - hidden_size: int - num_hidden_layers: int - intermediate_size: int - num_attention_heads: int - rms_norm_eps: float - vocab_size: int - num_key_value_heads: Optional[int] = None - rope_theta: float = 10000 - rope_traditional: bool = False - rope_scaling: Optional[Dict[str, Union[float, List[float]]]] = None - partial_rotary_factor: float = 1.0 - max_position_embeddings: int = 131072 - original_max_position_embeddings: int = 4096 - tie_word_embeddings: bool = False - - def __post_init__(self): - if self.num_key_value_heads is None: - self.num_key_value_heads = self.num_attention_heads - - if self.rope_scaling: - required_keys = {"long_factor", "type"} - if not all(key in self.rope_scaling for key in required_keys): - raise ValueError(f"rope_scaling must contain keys {required_keys}") - - if self.rope_scaling["type"] not in ["longrope", "su", "linear"]: - print( - "[WARNING] rope_scaling 'type' currently only supports 'linear', 'su', and 'longrope'; setting rope scaling to false." - ) - self.rope_scaling = None - - -class Attention(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - - dim = args.hidden_size - self.n_heads = n_heads = args.num_attention_heads - assert args.num_key_value_heads is not None - self.n_kv_heads = n_kv_heads = args.num_key_value_heads - self.num_hidden_layers = args.num_hidden_layers - - self.head_dim = head_dim = args.hidden_size // n_heads - self.scale = head_dim**-0.5 - - op_size = n_heads * head_dim + 2 * (n_kv_heads * head_dim) - self.qkv_proj = nn.Linear(dim, op_size, bias=False) - self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=False) - - rope_dim = int(head_dim * args.partial_rotary_factor) - if args.rope_scaling and args.rope_scaling["type"] in ["longrope", "su"]: - self.rope = SuScaledRotaryEmbedding( - rope_dim, - base=args.rope_theta, - max_position_embeddings=args.max_position_embeddings, - original_max_position_embeddings=args.original_max_position_embeddings, - short_factor=args.rope_scaling["short_factor"], - long_factor=args.rope_scaling["long_factor"], - ) - else: - rope_scale = 1.0 - if args.rope_scaling and args.rope_scaling["type"] == "linear": - assert isinstance(args.rope_scaling["factor"], float) - rope_scale = 1 / args.rope_scaling["factor"] - self.rope = nn.RoPE( - rope_dim, - traditional=args.rope_traditional, - base=args.rope_theta, - scale=rope_scale, - ) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - B, L, D = x.shape - - qkv = self.qkv_proj(x) - query_pos = self.n_heads * self.head_dim - queries, keys, values = mx.split( - qkv, [query_pos, query_pos + self.n_kv_heads * self.head_dim], axis=-1 - ) - - # Prepare the queries, keys and values for the attention computation - queries = queries.reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) - keys = keys.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - values = values.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - - if cache is not None: - queries = self.rope(queries, offset=cache.offset) - keys = self.rope(keys, offset=cache.offset) - keys, values = cache.update_and_fetch(keys, values) - else: - queries = self.rope(queries) - keys = self.rope(keys) - - output = scaled_dot_product_attention( - queries, keys, values, cache=cache, scale=self.scale, mask=mask - ) - output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) - return self.o_proj(output) - - -class MLP(nn.Module): - def __init__(self, dim, hidden_dim): - super().__init__() - self.gate_up_proj = nn.Linear(dim, 2 * hidden_dim, bias=False) - self.down_proj = nn.Linear(hidden_dim, dim, bias=False) - - def __call__(self, x) -> mx.array: - x = self.gate_up_proj(x) - gate, x = mx.split(x, 2, axis=-1) - return self.down_proj(nn.silu(gate) * x) - - -class TransformerBlock(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.num_attention_heads = args.num_attention_heads - self.hidden_size = args.hidden_size - self.self_attn = Attention(args) - self.mlp = MLP(args.hidden_size, args.intermediate_size) - self.input_layernorm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - self.post_attention_layernorm = nn.RMSNorm( - args.hidden_size, eps=args.rms_norm_eps - ) - self.args = args - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - r = self.self_attn(self.input_layernorm(x), mask, cache) - h = x + r - r = self.mlp(self.post_attention_layernorm(h)) - out = h + r - return out - - -class Phi3Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.vocab_size = args.vocab_size - self.num_hidden_layers = args.num_hidden_layers - assert self.vocab_size > 0 - self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size) - self.layers = [ - TransformerBlock(args=args) for _ in range(args.num_hidden_layers) - ] - self.norm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - h = self.embed_tokens(inputs) - - if mask is None: - mask = create_attention_mask(h, cache) - - if cache is None: - cache = [None] * len(self.layers) - - for layer, c in zip(self.layers, cache): - h = layer(h, mask, c) - - return self.norm(h) - - -class Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.model_type = args.model_type - self.model = Phi3Model(args) - if not args.tie_word_embeddings: - self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=False) - self.args = args - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - out = self.model(inputs, mask, cache) - if self.args.tie_word_embeddings: - out = self.model.embed_tokens.as_linear(out) - else: - out = self.lm_head(out) - return out - - @property - def layers(self): - return self.model.layers diff --git a/llms/mlx_lm/models/phi3small.py b/llms/mlx_lm/models/phi3small.py deleted file mode 100644 index cd566eec..00000000 --- a/llms/mlx_lm/models/phi3small.py +++ /dev/null @@ -1,313 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -import math -from dataclasses import dataclass -from functools import partial -from typing import Any, Optional - -import mlx.core as mx -import mlx.nn as nn - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str - hidden_size: int - dense_attention_every_n_layers: int - ff_intermediate_size: int - gegelu_limit: float - num_hidden_layers: int - num_attention_heads: int - layer_norm_epsilon: float - vocab_size: int - num_key_value_heads: int - mup_attn_multiplier: float = 1.0 - mup_use_scaling: bool = True - mup_embedding_multiplier: float = 10.0 - mup_width_multiplier: float = 8.0 - rope_embedding_base: float = 1000000 - rope_position_scale: float = 1.0 - blocksparse_block_size: int = 64 - blocksparse_num_local_blocks: int = 16 - blocksparse_vert_stride: int = 8 - - -@partial(mx.compile, shapeless=True) -def gegelu_impl(a_gelu, a_linear, limit): - a_gelu = mx.where( - mx.isinf(a_gelu), - a_gelu, - mx.clip(a_gelu, a_min=None, a_max=limit), - ) - a_linear = mx.where( - mx.isinf(a_linear), - a_linear, - mx.clip(a_linear, a_min=-limit, a_max=limit), - ) - out_gelu = a_gelu * mx.sigmoid(1.702 * a_gelu) - return out_gelu * (a_linear + 1.0) - - -def gegelu(x, limit): - a_gelu, a_linear = x[..., ::2], x[..., 1::2] - return gegelu_impl(a_gelu, a_linear, limit) - - -class Attention(nn.Module): - def __init__(self, args: ModelArgs, layer_idx): - super().__init__() - - dim = args.hidden_size - self.n_heads = n_heads = args.num_attention_heads - self.n_kv_heads = n_kv_heads = args.num_key_value_heads - self.n_q_per_kv = n_heads // n_kv_heads - - self.head_dim = head_dim = args.hidden_size // n_heads - - self.query_key_value = nn.Linear( - dim, (self.n_heads + 2 * self.n_kv_heads) * head_dim - ) - self.dense = nn.Linear(dim, dim) - - if args.mup_use_scaling: - norm_factor = head_dim / args.mup_attn_multiplier - else: - norm_factor = math.sqrt(head_dim) - self.scale = 1.0 / norm_factor - - self.rope = nn.RoPE( - head_dim, - traditional=False, - base=args.rope_embedding_base, - scale=args.rope_position_scale, - ) - - if layer_idx % args.dense_attention_every_n_layers == 0: - self.block_sparse = True - self.blocksparse_block_size = args.blocksparse_block_size - if self.blocksparse_block_size not in (32, 64): - raise ValueError( - f"Unsupported block size {self.blocksparse_block_size}" - ) - self.blocksparse_num_local_blocks = args.blocksparse_num_local_blocks - self.blocksparse_vert_stride = args.blocksparse_vert_stride - else: - self.block_sparse = False - - def _block_sparse_mask(self, q_len, kv_len): - vert_stride = self.blocksparse_vert_stride - local_blocks = self.blocksparse_num_local_blocks - block_size = self.blocksparse_block_size - n_heads = self.n_heads - - kv_blocks = (kv_len + block_size - 1) // block_size - q_blocks = (q_len + block_size - 1) // block_size - q_pos = mx.arange(kv_blocks - q_blocks, kv_blocks)[None, :, None] - k_pos = mx.arange(kv_blocks)[None, None] - - mask_vert_strided = ( - mx.arange(kv_blocks)[None, :] + mx.arange(1, n_heads + 1)[:, None] - ) % vert_stride - mask_vert_strided = (mask_vert_strided == 0)[:, None, :] - - block_mask = (q_pos >= k_pos) & ( - (q_pos - k_pos < local_blocks) | mask_vert_strided - ) - block_mask = block_mask.reshape( - self.n_kv_heads, self.n_q_per_kv, *block_mask.shape[-2:] - ) - dense_mask = mx.repeat( - mx.repeat(block_mask, block_size, axis=-1), block_size, axis=-2 - ) - return block_mask, dense_mask[..., -q_len:, :kv_len] - - def _block_sparse_attention(self, queries, keys, values, scale, mask): - queries = scale * queries - B = queries.shape[0] - L = queries.shape[2] - queries = mx.reshape(queries, (B, self.n_kv_heads, self.n_q_per_kv, L, -1)) - keys = mx.expand_dims(keys, 2) - values = mx.expand_dims(values, 2) - - # TODO get rid of dense mask if we have a fill value - block_mask, dense_mask = self._block_sparse_mask(L, keys.shape[-2]) - scores = queries @ mx.swapaxes(keys, -1, -2) - # TODO, uncomment when faster - # scores = mx.block_masked_mm( - # queries, - # mx.swapaxes(keys, -1, -2), - # mask_out=block_mask, - # block_size=self.blocksparse_block_size, - # ) - - if mask is not None: - scores = scores + mask - scores = scores + mx.where( - dense_mask, mx.array(0, scores.dtype), mx.array(-float("inf"), scores.dtype) - ) - scores = mx.softmax(scores, axis=-1, precise=True) - - output = scores @ values - # TODO, uncomment when faster - # output = mx.block_masked_mm( - # scores, values, mask_lhs=block_mask, block_size=self.blocksparse_block_size - # ) - return mx.reshape(output, (B, self.n_heads, L, -1)) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - B, L, D = x.shape - - qkv = self.query_key_value(x) - qkv = qkv.reshape(B, L, -1, self.n_q_per_kv + 2, self.head_dim) - queries = qkv[..., :-2, :].flatten(-3, -2) - keys = qkv[..., -2, :] - values = qkv[..., -1, :] - - # Prepare the queries, keys and values for the attention computation - queries = queries.transpose(0, 2, 1, 3) - keys = keys.transpose(0, 2, 1, 3) - values = values.transpose(0, 2, 1, 3) - - if cache is not None: - queries = self.rope(queries, offset=cache.offset) - keys = self.rope(keys, offset=cache.offset) - keys, values = cache.update_and_fetch(keys, values) - else: - queries = self.rope(queries) - keys = self.rope(keys) - - if self.block_sparse: - output = self._block_sparse_attention( - queries, keys, values, scale=self.scale, mask=mask - ) - else: - output = scaled_dot_product_attention( - queries, keys, values, cache=cache, scale=self.scale, mask=mask - ) - output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) - return self.dense(output) - - -class MLP(nn.Module): - def __init__(self, args): - super().__init__() - dim = args.hidden_size - hidden_dim = args.ff_intermediate_size - self.gegelu_limit = args.gegelu_limit - self.up_proj = nn.Linear(dim, 2 * hidden_dim) - self.down_proj = nn.Linear(hidden_dim, dim) - - def __call__(self, x) -> mx.array: - x = self.up_proj(x) - return self.down_proj(gegelu(x, self.gegelu_limit)) - - -class TransformerBlock(nn.Module): - def __init__(self, args: ModelArgs, layer_idx): - super().__init__() - self.num_attention_heads = args.num_attention_heads - self.hidden_size = args.hidden_size - self.self_attn = Attention(args, layer_idx) - self.mlp = MLP(args) - self.input_layernorm = nn.LayerNorm( - args.hidden_size, eps=args.layer_norm_epsilon - ) - self.post_attention_layernorm = nn.LayerNorm( - args.hidden_size, - eps=args.layer_norm_epsilon, - ) - self.args = args - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - r = self.self_attn(self.input_layernorm(x), mask, cache) - h = x + r - r = self.mlp(self.post_attention_layernorm(h)) - out = h + r - return out - - -class Phi3Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.vocab_size = args.vocab_size - self.num_hidden_layers = args.num_hidden_layers - assert self.vocab_size > 0 - self.mup_embedding_multiplier = args.mup_embedding_multiplier - self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size) - self.layers = [ - TransformerBlock(args=args, layer_idx=l) - for l in range(args.num_hidden_layers) - ] - self.final_layernorm = nn.LayerNorm( - args.hidden_size, eps=args.layer_norm_epsilon - ) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - h = self.embed_tokens(inputs) - if self.mup_embedding_multiplier: - h = self.mup_embedding_multiplier * h - - if mask is None: - mask = create_attention_mask(h, cache) - - if cache is None: - cache = [None] * len(self.layers) - - for layer, c in zip(self.layers, cache): - h = layer(h, mask, c) - - return self.final_layernorm(h) - - -class Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.model_type = args.model_type - self.model = Phi3Model(args) - self.args = args - self.mup_width_multiplier = args.mup_width_multiplier - self._dummy_tokenizer_ids = mx.array( - [100256, 100258, 100259, 100260, 100264, 100265] - + list(range(100267, 100352)) - ) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - out = self.model(inputs, mask, cache) - out = self.model.embed_tokens.as_linear(out) - if self.mup_width_multiplier: - out = out / self.mup_width_multiplier - out[self._dummy_tokenizer_ids] = -float("inf") - return out - - @property - def layers(self): - return self.model.layers - - def sanitize(self, weights): - # Remove unused precomputed rotary freqs - return { - k: v for k, v in weights.items() if "self_attn.rotary_emb.inv_freq" not in k - } diff --git a/llms/mlx_lm/models/phimoe.py b/llms/mlx_lm/models/phimoe.py deleted file mode 100644 index bddcb128..00000000 --- a/llms/mlx_lm/models/phimoe.py +++ /dev/null @@ -1,214 +0,0 @@ -# Copyright © 2024 Apple Inc. -import math -from dataclasses import dataclass -from typing import Dict, List, Optional, Union - -import mlx.core as mx -import mlx.nn as nn - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention -from .su_rope import SuScaledRotaryEmbedding -from .switch_layers import SwitchGLU - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str = "phimoe" - vocab_size: int = 32064 - hidden_size: int = 4096 - intermediate_size: int = 6400 - num_hidden_layers: int = 32 - num_attention_heads: int = 32 - num_key_value_heads: int = 8 - max_position_embeddings: int = 131072 - original_max_position_embeddings: int = 4096 - rms_norm_eps: float = 1e-6 - rope_scaling: Dict[str, Union[float, List[float]]] = None - num_local_experts: int = 16 - num_experts_per_tok: int = 2 - rope_theta: float = 10000.0 - - -class Attention(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - - dim = args.hidden_size - self.n_heads = n_heads = args.num_attention_heads - self.n_kv_heads = n_kv_heads = args.num_key_value_heads - - head_dim = args.hidden_size // n_heads - self.scale = head_dim**-0.5 - - self.q_proj = nn.Linear(dim, n_heads * head_dim, bias=True) - self.k_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=True) - self.v_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=True) - self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=True) - - self.rope = SuScaledRotaryEmbedding( - head_dim, - base=args.rope_theta, - max_position_embeddings=args.max_position_embeddings, - original_max_position_embeddings=args.original_max_position_embeddings, - short_factor=args.rope_scaling["short_factor"], - long_factor=args.rope_scaling["long_factor"], - short_mscale=args.rope_scaling["short_mscale"], - long_mscale=args.rope_scaling["long_mscale"], - ) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache=None, - ) -> mx.array: - B, L, D = x.shape - - queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x) - - # Prepare the queries, keys and values for the attention computation - queries = queries.reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) - keys = keys.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - values = values.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - - if cache is not None: - queries = self.rope(queries, offset=cache.offset) - keys = self.rope(keys, offset=cache.offset) - keys, values = cache.update_and_fetch(keys, values) - else: - queries = self.rope(queries) - keys = self.rope(keys) - - output = scaled_dot_product_attention( - queries, keys, values, cache=cache, scale=self.scale, mask=mask - ) - output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) - return self.o_proj(output) - - -class PhiMoESparseMoeBlock(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.hidden_dim = args.hidden_size - self.ffn_dim = args.intermediate_size - self.num_experts = args.num_local_experts - self.top_k = args.num_experts_per_tok - - self.gate = nn.Linear(self.hidden_dim, self.num_experts, bias=False) - self.switch_mlp = SwitchGLU(self.hidden_dim, self.ffn_dim, self.num_experts) - - def __call__(self, x: mx.array) -> mx.array: - gates = self.gate(x) - - k = self.top_k - inds = mx.stop_gradient(mx.argpartition(-gates, kth=k - 1, axis=-1)[..., :k]) - scores = mx.take_along_axis(gates, inds, axis=-1) - scores = mx.softmax(scores, axis=-1, precise=True) - - y = self.switch_mlp(x, inds) - y = (y * scores[..., None]).sum(axis=-2) - - return y - - -class PhiMoEDecoderLayer(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.hidden_size = args.hidden_size - - self.self_attn = Attention(args) - self.block_sparse_moe = PhiMoESparseMoeBlock(args) - self.input_layernorm = nn.LayerNorm(args.hidden_size, eps=args.rms_norm_eps) - self.post_attention_layernorm = nn.LayerNorm( - args.hidden_size, eps=args.rms_norm_eps - ) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache=None, - ) -> mx.array: - residual = x - hidden_states = self.input_layernorm(x) - hidden_states = self.self_attn(hidden_states, mask=mask, cache=cache) - hidden_states = residual + hidden_states - - residual = hidden_states - hidden_states = self.post_attention_layernorm(hidden_states) - hidden_states = self.block_sparse_moe(hidden_states) - hidden_states = residual + hidden_states - - return hidden_states - - -class PhiMoEModel(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.vocab_size = args.vocab_size - - self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size) - self.layers = [PhiMoEDecoderLayer(args) for _ in range(args.num_hidden_layers)] - self.norm = nn.LayerNorm(args.hidden_size, eps=args.rms_norm_eps) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ) -> mx.array: - h = self.embed_tokens(inputs) - - if mask is None: - mask = create_attention_mask(h, cache) - - if cache is None: - cache = [None] * len(self.layers) - - for layer, c in zip(self.layers, cache): - h = layer(h, mask, c) - - return self.norm(h) - - -class Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.model_type = args.model_type - self.args = args - self.model = PhiMoEModel(args) - self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=True) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - out = self.model(inputs, mask, cache) - return self.lm_head(out) - - def sanitize(self, weights): - if "model.layers.0.block_sparse_moe.experts.0.w1.weight" not in weights: - return weights - for l in range(self.args.num_hidden_layers): - prefix = f"model.layers.{l}" - for n, m in [("w1", "gate_proj"), ("w2", "down_proj"), ("w3", "up_proj")]: - for k in ["weight", "scales", "biases"]: - if f"{prefix}.block_sparse_moe.experts.0.{n}.{k}" in weights: - to_join = [ - weights.pop( - f"{prefix}.block_sparse_moe.experts.{e}.{n}.{k}" - ) - for e in range(self.args.num_local_experts) - ] - weights[f"{prefix}.block_sparse_moe.switch_mlp.{m}.{k}"] = ( - mx.stack(to_join) - ) - - return weights - - @property - def layers(self): - return self.model.layers diff --git a/llms/mlx_lm/models/phixtral.py b/llms/mlx_lm/models/phixtral.py deleted file mode 100644 index 5477c2c0..00000000 --- a/llms/mlx_lm/models/phixtral.py +++ /dev/null @@ -1,202 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -import inspect -import math -from dataclasses import dataclass -from typing import Tuple - -import mlx.core as mx -import mlx.nn as nn - -from .base import create_attention_mask, scaled_dot_product_attention -from .switch_layers import SwitchMLP - - -@dataclass -class ModelArgs: - model_type: str - num_vocab: int = 51200 - model_dim: int = 2560 - num_heads: int = 32 - num_layers: int = 32 - rotary_dim: int = 32 - num_experts_per_tok: int = 2 - num_local_experts: int = 4 - - @classmethod - def from_dict(cls, params): - return cls( - **{ - k: v - for k, v in params.items() - if k in inspect.signature(cls).parameters - } - ) - - -class RoPEAttention(nn.Module): - def __init__(self, dims: int, num_heads: int, rotary_dim: int): - super().__init__() - - self.num_heads = num_heads - - self.rope = nn.RoPE(rotary_dim, traditional=False) - self.Wqkv = nn.Linear(dims, 3 * dims) - self.out_proj = nn.Linear(dims, dims) - - def __call__(self, x, mask=None, cache=None): - qkv = self.Wqkv(x) - queries, keys, values = mx.split(qkv, 3, axis=-1) - - # Extract some shapes - num_heads = self.num_heads - B, L, D = queries.shape - - # Prepare the queries, keys and values for the attention computation - queries = queries.reshape(B, L, num_heads, -1).transpose(0, 2, 1, 3) - keys = keys.reshape(B, L, num_heads, -1).transpose(0, 2, 1, 3) - values = values.reshape(B, L, num_heads, -1).transpose(0, 2, 1, 3) - - # Add RoPE to the queries and keys and combine them with the cache - if cache is not None: - queries = self.rope(queries, offset=cache.offset) - keys = self.rope(keys, offset=cache.offset) - keys, values = cache.update_and_fetch(keys, values) - else: - queries = self.rope(queries) - keys = self.rope(keys) - - queries = queries.astype(mx.float32) - - # Finally perform the attention computation - scale = math.sqrt(1 / queries.shape[-1]) - - output = scaled_dot_product_attention( - queries.astype(mx.float32), - keys, - values, - cache=cache, - scale=scale, - mask=mask, - ).astype(values.dtype) - output = output.moveaxis(2, 1).reshape(B, L, -1) - - return self.out_proj(output) - - -class MOE(nn.Module): - def __init__(self, args: ModelArgs, dim: int, hidden_dim: int): - super().__init__() - self.dim = dim - self.hidden_dim = hidden_dim - self.num_experts = args.num_local_experts - self.num_experts_per_tok = args.num_experts_per_tok - self.switch_mlp = SwitchMLP( - self.dim, self.hidden_dim, self.num_experts, bias=True - ) - self.gate = nn.Linear(args.model_dim, self.num_experts, bias=False) - - def __call__(self, x: mx.array) -> mx.array: - gates = self.gate(x) - - k = self.num_experts_per_tok - inds = mx.stop_gradient(mx.argpartition(-gates, kth=k - 1, axis=-1))[..., :k] - scores = mx.take_along_axis(gates, inds, axis=-1) - scores = mx.softmax(scores, axis=-1, precise=True) - - y = self.switch_mlp(x, inds) - y = (y * scores[..., None]).sum(axis=-2) - - return y - - -class ParallelBlock(nn.Module): - def __init__(self, config: ModelArgs): - super().__init__() - dims = config.model_dim - mlp_dims = dims * 4 - self.mixer = RoPEAttention(dims, config.num_heads, config.rotary_dim) - self.ln = nn.LayerNorm(dims) - self.moe = MOE(config, dims, mlp_dims) - - def __call__(self, x, mask, cache): - h = self.ln(x) - attn_h = self.mixer(h, mask, cache) - ff_h = self.moe(h) - return attn_h + ff_h + x - - -class TransformerDecoder(nn.Module): - def __init__(self, config: ModelArgs): - super().__init__() - self.embd = Embd(config) - self.h = [ParallelBlock(config) for i in range(config.num_layers)] - - def __call__(self, x, mask, cache): - x = self.embd(x) - if cache is None: - cache = [None] * len(self.h) - - for layer, c in zip(self.h, cache): - x = layer(x, mask, c) - return x - - -class Embd(nn.Module): - def __init__(self, config: ModelArgs): - super().__init__() - self.wte = nn.Embedding(config.num_vocab, config.model_dim) - - def __call__(self, x): - return self.wte(x) - - -class OutputHead(nn.Module): - def __init__(self, config: ModelArgs) -> None: - super().__init__() - self.ln = nn.LayerNorm(config.model_dim) - self.linear = nn.Linear(config.model_dim, config.num_vocab) - - def __call__(self, inputs): - return self.linear(self.ln(inputs)) - - -class Model(nn.Module): - def __init__(self, config: ModelArgs): - super().__init__() - self.model_type = config.model_type - self.transformer = TransformerDecoder(config) - self.lm_head = OutputHead(config) - self.args = config - - def __call__( - self, - x: mx.array, - mask: mx.array = None, - cache=None, - ) -> mx.array: - - if mask is None: - mask = create_attention_mask(x, cache) - - y = self.transformer(x, mask, cache) - return self.lm_head(y) - - def sanitize(self, weights): - if "transformer.h.0.moe.mlp.0.fc1.weight" not in weights: - return weights - for l in range(self.args.num_layers): - prefix = f"transformer.h.{l}" - for n in ["fc1", "fc2"]: - for k in ["weight", "scales", "biases", "bias"]: - if f"{prefix}.moe.mlp.0.{n}.{k}" in weights: - to_join = [ - weights.pop(f"{prefix}.moe.mlp.{e}.{n}.{k}") - for e in range(self.args.num_local_experts) - ] - weights[f"{prefix}.moe.switch_mlp.{n}.{k}"] = mx.stack(to_join) - return weights - - @property - def layers(self): - return self.transformer.h diff --git a/llms/mlx_lm/models/plamo.py b/llms/mlx_lm/models/plamo.py deleted file mode 100644 index 9107daad..00000000 --- a/llms/mlx_lm/models/plamo.py +++ /dev/null @@ -1,214 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -from dataclasses import dataclass -from typing import Any, Optional - -import mlx.core as mx -import mlx.nn as nn -import numpy as np - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str - hidden_size: int - num_hidden_layers: int - intermediate_size: int - num_attention_heads: int - rms_norm_eps: float - vocab_size: int - n_shared_head: int = 8 - rope_theta: float = 10000 - rope_traditional: bool = False - - -class Attention(nn.Module): - def __init__(self, config: ModelArgs) -> None: - super().__init__() - self.config = config - self.hidden_size = config.hidden_size - head_dim = self.hidden_size // config.num_attention_heads - - self.q_num_heads = config.num_attention_heads - self.qk_dim = self.v_dim = head_dim - self.k_num_heads = self.v_num_heads = int( - np.ceil(self.q_num_heads / config.n_shared_head) - ) - - self.scale = head_dim**-0.5 - - self.q_proj = nn.Linear( - self.hidden_size, self.q_num_heads * self.qk_dim, bias=False - ) - self.k_proj = nn.Linear( - self.hidden_size, self.k_num_heads * self.qk_dim, bias=False - ) - self.v_proj = nn.Linear( - self.hidden_size, self.v_num_heads * self.v_dim, bias=False - ) - self.o_proj = nn.Linear( - self.q_num_heads * self.v_dim, self.hidden_size, bias=False - ) - self.rotary_emb = nn.RoPE( - head_dim, - traditional=config.rope_traditional, - base=config.rope_theta, - scale=1.0, - ) - - def __call__( - self, - hidden_states: mx.array, - attention_mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - bsz, q_len, _ = hidden_states.shape - - queries = self.q_proj(hidden_states) - keys = self.k_proj(hidden_states) - values = self.v_proj(hidden_states) - - # Prepare the queries, keys and values for the attention computation - queries = queries.reshape(bsz, q_len, self.q_num_heads, self.qk_dim).transpose( - 0, 2, 1, 3 - ) - keys = keys.reshape(bsz, q_len, self.k_num_heads, self.qk_dim).transpose( - 0, 2, 1, 3 - ) - values = values.reshape(bsz, q_len, self.v_num_heads, self.v_dim).transpose( - 0, 2, 1, 3 - ) - - if cache is not None: - queries = self.rotary_emb(queries, offset=cache.offset) - keys = self.rotary_emb(keys, offset=cache.offset) - keys, values = cache.update_and_fetch(keys, values) - else: - queries = self.rotary_emb(queries) - keys = self.rotary_emb(keys) - - keys = mx.tile(keys, [1, self.config.n_shared_head, 1, 1]) - values = mx.tile(values, [1, self.config.n_shared_head, 1, 1]) - - output = scaled_dot_product_attention( - queries, - keys, - values, - cache=cache, - scale=self.scale, - mask=attention_mask, - ) - output = output.transpose(0, 2, 1, 3).reshape(bsz, q_len, -1) - return self.o_proj(output) - - -class MLP(nn.Module): - def __init__(self, config: ModelArgs) -> None: - super().__init__() - self.config = config - self.hidden_size = config.hidden_size - self.intermediate_size = config.intermediate_size - self.gate_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False) - self.up_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False) - self.down_proj = nn.Linear(self.intermediate_size, self.hidden_size, bias=False) - - def __call__(self, x: mx.array) -> mx.array: - return self.down_proj(nn.silu(self.gate_proj(x)) * self.up_proj(x)) # type: ignore - - -class PlamoDecoderLayer(nn.Module): - def __init__(self, config: ModelArgs) -> None: - super().__init__() - self.config = config - self.hidden_size = config.hidden_size - self.self_attn = Attention(config) - self.mlp = MLP(config) - self.norm = nn.RMSNorm(config.hidden_size, eps=config.rms_norm_eps) - - def __call__( - self, - hidden_states: mx.array, - attention_mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ): - # from LlamaDecoder - residual = hidden_states - - hidden_states = self.norm(hidden_states) - - # Self Attention - hidden_states_sa = self.self_attn( - hidden_states=hidden_states, - attention_mask=attention_mask, - cache=cache, - ) - - # Fully Connected - hidden_states_mlp = self.mlp(hidden_states) - - hidden_states = residual + hidden_states_sa + hidden_states_mlp - return hidden_states - - -class PlamoDecoder(nn.Module): - def __init__(self, config: ModelArgs) -> None: - super().__init__() - self.layers = [ - PlamoDecoderLayer(config) for _ in range(config.num_hidden_layers) - ] - - -class PlamoModel(nn.Module): - def __init__(self, config: ModelArgs): - super().__init__() - self.config = config - self.vocab_size = config.vocab_size - - self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size) - self.layers = PlamoDecoder(config) # type: ignore - self.norm = nn.RMSNorm(config.hidden_size, eps=config.rms_norm_eps) - - def __call__( - self, - inputs: mx.array, - cache: Optional[Any] = None, - mask: Optional[mx.array] = None, - ) -> mx.array: - h = self.embed_tokens(inputs) - - if mask is None: - mask = create_attention_mask(h, cache) - - if cache is None: - cache = [None for _ in range(len(self.layers.layers))] - - for layer, c in zip(self.layers.layers, cache): - h = layer(h, mask, cache=c) - - return self.norm(h) - - -class Model(nn.Module): - def __init__(self, args: ModelArgs) -> None: - super().__init__() - self.model_type = args.model_type - self.model = PlamoModel(args) - self.lm_head: nn.Module = nn.Linear( - args.hidden_size, args.vocab_size, bias=False - ) - self.args = args - - def __call__( - self, - inputs: mx.array, - cache: Optional[Any] = None, - mask: Optional[mx.array] = None, - ) -> mx.array: - out = self.model(inputs, cache, mask) - return self.lm_head(out) - - @property - def layers(self): - return self.model.layers.layers diff --git a/llms/mlx_lm/models/plamo2.py b/llms/mlx_lm/models/plamo2.py deleted file mode 100644 index 657fa02e..00000000 --- a/llms/mlx_lm/models/plamo2.py +++ /dev/null @@ -1,608 +0,0 @@ -# Copyright © 2025 Apple Inc. - -import math -from dataclasses import dataclass -from typing import Any, Optional - -import mlx.core as mx -import mlx.nn as nn -from mlx_lm.models.base import BaseModelArgs, create_attention_mask - -from .cache import KVCache, MambaCache - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str = "plamo2" - hidden_size: int = 4096 - num_hidden_layers: int = 32 - rms_norm_eps: float = 1e-6 - tie_word_embeddings: bool = True - num_attention_heads: int = 32 - num_key_value_heads: int = 4 - hidden_size_per_head: int = 128 - max_position_embeddings: int = 2048 - attention_window_size: int = 2048 - full_attention_idx: Optional[list[int]] = None - mamba_d_state: int = 64 - mamba_d_conv: int = 4 - mamba_num_heads: int = 64 - mamba_step: int = 2 - mamba_chunk_size: int = 256 - mamba_enabled: bool = True - intermediate_size: int = 13312 - vocab_size: int = 32000 - - -class RMSNorm(nn.Module): - def __init__( - self, - hidden_size: int, - eps: float = 1e-6, - offset: float = 1.0, - ) -> None: - super().__init__() - self.weight = mx.zeros(hidden_size) - self.variance_epsilon = eps - self.offset = offset - - def __call__(self, hidden_states: mx.array) -> mx.array: - return mx.fast.rms_norm( - hidden_states, self.weight + self.offset, self.variance_epsilon - ) - - -def _rms_norm(hidden_states: mx.array, eps: float) -> mx.array: - input_dtype = hidden_states.dtype - hidden_states = hidden_states.astype(mx.float32) - variance = mx.power(hidden_states, 2).mean(-1, keepdims=True) - hidden_states = hidden_states * mx.rsqrt(variance + eps) - hidden_states = hidden_states.astype(input_dtype) - - return hidden_states - - -def get_initial_dt_bias(num_heads: int) -> mx.array: - dt_min = 0.001 - dt_max = 0.1 - dt = mx.exp( - mx.random.uniform(shape=(num_heads,)) * (math.log(dt_max) - math.log(dt_min)) - + math.log(dt_min) - ) - dt = mx.clip(dt, a_min=1e-4, a_max=None) - inv_dt = dt + mx.log(-mx.expm1(-dt)) - return inv_dt - - -def get_initial_A(num_heads: int) -> mx.array: - A = mx.arange(1, num_heads + 1, dtype=mx.float32) - return mx.log(A) - - -# From: https://github.com/state-spaces/mamba/blob/0cce0fa645f100f00620ddf2333c2b7712abfdec/mamba_ssm/ops/triton/selective_state_update.py#L219 -def selective_state_update_ref( - state, x, dt, A, B, C, D=None, z=None, dt_bias=None, dt_softplus=False -) -> tuple[mx.array, mx.array]: - """ - Argument: - state: (batch, dim, dstate) or (batch, nheads, dim, dstate) - x: (batch, dim) or (batch, nheads, dim) - dt: (batch, dim) or (batch, nheads, dim) - A: (dim, dstate) or (nheads, dim, dstate) - B: (batch, dstate) or (batch, ngroups, dstate) - C: (batch, dstate) or (batch, ngroups, dstate) - D: (dim,) or (nheads, dim) - z: (batch, dim) or (batch, nheads, dim) - dt_bias: (dim,) or (nheads, dim) - Return: - out: (batch, dim) or (batch, nheads, dim) - """ - has_heads = state.ndim > 3 - if state.ndim == 3: - state = mx.expand_dims(state, 1) - if x.ndim == 2: - x = mx.expand_dims(x, 1) - if dt.ndim == 2: - dt = mx.expand_dims(dt, 1) - if A.ndim == 2: - A = mx.expand_dims(A, 0) - if B.ndim == 2: - B = mx.expand_dims(B, 1) - if C.ndim == 2: - C = mx.expand_dims(C, 1) - if D is not None and D.ndim == 1: - D = mx.expand_dims(D, 0) - if z is not None and z.ndim == 2: - z = mx.expand_dims(z, 1) - if dt_bias is not None and dt_bias.ndim == 1: - dt_bias = mx.expand_dims(dt_bias, 0) - batch, nheads, dim, dstate = state.shape - assert x.shape == (batch, nheads, dim) - assert dt.shape == x.shape - assert A.shape == (nheads, dim, dstate) - ngroups = B.shape[1] - assert nheads % ngroups == 0, "nheads must be divisible by ngroups" - assert B.shape == (batch, ngroups, dstate) - assert C.shape == B.shape - if D is not None: - assert D.shape == (nheads, dim) - if z is not None: - assert z.shape == x.shape - if dt_bias is not None: - assert dt_bias.shape == (nheads, dim) - dt = dt + dt_bias - dt = nn.softplus(dt) if dt_softplus else dt - dA = mx.exp(mx.expand_dims(dt, axis=-1) * A) # (batch, nheads, dim, dstate) - B = mx.reshape( - mx.repeat(mx.expand_dims(B, axis=2), nheads // ngroups, 2), - (batch, nheads, dstate), - ) # (batch, nheads, dstate) - C = mx.reshape( - mx.repeat(mx.expand_dims(C, axis=2), nheads // ngroups, 2), - (batch, nheads, dstate), - ) # (batch, nheads, dstate) - dB = mx.expand_dims(dt, axis=-1) * mx.expand_dims( - B, axis=-2 - ) # (batch, nheads, dim, dstate) - state = state * dA + dB * mx.expand_dims(x, axis=-1) # (batch, dim, dstate) - out = mx.einsum("bhdn,bhn->bhd", state.astype(C.dtype), C) - if D is not None: - out += (x * D).astype(out.dtype) - out = (out if z is None else out * nn.silu(z)).astype(x.dtype) - if not has_heads: - out = out.squeeze(1) - return out, state - - -def ssd_update_state( - ssm_state: mx.array, - x: mx.array, - dt: mx.array, - A: mx.array, - B: mx.array, - C: mx.array, - D: mx.array, - z: mx.array, - dt_bias: mx.array, - dt_softplus: bool, -) -> tuple[mx.array, mx.array]: - assert ssm_state.dtype == mx.float32 - dtype = x.dtype - - hidden_size_per_head = x.shape[-1] - d_state = B.shape[-1] - A = mx.broadcast_to( - A[:, None, None], (A.shape[0], hidden_size_per_head, d_state) - ).astype(mx.float32) - dt = mx.broadcast_to( - dt[..., None], (dt.shape[0], dt.shape[1], hidden_size_per_head) - ) - dt_bias = mx.broadcast_to( - dt_bias[:, None], (dt_bias.shape[0], hidden_size_per_head) - ) - D = mx.broadcast_to(D[:, None], (D.shape[0], hidden_size_per_head)) - out, ssm_state = selective_state_update_ref( - ssm_state, - x.astype(dtype), - dt.astype(dtype), - A.astype(mx.float32), - B.astype(dtype), - C.astype(dtype), - D.astype(mx.float32), - z.astype(dtype), - dt_bias.astype(mx.float32), - dt_softplus=dt_softplus, - ) - return out[:, None], ssm_state - - -def ssd_chunk_scan_combined( - x: mx.array, - dt: mx.array, - A: mx.array, - B: mx.array, - C: mx.array, - D: mx.array, - z: mx.array, - dt_bias: mx.array, - dt_softplus: bool, - ssm_state: mx.array, -) -> tuple[mx.array, mx.array]: - assert ssm_state.dtype == mx.float32 - length = x.shape[1] - ys = [] - for i in range(length): - y, ssm_state = ssd_update_state( - ssm_state, - x[:, i], - dt[:, i], - A, - B[:, i], - C[:, i], - D if D.ndim == 1 else D[:, i], - z=z[:, i], - dt_bias=dt_bias, - dt_softplus=dt_softplus, - ) - ys.append(y) - return mx.concatenate(ys, axis=1), ssm_state - - -def causal_conv1d_update(conv_state, x, weight) -> tuple[mx.array, mx.array]: - _, seqlen, dim = x.shape - state_len = conv_state.shape[-2] - x = mx.concatenate([conv_state, x], axis=-2) - conv_state = x[:, -state_len:] - out = mx.conv1d( - x, - weight, - padding=0, - groups=dim, - )[:, -seqlen:] - return nn.silu(out), conv_state - - -class Mamba(nn.Module): - def __init__(self, config: ModelArgs) -> None: - super().__init__() - self.config = config - self.hidden_size = config.hidden_size - self.d_state = config.mamba_d_state - self.d_conv = config.mamba_d_conv - self.chunk_size = config.mamba_chunk_size - self.num_heads = config.mamba_num_heads - self.hidden_size_per_head = config.hidden_size_per_head - - self.intermediate_size = self.num_heads * self.hidden_size_per_head - - self.in_proj = nn.Linear( - self.hidden_size, 2 * self.intermediate_size, bias=False - ) - self.conv1d = nn.Conv1d( - in_channels=self.intermediate_size, - out_channels=self.intermediate_size, - bias=False, - kernel_size=self.d_conv, - groups=self.intermediate_size, - padding=0, - ) - self.dt_dim = max(64, self.hidden_size // 16) - self.bcdt_proj = nn.Linear( - self.intermediate_size, - self.dt_dim + 2 * self.d_state, - bias=False, - ) - self.dt_proj = nn.Linear(self.dt_dim, self.num_heads, bias=False) - - self.dt_bias = get_initial_dt_bias(self.num_heads) - self.A_log = get_initial_A(self.num_heads) - self.D = mx.ones(self.num_heads, dtype=mx.float32) - - self.dt_norm_weight = mx.ones(self.dt_dim) - self.B_norm_weight = mx.ones(self.d_state) - self.C_norm_weight = mx.ones(self.d_state) - - self.out_proj = nn.Linear(self.intermediate_size, self.hidden_size, bias=False) - - def __call__( - self, - hidden_states: mx.array, - mask: Optional[mx.array] = None, - cache=None, - ): - bsize, length, _ = hidden_states.shape - - if cache is not None and cache[0] is not None: - conv_state = cache[0] - ssm_state = cache[1] - else: - conv_state = mx.zeros( - (bsize, self.d_conv - 1, self.intermediate_size), - dtype=hidden_states.dtype, - ) - ssm_state = mx.zeros( - (bsize, self.num_heads, self.hidden_size_per_head, self.d_state), - dtype=mx.float32, - ) - - zx = self.in_proj(hidden_states) - zx = zx.reshape(bsize, length, self.num_heads, -1) - # z: (bsize, length, num_heads, hidden_size_per_head) - # x: (bsize, length, num_heads, hidden_size_per_head) - z, x = mx.split( - zx, - [ - self.hidden_size_per_head, - ], - axis=-1, - ) - - x = x.reshape(bsize, -1, self.num_heads * self.hidden_size_per_head) - x, conv_state = causal_conv1d_update(conv_state, x, self.conv1d.weight) - BCdt = self.bcdt_proj(x) - x = x.reshape(bsize, length, self.num_heads, -1) - B, C, dt = mx.split(BCdt, [self.d_state, self.d_state * 2], axis=-1) - - A = -mx.exp(self.A_log.astype(mx.float32)) # (num_heads,) - dt = mx.fast.rms_norm(dt, self.dt_norm_weight, self.config.rms_norm_eps) - B = mx.fast.rms_norm(B, self.B_norm_weight, self.config.rms_norm_eps) - C = mx.fast.rms_norm(C, self.C_norm_weight, self.config.rms_norm_eps) - - # (bsize, length, num_heads, 1) - dt = self.dt_proj(dt)[..., None] - - out, ssm_state = ssd_chunk_scan_combined( - x, - dt.reshape(bsize, length, -1), - A, - B, - C, - D=self.D, - z=z, - dt_bias=self.dt_bias, - dt_softplus=True, - ssm_state=ssm_state, - ) - - if cache is not None: - cache[0] = conv_state - cache[1] = ssm_state - y = self.out_proj(out.reshape(bsize, length, -1)) - - return y - - -class Attention(nn.Module): - def __init__(self, config: ModelArgs) -> None: - super().__init__() - self.config = config - self.hidden_size = config.hidden_size - head_dim = config.hidden_size_per_head - self.max_position_embeddings = config.max_position_embeddings - self.scale = head_dim**-0.5 - - self.q_num_heads = config.num_attention_heads - self.qk_dim = self.v_dim = head_dim - self.k_num_heads = self.v_num_heads = config.num_key_value_heads - assert self.q_num_heads % self.k_num_heads == 0 - self.n_group = self.q_num_heads // self.k_num_heads - - self.q_proj_dim = self.q_num_heads * self.qk_dim - self.k_proj_dim = self.k_num_heads * self.qk_dim - self.v_proj_dim = self.k_num_heads * self.v_dim - self.qkv_proj = nn.Linear( - self.hidden_size, - self.q_proj_dim + self.k_proj_dim + self.v_proj_dim, - bias=False, - ) - self.o_proj = nn.Linear( - self.q_num_heads * self.v_dim, self.hidden_size, bias=False - ) - - self.q_weight = mx.ones((self.q_num_heads, self.qk_dim)) - self.k_weight = mx.ones((self.k_num_heads, self.qk_dim)) - - self.rope = nn.RoPE(self.qk_dim) - - def __call__( - self, - hidden_states: mx.array, - mask: Optional[mx.array] = None, - cache=None, - ): - B, T, _ = hidden_states.shape - - qkv = self.qkv_proj(hidden_states) - q, k, v = mx.split( - qkv, [self.q_proj_dim, self.q_proj_dim + self.k_proj_dim], axis=-1 - ) - q = q.reshape(B, T, self.q_num_heads, self.qk_dim).transpose(0, 2, 1, 3) - k = k.reshape(B, T, self.k_num_heads, self.qk_dim).transpose(0, 2, 1, 3) - v = v.reshape(B, T, self.v_num_heads, self.v_dim).transpose(0, 2, 1, 3) - - q = _rms_norm(q, 1e-6) * self.q_weight[:, None] - k = _rms_norm(k, 1e-6) * self.k_weight[:, None] - - if cache is not None: - q = self.rope(q, offset=cache.offset) - k = self.rope(k, offset=cache.offset) - k, v = cache.update_and_fetch(k, v) - else: - q = self.rope(q) - k = self.rope(k) - - output = mx.fast.scaled_dot_product_attention( - q, - k, - v, - scale=self.scale, - mask=mask, - ) - output = output.transpose(0, 2, 1, 3).reshape( - B, T, self.q_num_heads * self.v_dim - ) - return self.o_proj(output) - - -class MLP(nn.Module): - def __init__(self, config: ModelArgs) -> None: - super().__init__() - self.config = config - self.hidden_size = config.hidden_size - self.intermediate_size = config.intermediate_size - self.gate_up_proj = nn.Linear( - self.hidden_size, self.intermediate_size * 2, bias=False - ) - self.down_proj = nn.Linear(self.intermediate_size, self.hidden_size, bias=False) - - def __call__(self, x: mx.array) -> mx.array: - h = self.gate_up_proj(x) - hs = mx.split(h, 2, axis=-1) - return self.down_proj(nn.silu(hs[0]) * hs[1]) - - -class PlamoDecoderLayer(nn.Module): - def __init__(self, config: ModelArgs, is_mamba: bool) -> None: - super().__init__() - self.config = config - self.hidden_size = config.hidden_size - self.is_mamba = is_mamba - self.mixer: nn.Module - if is_mamba: - self.mixer = Mamba(config) - else: - self.mixer = Attention(config) - self.mlp = MLP(config) - self.pre_mixer_norm = RMSNorm( - config.hidden_size, eps=config.rms_norm_eps, offset=1.0 - ) - self.post_mixer_norm = RMSNorm( - config.hidden_size, eps=config.rms_norm_eps, offset=1.0 / 5 - ) - self.pre_mlp_norm = RMSNorm( - config.hidden_size, eps=config.rms_norm_eps, offset=1.0 - ) - self.post_mlp_norm = RMSNorm( - config.hidden_size, eps=config.rms_norm_eps, offset=1.0 / (5**1.5) - ) - - def __call__( - self, - hidden_states: mx.array, - mask: Optional[mx.array] = None, - cache=None, - ): - residual = hidden_states - hidden_states = self.pre_mixer_norm(hidden_states) - - hidden_states_sa = self.mixer( - hidden_states=hidden_states, - mask=mask, - cache=cache, - ) - - hidden_states_sa = self.post_mixer_norm(hidden_states_sa) - hidden_states = residual + hidden_states_sa - - residual = hidden_states - hidden_states = self.pre_mlp_norm(hidden_states) - - # Fully Connected - hidden_states_mlp = self.mlp(hidden_states) - - # Residual - hidden_states_mlp = self.post_mlp_norm(hidden_states_mlp) - return residual + hidden_states_mlp - - -def is_mamba(config: ModelArgs, i: int) -> bool: - if not config.mamba_enabled: - return False - assert config.mamba_step > 1 - assert i < config.num_hidden_layers - - if config.num_hidden_layers <= (config.mamba_step // 2): - # use attention in last layer - return i != config.num_hidden_layers - 1 - return (i % config.mamba_step) != (config.mamba_step // 2) - - -class PlamoDecoder(nn.Module): - def __init__(self, config: ModelArgs) -> None: - super().__init__() - - self.layers = [ - PlamoDecoderLayer(config, is_mamba=is_mamba(config, i)) - for i in range(config.num_hidden_layers) - ] - - def __call__(self, x: mx.array, mask: mx.array, cache): - for i, decoder_layer in enumerate(self.layers): - x = decoder_layer( - x, - mask=mask, - cache=cache[i], - ) - return x - - -class PlamoModel(nn.Module): - def __init__(self, config: ModelArgs): - super().__init__() - - self.config = config - self.vocab_size = config.vocab_size - - self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size) - self.layers = PlamoDecoder(config) # type: ignore - self.norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps) - - def __call__( - self, - inputs: mx.array, - mask: Optional[mx.array] = None, - cache=None, - ): - batch_size, seq_length = inputs.shape - - h = self.embed_tokens(inputs) - - if mask is None: - mask = create_attention_mask(h, [cache[1]] if cache is not None else None) - - if cache is None: - cache = [None] * len(self.layers.layers) - - # decoder layers - out = self.layers( - h, - mask, - cache, - ) - - return self.norm(out) - - -class Model(nn.Module): - def __init__(self, config: ModelArgs) -> None: - super().__init__() - self.config = config - self.model_type = config.model_type - self.model = PlamoModel(config) - - self.vocab_size = config.vocab_size - - if not config.tie_word_embeddings: - self.lm_head: nn.Module = nn.Linear( - config.hidden_size, self.vocab_size, bias=False - ) - - def sanitize(self, weights: dict[Any, Any]) -> dict[Any, Any]: - for k, v in weights.items(): - if "conv1d.weight" in k and v.shape[-1] != 1: - weights[k] = v.moveaxis(2, 1) - return weights - - def make_cache(self): - # TODO use RotatingKVCache is not full_attn - # full_attn = self.layer_idx in self.config.full_attention_idx - return [MambaCache() if l.is_mamba else KVCache() for l in self.layers] - - def __call__( - self, inputs: mx.array, mask: Optional[mx.array] = None, cache=None - ) -> mx.array: - outputs = self.model( - inputs=inputs, - mask=None, - cache=cache, - ) - if self.config.tie_word_embeddings: - logits = self.model.embed_tokens.as_linear(outputs) - else: - logits = self.lm_head(outputs) - - return logits - - @property - def layers(self): - return self.model.layers.layers diff --git a/llms/mlx_lm/models/qwen.py b/llms/mlx_lm/models/qwen.py deleted file mode 100644 index ec8a0199..00000000 --- a/llms/mlx_lm/models/qwen.py +++ /dev/null @@ -1,159 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -from dataclasses import dataclass - -import mlx.core as mx -import mlx.nn as nn - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str - hidden_size: int = 2048 - num_attention_heads: int = 16 - num_hidden_layers: int = 24 - kv_channels: int = 128 - max_position_embeddings: int = 8192 - layer_norm_epsilon: float = 1e-6 - intermediate_size: int = 11008 - no_bias: bool = True - vocab_size: int = 151936 - num_key_value_heads = None - - def __post_init__(self): - if self.num_key_value_heads is None: - self.num_key_value_heads = self.num_attention_heads - - -class Attention(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - - hidden_size = args.hidden_size - self.num_attention_heads = args.num_attention_heads - - hidden_size_per_attention_head = hidden_size // self.num_attention_heads - - self.rotary_emb = nn.RoPE(hidden_size_per_attention_head, traditional=False) - - proj_size = args.kv_channels * self.num_attention_heads - - self.c_attn = nn.Linear(hidden_size, proj_size * 3, bias=True) - self.c_proj = nn.Linear(hidden_size, proj_size, bias=not args.no_bias) - - self.scale = hidden_size_per_attention_head**-0.5 - - def __call__(self, x, mask=None, cache=None): - qkv = self.c_attn(x) - - q, k, v = mx.split(qkv, 3, axis=-1) - - B, L, _ = q.shape - - queries = q.reshape(B, L, self.num_attention_heads, -1).transpose(0, 2, 1, 3) - keys = k.reshape(B, L, self.num_attention_heads, -1).transpose(0, 2, 1, 3) - values = v.reshape(B, L, self.num_attention_heads, -1).transpose(0, 2, 1, 3) - - if cache is not None: - queries = self.rotary_emb(queries, offset=cache.offset) - keys = self.rotary_emb(keys, offset=cache.offset) - keys, values = cache.update_and_fetch(keys, values) - else: - queries = self.rotary_emb(queries) - keys = self.rotary_emb(keys) - - output = scaled_dot_product_attention( - queries, keys, values, cache=cache, scale=self.scale, mask=mask - ) - output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) - - return self.c_proj(output) - - -class MLP(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - - self.w1 = nn.Linear( - args.hidden_size, args.intermediate_size // 2, bias=not args.no_bias - ) - self.w2 = nn.Linear( - args.hidden_size, args.intermediate_size // 2, bias=not args.no_bias - ) - self.c_proj = nn.Linear( - args.intermediate_size // 2, args.hidden_size, bias=not args.no_bias - ) - - def __call__(self, x): - a1 = self.w1(x) - a2 = self.w2(x) - return self.c_proj(a1 * nn.silu(a2)) - - -class TransformerBlock(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - - self.ln_1 = nn.RMSNorm(args.hidden_size, eps=args.layer_norm_epsilon) - self.attn = Attention(args) - self.ln_2 = nn.RMSNorm(args.hidden_size, eps=args.layer_norm_epsilon) - self.mlp = MLP(args) - - def __call__(self, x, mask=None, cache=None): - residual = x - x = self.ln_1(x) - x = self.attn(x, mask=mask, cache=cache) - residual = x + residual - x = self.ln_2(residual) - x = self.mlp(x) - x = x + residual - - return x - - -class QwenModel(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.wte = nn.Embedding(args.vocab_size, args.hidden_size) - self.h = [TransformerBlock(args) for _ in range(args.num_hidden_layers)] - self.ln_f = nn.RMSNorm(args.hidden_size, eps=args.layer_norm_epsilon) - - def __call__(self, inputs, mask=None, cache=None): - x = self.wte(inputs) - - if mask is None: - mask = create_attention_mask(x, cache) - - if cache is None: - cache = [None] * len(self.h) - - for layer, c in zip(self.h, cache): - x = layer(x, mask, c) - - return self.ln_f(x) - - -class Model(nn.Module): - def __init__(self, config: ModelArgs): - super().__init__() - self.model_type = config.model_type - self.transformer = QwenModel(config) - self.lm_head = nn.Linear( - config.hidden_size, config.vocab_size, bias=not config.no_bias - ) - self.args = config - - def __call__( - self, - x: mx.array, - mask: mx.array = None, - cache=None, - ) -> mx.array: - y = self.transformer(x, mask, cache) - return self.lm_head(y) - - @property - def layers(self): - return self.transformer.h diff --git a/llms/mlx_lm/models/qwen2.py b/llms/mlx_lm/models/qwen2.py deleted file mode 100644 index 381767c4..00000000 --- a/llms/mlx_lm/models/qwen2.py +++ /dev/null @@ -1,201 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -from dataclasses import dataclass -from typing import Any, Dict, Optional, Union - -import mlx.core as mx -import mlx.nn as nn - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str - hidden_size: int - num_hidden_layers: int - intermediate_size: int - num_attention_heads: int - rms_norm_eps: float - vocab_size: int - num_key_value_heads: Optional[int] = None - rope_theta: float = 1000000 - rope_traditional: bool = False - rope_scaling: Optional[Dict[str, Union[float, str]]] = None - tie_word_embeddings: bool = True - - def __post_init__(self): - if self.num_key_value_heads is None: - self.num_key_value_heads = self.num_attention_heads - - if self.rope_scaling: - required_keys = {"factor", "type"} - if not all(key in self.rope_scaling for key in required_keys): - raise ValueError(f"rope_scaling must contain keys {required_keys}") - - if self.rope_scaling["type"] != "linear": - raise ValueError("rope_scaling 'type' currently only supports 'linear'") - - -class Attention(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - - dim = args.hidden_size - self.n_heads = n_heads = args.num_attention_heads - assert args.num_key_value_heads is not None - self.n_kv_heads = n_kv_heads = args.num_key_value_heads - - head_dim = args.hidden_size // n_heads - self.scale = head_dim**-0.5 - - self.q_proj = nn.Linear(dim, n_heads * head_dim, bias=True) - self.k_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=True) - self.v_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=True) - self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=False) - - rope_scale = ( - 1 / args.rope_scaling["factor"] - if args.rope_scaling is not None and args.rope_scaling["type"] == "linear" - else 1 - ) - self.rope = nn.RoPE( - head_dim, - traditional=args.rope_traditional, - base=args.rope_theta, - scale=rope_scale, - ) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - B, L, D = x.shape - - queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x) - - # Prepare the queries, keys and values for the attention computation - queries = queries.reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) - keys = keys.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - values = values.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - - if cache is not None: - queries = self.rope(queries, offset=cache.offset) - keys = self.rope(keys, offset=cache.offset) - keys, values = cache.update_and_fetch(keys, values) - else: - queries = self.rope(queries) - keys = self.rope(keys) - - output = scaled_dot_product_attention( - queries, keys, values, cache=cache, scale=self.scale, mask=mask - ) - output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) - return self.o_proj(output) - - -class MLP(nn.Module): - def __init__(self, dim, hidden_dim): - super().__init__() - self.gate_proj = nn.Linear(dim, hidden_dim, bias=False) - self.down_proj = nn.Linear(hidden_dim, dim, bias=False) - self.up_proj = nn.Linear(dim, hidden_dim, bias=False) - - def __call__(self, x) -> mx.array: - return self.down_proj(nn.silu(self.gate_proj(x)) * self.up_proj(x)) - - -class TransformerBlock(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.num_attention_heads = args.num_attention_heads - self.hidden_size = args.hidden_size - self.self_attn = Attention(args) - self.mlp = MLP(args.hidden_size, args.intermediate_size) - self.input_layernorm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - self.post_attention_layernorm = nn.RMSNorm( - args.hidden_size, eps=args.rms_norm_eps - ) - self.args = args - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - r = self.self_attn(self.input_layernorm(x), mask, cache) - h = x + r - r = self.mlp(self.post_attention_layernorm(h)) - out = h + r - return out - - -class Qwen2Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.vocab_size = args.vocab_size - self.num_hidden_layers = args.num_hidden_layers - assert self.vocab_size > 0 - self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size) - self.layers = [ - TransformerBlock(args=args) for _ in range(args.num_hidden_layers) - ] - self.norm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - h = self.embed_tokens(inputs) - - if mask is None: - mask = create_attention_mask(h, cache) - - if cache is None: - cache = [None] * len(self.layers) - - for layer, c in zip(self.layers, cache): - h = layer(h, mask, c) - - return self.norm(h) - - -class Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.model_type = args.model_type - self.model = Qwen2Model(args) - if not args.tie_word_embeddings: - self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=False) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - out = self.model(inputs, mask, cache) - if self.args.tie_word_embeddings: - out = self.model.embed_tokens.as_linear(out) - else: - out = self.lm_head(out) - return out - - def sanitize(self, weights): - if self.args.tie_word_embeddings: - weights.pop("lm_head.weight", None) - # Remove unused precomputed rotary freqs - return { - k: v for k, v in weights.items() if "self_attn.rotary_emb.inv_freq" not in k - } - - @property - def layers(self): - return self.model.layers diff --git a/llms/mlx_lm/models/qwen2_moe.py b/llms/mlx_lm/models/qwen2_moe.py deleted file mode 100644 index c6aba622..00000000 --- a/llms/mlx_lm/models/qwen2_moe.py +++ /dev/null @@ -1,241 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -import math -from dataclasses import dataclass -from typing import Any, Dict, Optional, Union - -import mlx.core as mx -import mlx.nn as nn - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention -from .switch_layers import SwitchGLU - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str - hidden_size: int - num_hidden_layers: int - intermediate_size: int - num_attention_heads: int - num_experts_per_tok: int - num_experts: int - moe_intermediate_size: int - shared_expert_intermediate_size: int - rms_norm_eps: float - vocab_size: int - num_key_value_heads: Optional[int] = None - rope_theta: float = 1000000 - rope_traditional: bool = False - rope_scaling: Optional[Dict[str, Union[float, str]]] = None - tie_word_embeddings: bool = False - - def __post_init__(self): - if self.num_key_value_heads is None: - self.num_key_value_heads = self.num_attention_heads - - if self.rope_scaling: - required_keys = {"factor", "type"} - if not all(key in self.rope_scaling for key in required_keys): - raise ValueError(f"rope_scaling must contain keys {required_keys}") - - if self.rope_scaling["type"] != "linear": - raise ValueError("rope_scaling 'type' currently only supports 'linear'") - - -class Attention(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - - dim = args.hidden_size - self.n_heads = n_heads = args.num_attention_heads - assert args.num_key_value_heads is not None - self.n_kv_heads = n_kv_heads = args.num_key_value_heads - - head_dim = args.hidden_size // n_heads - self.scale = head_dim**-0.5 - - self.q_proj = nn.Linear(dim, n_heads * head_dim, bias=True) - self.k_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=True) - self.v_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=True) - self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=False) - - self.rope = nn.RoPE( - head_dim, - traditional=args.rope_traditional, - base=args.rope_theta, - ) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - B, L, D = x.shape - - queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x) - - # Prepare the queries, keys and values for the attention computation - queries = queries.reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) - keys = keys.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - values = values.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - - if cache is not None: - queries = self.rope(queries, offset=cache.offset) - keys = self.rope(keys, offset=cache.offset) - keys, values = cache.update_and_fetch(keys, values) - else: - queries = self.rope(queries) - keys = self.rope(keys) - - output = scaled_dot_product_attention( - queries, keys, values, cache=cache, scale=self.scale, mask=mask - ) - output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) - return self.o_proj(output) - - -class MLP(nn.Module): - def __init__(self, dim, hidden_dim): - super().__init__() - self.gate_proj = nn.Linear(dim, hidden_dim, bias=False) - self.down_proj = nn.Linear(hidden_dim, dim, bias=False) - self.up_proj = nn.Linear(dim, hidden_dim, bias=False) - - def __call__(self, x) -> mx.array: - return self.down_proj(nn.silu(self.gate_proj(x)) * self.up_proj(x)) - - -class Qwen2MoeSparseMoeBlock(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - dim = args.hidden_size - intermediate_size = args.moe_intermediate_size - shared_expert_intermediate_size = args.shared_expert_intermediate_size - - self.num_experts = num_experts = args.num_experts - self.top_k = args.num_experts_per_tok - - self.gate = nn.Linear(dim, num_experts, bias=False) - self.switch_mlp = SwitchGLU(dim, intermediate_size, num_experts) - - self.shared_expert = MLP(dim, shared_expert_intermediate_size) - self.shared_expert_gate = nn.Linear(dim, 1, bias=False) - - def __call__( - self, - x: mx.array, - ): - gates = self.gate(x) - gates = mx.softmax(gates, axis=-1, precise=True) - - k = self.top_k - inds = mx.stop_gradient(mx.argpartition(-gates, kth=k - 1, axis=-1)[..., :k]) - scores = mx.take_along_axis(gates, inds, axis=-1) - - y = self.switch_mlp(x, inds) - y = (y * scores[..., None]).sum(axis=-2) - - shared_expert_output = self.shared_expert(x) - shared_expert_output = ( - mx.sigmoid(self.shared_expert_gate(x)) * shared_expert_output - ) - - return y + shared_expert_output - - -class Qwen2MoeDecoderLayer(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.hidden_size = args.hidden_size - self.self_attn = Attention(args) - self.mlp = Qwen2MoeSparseMoeBlock(args) - - self.input_layernorm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - self.post_attention_layernorm = nn.RMSNorm( - args.hidden_size, eps=args.rms_norm_eps - ) - self.args = args - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - r = self.self_attn(self.input_layernorm(x), mask, cache) - h = x + r - r = self.mlp(self.post_attention_layernorm(h)) - out = h + r - return out - - -class Qwen2MoeModel(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.vocab_size = args.vocab_size - self.num_hidden_layers = args.num_hidden_layers - assert self.vocab_size > 0 - self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size) - self.layers = [ - Qwen2MoeDecoderLayer(args=args) for _ in range(args.num_hidden_layers) - ] - self.norm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - h = self.embed_tokens(inputs) - - if mask is None: - mask = create_attention_mask(h, cache) - - if cache is None: - cache = [None] * len(self.layers) - - for layer, c in zip(self.layers, cache): - h = layer(h, mask, c) - - return self.norm(h) - - -class Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.model_type = args.model_type - self.model = Qwen2MoeModel(args) - self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=False) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - out = self.model(inputs, mask, cache) - return self.lm_head(out) - - def sanitize(self, weights): - if "model.layers.0.mlp.experts.0.up_proj.weight" not in weights: - return weights - for l in range(self.args.num_hidden_layers): - prefix = f"model.layers.{l}" - for n in ["up_proj", "down_proj", "gate_proj"]: - for k in ["weight", "scales", "biases"]: - if f"{prefix}.mlp.experts.0.{n}.{k}" in weights: - to_join = [ - weights.pop(f"{prefix}.mlp.experts.{e}.{n}.{k}") - for e in range(self.args.num_experts) - ] - weights[f"{prefix}.mlp.switch_mlp.{n}.{k}"] = mx.stack(to_join) - return weights - - @property - def layers(self): - return self.model.layers diff --git a/llms/mlx_lm/models/recurrent_gemma.py b/llms/mlx_lm/models/recurrent_gemma.py deleted file mode 100644 index ad07d925..00000000 --- a/llms/mlx_lm/models/recurrent_gemma.py +++ /dev/null @@ -1,458 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -import math -from dataclasses import dataclass -from typing import List, Literal, Optional - -import mlx.core as mx -import mlx.nn as nn - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention -from .cache import MambaCache, RotatingKVCache - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str - attention_bias: bool - conv1d_width: int - hidden_size: int - intermediate_size: int - logits_soft_cap: float - num_attention_heads: int - num_hidden_layers: int - num_key_value_heads: int - rms_norm_eps: float - rope_theta: float - attention_window_size: int - vocab_size: int - embeddings_scale_by_sqrt_dim: bool = True - block_types: Optional[List[str]] = None - _block_types: Optional[List[str]] = None - - def __post_init__(self): - # For some reason these have different names in 2B and 9B - if self.block_types is None: - self.block_types = self._block_types - - -class RMSNorm(nn.Module): - def __init__(self, dims: int, eps: float = 1e-5): - super().__init__() - self.weight = mx.ones((dims,)) - self.eps = eps - - def __call__(self, x): - return mx.fast.rms_norm(x, 1.0 + self.weight, self.eps) - - -def rnn_scan(x, a, h0): - assert x.ndim == 3 - assert a.shape == x.shape[-a.ndim :] - assert a.dtype == x.dtype - - if x.shape[1] == 1: - # Using scan in sampling mode. - if h0 is None: - return x, x[:, 0] - - else: - y = a * h0[:, None] + x - return y, y[:, -1] - - else: - # Using scan in linear mode. - if h0 is not None: - h_t = h0 - else: - B, _, D = x.shape - h_t = mx.zeros((B, D), dtype=x.dtype) - - y = mx.zeros_like(x) - for t in range(x.shape[1]): - h_t = a[:, t] * h_t + x[:, t] - y[:, t] = h_t - - return y, h_t - - -class Conv1d(nn.Module): - def __init__( - self, - channels: int, - kernel_size: int, - ): - super().__init__() - self.weight = mx.zeros((channels, kernel_size, 1)) - self.bias = mx.zeros((channels,)) - - def __call__(self, x, cache=None): - B, L, C = x.shape - groups, K, _ = self.weight.shape - - if cache is not None: - x = mx.concatenate([cache, x], axis=1) - else: - x = mx.pad(x, [(0, 0), (K - 1, 0), (0, 0)]) - - y = mx.conv_general(x, self.weight, groups=groups) - y = y + self.bias - - return y, x[:, -K + 1 :, :] - - -class RGLRU(nn.Module): - """A Real-Gated Linear Recurrent Unit (RG-LRU) layer.""" - - def __init__( - self, - width: int, - num_heads: int, - ): - super().__init__() - self.width = width - self.num_heads = num_heads - self.head_dim = self.width // self.num_heads - - self.recurrent_param = mx.zeros((self.width,)) - - self.input_gate_weight = mx.zeros( - (self.num_heads, self.head_dim, self.head_dim), - ) - self.input_gate_bias = mx.zeros((self.num_heads, self.head_dim)) - - self.recurrent_gate_weight = mx.zeros( - (self.num_heads, self.head_dim, self.head_dim), - ) - self.recurrent_gate_bias = mx.zeros((self.num_heads, self.head_dim)) - - def __call__( - self, - x: mx.array, - cache=None, - ): - B, L, _ = x.shape - - def apply_block_linear(h, w, b): - h = h.reshape((B, L, self.num_heads, self.head_dim)) - h = (h.swapaxes(1, 2) @ w).swapaxes(1, 2) + b - return mx.sigmoid(h.flatten(2, 3)) - - # Gates for x and a. - gate_x = apply_block_linear(x, self.input_gate_weight, self.input_gate_bias) - gate_a = apply_block_linear( - x, self.recurrent_gate_weight, self.recurrent_gate_bias - ) - - # Compute the parameter `A` of the recurrence. - log_a = -8.0 * gate_a * nn.softplus(self.recurrent_param) - a = mx.exp(log_a) - a_square = mx.exp(2 * log_a) - - # Gate the input. - gated_x = x * gate_x - - # Apply gamma normalization to the input. - multiplier = mx.sqrt(1 - a_square) - if cache is None: - multiplier[:, 0, :] = 1.0 - normalized_x = gated_x * multiplier.astype(x.dtype) - - y, last_h = rnn_scan( - x=normalized_x, - a=a, - h0=cache, - ) - - return y, last_h - - -class RecurrentBlock(nn.Module): - - def __init__( - self, - width: int, - num_heads: int, - lru_width: int = None, - conv1d_temporal_width: int = 4, - ): - super().__init__() - self.width = width - self.num_heads = num_heads - self.lru_width = lru_width or width - self.conv1d_temporal_width = conv1d_temporal_width - - self.linear_y = nn.Linear(width, self.lru_width) - self.linear_x = nn.Linear(width, self.lru_width) - self.linear_out = nn.Linear(self.lru_width, width) - self.conv_1d = Conv1d( - channels=self.lru_width, - kernel_size=self.conv1d_temporal_width, - ) - self.rg_lru = RGLRU( - width=self.lru_width, - num_heads=self.num_heads, - ) - - def __call__( - self, - x: mx.array, - cache=None, - mask=None, - ): - # y branch. - y = self.linear_y(x) - y = nn.gelu_approx(y) - - # x branch. - x = self.linear_x(x) - if cache is None: - cache = [None, None] - x, cache[0] = self.conv_1d(x=x, cache=cache[0]) - x, cache[1] = self.rg_lru(x=x, cache=cache[1]) - - x = x * y - x = self.linear_out(x) - - return x - - -class LocalAttentionBlock(nn.Module): - - def __init__( - self, - width: int, - num_heads: int, - window_size: int, - ): - super().__init__() - self.width = width - self.num_heads = num_heads - self.window_size = window_size - self.scale = (width // num_heads) ** (-0.5) - - self.head_dim = self.width // self.num_heads - self.q_proj = nn.Linear(self.width, self.width, bias=False) - self.k_proj = nn.Linear(self.width, self.head_dim, bias=False) - self.v_proj = nn.Linear(self.width, self.head_dim, bias=False) - self.o_proj = nn.Linear(self.width, self.width, bias=True) - self.rope = nn.RoPE( - self.head_dim // 2, - traditional=False, - ) - - def __call__( - self, - x: mx.array, - cache=None, - mask=None, - ): - B, L, D = x.shape - - queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x) - - queries = queries.reshape(B, L, self.num_heads, -1).transpose(0, 2, 1, 3) - keys = keys.reshape(B, L, 1, -1).transpose(0, 2, 1, 3) - values = values.reshape(B, L, 1, -1).transpose(0, 2, 1, 3) - - if cache is not None: - queries = self.rope(queries, offset=cache.offset) - keys = self.rope(keys, offset=cache.offset) - keys, values = cache.update_and_fetch(keys, values) - else: - queries = self.rope(queries) - keys = self.rope(keys) - - output = scaled_dot_product_attention( - queries, keys, values, cache=cache, scale=self.scale, mask=mask - ) - output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) - return self.o_proj(output) - - -class MLPBlock(nn.Module): - - def __init__(self, width: int, expanded_width: int): - super().__init__() - self.up_proj = nn.Linear(width, expanded_width // 2) - self.gate_proj = nn.Linear(width, expanded_width // 2) - self.down_proj = nn.Linear(expanded_width // 2, width) - - def __call__(self, x: mx.array): - gate = self.gate_proj(x) - x = self.up_proj(x) - return self.down_proj(nn.gelu_approx(gate) * x) - - -class ResidualBlock(nn.Module): - - def __init__( - self, - width: int, - mlp_expanded_width: int, - num_heads: int, - attention_window_size: int, - temporal_block_type: str, - lru_width: Optional[int] = None, - conv1d_temporal_width: int = 4, - ): - """Initializes the residual block. - - Args: - width: The width of the block. - mlp_expanded_width: The width of the expansion inside the MLP block. - num_heads: The number of heads for the Attention or the RG-LRU. - attention_window_size: The window size for the local attention block. - temporal_block_type: Either "recurrent" or "attention", specifying the - type of recurrent block to use. - lru_width: The width of the RG-LRU if different from `width`. - conv1d_temporal_width: The width of the temporal convolution. - """ - super().__init__() - self.width = width - self.mlp_expanded_width = mlp_expanded_width - self.num_heads = num_heads - self.attention_window_size = attention_window_size - self.temporal_block_type = temporal_block_type - self.lru_width = lru_width - self.conv1d_temporal_width = conv1d_temporal_width - - self.temporal_pre_norm = RMSNorm(width) - if self.temporal_block_type == "recurrent": - self.temporal_block = RecurrentBlock( - width=self.width, - num_heads=self.num_heads, - lru_width=self.lru_width, - conv1d_temporal_width=self.conv1d_temporal_width, - ) - - else: - self.temporal_block = LocalAttentionBlock( - width=self.width, - num_heads=self.num_heads, - window_size=self.attention_window_size, - ) - - self.channel_pre_norm = RMSNorm(width) - self.mlp_block = MLPBlock( - width=self.width, - expanded_width=self.mlp_expanded_width, - ) - - def __call__( - self, - x: mx.array, - cache=None, - mask=None, - ): - raw_x = x - - inputs_normalized = self.temporal_pre_norm(raw_x) - - x = self.temporal_block(inputs_normalized, cache=cache, mask=mask) - residual = x + raw_x - - x = self.channel_pre_norm(residual) - x = self.mlp_block(x) - - x = x + residual - - return x - - -class Griffin(nn.Module): - def __init__(self, config): - super().__init__() - - self.config = config - self.embed_tokens = nn.Embedding( - config.vocab_size, - config.hidden_size, - ) - - self.scale_by_sqrt_dim = config.embeddings_scale_by_sqrt_dim - block_types = config.block_types - - self.layers = [ - ResidualBlock( - width=config.hidden_size, - mlp_expanded_width=config.intermediate_size, - num_heads=config.num_attention_heads, - attention_window_size=config.attention_window_size, - temporal_block_type=block_types[i % len(block_types)], - lru_width=None, - ) - for i in range(config.num_hidden_layers) - ] - self.final_norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps) - - def __call__( - self, - tokens, - mask: mx.array = None, - cache=None, - ): - x = self.embed_tokens(tokens) - if self.scale_by_sqrt_dim: - x = x * math.sqrt(x.shape[-1]) - - if cache is None: - cache = [None] * len(self.layers) - - for i, block in enumerate(self.layers): - if block.temporal_block_type != "recurrent": - mask_cache = [cache[i]] - - if mask is None: - mask = create_attention_mask(x, mask_cache) - - for i, block in enumerate(self.layers): - x = block(x, mask=mask, cache=cache[i]) - - return self.final_norm(x) - - -class Model(nn.Module): - - def __init__(self, config): - self.args = config - self.model = Griffin(config) - self.model_type = config.model_type - self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False) - - def __call__(self, tokens: mx.array, mask: mx.array = None, cache=None) -> mx.array: - """ - Args: - tokens: Sequence of input tokens. - """ - logits = self.model(tokens, mask=mask, cache=cache) - if "lm_head" in self: - logits = self.lm_head(logits) - else: - logits = self.model.embed_tokens.as_linear(logits) - - c = self.args.logits_soft_cap - if c: - logits = mx.tanh(logits / c) * c - return logits - - @property - def layers(self): - return self.model.layers - - def sanitize(self, weights): - for k, v in weights.items(): - if "conv_1d.weight" in k and v.shape[-1] != 1: - weights[k] = v.moveaxis(2, 1) - if "lm_head.weight" not in weights: - self.pop("lm_head") - return weights - - def make_cache(self): - cache = [] - for layer in self.layers: - if layer.temporal_block_type == "recurrent": - cache.append(MambaCache()) - else: - cache.append(RotatingKVCache(max_size=self.args.attention_window_size)) - return cache diff --git a/llms/mlx_lm/models/rope_utils.py b/llms/mlx_lm/models/rope_utils.py deleted file mode 100644 index d30b432d..00000000 --- a/llms/mlx_lm/models/rope_utils.py +++ /dev/null @@ -1,91 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -from typing import Optional - -import mlx.core as mx -import mlx.nn as nn - - -class Llama3RoPE(nn.Module): - - def __init__( - self, - dims: int, - max_position_embeddings: int = 2048, - traditional: bool = False, - base: float = 10000, - scaling_config: dict = None, - ): - super().__init__() - self.dims = dims - self.max_position_embeddings = max_position_embeddings - self.traditional = traditional - - factor = scaling_config["factor"] - low_freq_factor = scaling_config.get("low_freq_factor", 1.0) - high_freq_factor = scaling_config.get("high_freq_factor", 4.0) - old_context_len = scaling_config.get( - "original_max_position_embeddings", - 8192, - ) - - low_freq_wavelen = old_context_len / low_freq_factor - high_freq_wavelen = old_context_len / high_freq_factor - - freqs = base ** (mx.arange(0, dims, 2) / dims) - wavelens = 2 * mx.pi * freqs - - freqs = mx.where(wavelens > low_freq_wavelen, freqs * factor, freqs) - is_medium_freq = (wavelens > high_freq_wavelen) & (wavelens < low_freq_wavelen) - smooth_factors = (old_context_len / wavelens - low_freq_factor) / ( - high_freq_factor - low_freq_factor - ) - smooth_freqs = freqs / ((1 - smooth_factors) / factor + smooth_factors) - self._freqs = mx.where(is_medium_freq, smooth_freqs, freqs) - - def extra_repr(self): - return ( - f"{self.dims}, traditional={self.traditional}, " - f"max_position_embeddings={self.max_position_embeddings}" - ) - - def __call__(self, x, offset: int = 0): - return mx.fast.rope( - x, - self.dims, - traditional=self.traditional, - base=None, - scale=1.0, - offset=offset, - freqs=self._freqs, - ) - - -def initialize_rope( - dims, - base, - traditional, - scaling_config: Optional[dict] = None, - max_position_embeddings: Optional[int] = None, -): - if scaling_config is not None: - rope_type = scaling_config.get("type") or scaling_config.get( - "rope_type", "default" - ) - else: - rope_type = "default" - - if rope_type in ["default", "linear"]: - scale = 1 / scaling_config["factor"] if rope_type == "linear" else 1.0 - return nn.RoPE(dims, traditional=traditional, base=base, scale=scale) - - elif rope_type == "llama3": - return Llama3RoPE( - dims=dims, - max_position_embeddings=max_position_embeddings, - traditional=traditional, - base=base, - scaling_config=scaling_config, - ) - else: - raise ValueError(f"Unsupported RoPE type {rope_type}") diff --git a/llms/mlx_lm/models/stablelm.py b/llms/mlx_lm/models/stablelm.py deleted file mode 100644 index 0bbc2ca4..00000000 --- a/llms/mlx_lm/models/stablelm.py +++ /dev/null @@ -1,211 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -import math -from dataclasses import dataclass - -import mlx.core as mx -import mlx.nn as nn - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str - vocab_size: int - hidden_size: int - num_attention_heads: int - num_hidden_layers: int - num_key_value_heads: int - intermediate_size: int - rope_theta: float - use_qkv_bias: bool - partial_rotary_factor: float - layer_norm_eps: float - use_parallel_residual: bool = False - qk_layernorm: bool = False - - -class LayerNormPerHead(nn.Module): - - def __init__(self, head_dim, num_heads, eps): - super().__init__() - self.norms = [ - nn.LayerNorm(head_dim, eps=eps, bias=False) for _ in range(num_heads) - ] - self.eps = eps - - def __call__(self, x): - w = mx.stack([n.weight for n in self.norms]) - return w * mx.fast.layer_norm(x, None, None, self.eps) - - -class Attention(nn.Module): - def __init__(self, config: ModelArgs): - super().__init__() - - self.hidden_size = config.hidden_size - self.num_heads = config.num_attention_heads - self.head_dim = self.hidden_size // self.num_heads - self.num_key_value_heads = config.num_key_value_heads - self.rope_theta = config.rope_theta - self.partial_rotary_factor = config.partial_rotary_factor - - if (self.head_dim * self.num_heads) != self.hidden_size: - raise ValueError( - f"hidden_size must be divisible by num_heads (got `hidden_size`: {self.hidden_size}" - f" and `num_heads`: {self.num_heads})." - ) - - self.q_proj = nn.Linear( - self.hidden_size, self.num_heads * self.head_dim, bias=config.use_qkv_bias - ) - self.k_proj = nn.Linear( - self.hidden_size, - self.num_key_value_heads * self.head_dim, - bias=config.use_qkv_bias, - ) - self.v_proj = nn.Linear( - self.hidden_size, - self.num_key_value_heads * self.head_dim, - bias=config.use_qkv_bias, - ) - self.o_proj = nn.Linear( - self.num_heads * self.head_dim, self.hidden_size, bias=False - ) - - self.rope = nn.RoPE( - int(self.partial_rotary_factor * self.head_dim), - traditional=False, - base=self.rope_theta, - ) - - self.qk_layernorm = config.qk_layernorm - if self.qk_layernorm: - self.q_layernorm = LayerNormPerHead( - self.head_dim, self.num_heads, eps=config.layer_norm_eps - ) - self.k_layernorm = LayerNormPerHead( - self.head_dim, self.num_key_value_heads, eps=config.layer_norm_eps - ) - - def __call__(self, x, mask=None, cache=None): - queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x) - - # Extract some shapes - B, L, D = queries.shape - - queries = queries.reshape(B, L, self.num_heads, -1) - keys = keys.reshape(B, L, self.num_key_value_heads, -1) - if self.qk_layernorm: - queries = self.q_layernorm(queries) - keys = self.k_layernorm(keys) - queries = queries.transpose(0, 2, 1, 3) - keys = keys.transpose(0, 2, 1, 3) - values = values.reshape(B, L, self.num_key_value_heads, -1).transpose( - 0, 2, 1, 3 - ) - - # Add RoPE to the queries and keys and combine them with the cache - if cache is not None: - queries = self.rope(queries, offset=cache.offset) - keys = self.rope(keys, offset=cache.offset) - keys, values = cache.update_and_fetch(keys, values) - else: - queries = self.rope(queries) - keys = self.rope(keys) - - queries = queries.astype(mx.float32) - keys = keys.astype(mx.float32) - - # Finally perform the attention computation - scale = math.sqrt(1 / queries.shape[-1]) - output = scaled_dot_product_attention( - queries, keys, values, cache=cache, scale=scale, mask=mask - ).astype(values.dtype) - output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) - return self.o_proj(output) - - -class MLP(nn.Module): - def __init__(self, dim, hidden_dim): - super().__init__() - self.gate_proj = nn.Linear(dim, hidden_dim, bias=False) - self.down_proj = nn.Linear(hidden_dim, dim, bias=False) - self.up_proj = nn.Linear(dim, hidden_dim, bias=False) - - def __call__(self, x) -> mx.array: - return self.down_proj(nn.silu(self.gate_proj(x)) * self.up_proj(x)) - - -class DecoderLayer(nn.Module): - def __init__(self, config: ModelArgs): - super().__init__() - self.self_attn = Attention(config=config) - self.mlp = MLP(config.hidden_size, config.intermediate_size) - self.input_layernorm = nn.LayerNorm( - config.hidden_size, - eps=config.layer_norm_eps, - ) - self.use_parallel_residual = config.use_parallel_residual - if not self.use_parallel_residual: - self.post_attention_layernorm = nn.LayerNorm( - config.hidden_size, - eps=config.layer_norm_eps, - ) - - def __call__(self, x, mask, cache): - h = self.input_layernorm(x) - r = self.self_attn(h, mask, cache) - - if self.use_parallel_residual: - out = x + r + self.mlp(h) - else: - h = x + r - r = self.mlp(self.post_attention_layernorm(h)) - out = h + r - return out - - -class StableLM(nn.Module): - def __init__(self, config: ModelArgs): - super().__init__() - self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size) - self.layers = [DecoderLayer(config) for i in range(config.num_hidden_layers)] - self.norm = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps) - - def __call__(self, x, mask, cache): - x = self.embed_tokens(x) - if cache is None: - cache = [None] * len(self.layers) - - for layer, c in zip(self.layers, cache): - x = layer(x, mask, cache=c) - - return self.norm(x) - - -class Model(nn.Module): - def __init__(self, config: ModelArgs): - super().__init__() - self.model_type = config.model_type - self.model = StableLM(config) - self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False) - self.args = config - - def __call__( - self, - x: mx.array, - mask: mx.array = None, - cache=None, - ) -> mx.array: - - if mask is None: - mask = create_attention_mask(x, cache) - - y = self.model(x, mask, cache) - return self.lm_head(y) - - @property - def layers(self): - return self.model.layers diff --git a/llms/mlx_lm/models/starcoder2.py b/llms/mlx_lm/models/starcoder2.py deleted file mode 100644 index 71c397f6..00000000 --- a/llms/mlx_lm/models/starcoder2.py +++ /dev/null @@ -1,169 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -from dataclasses import dataclass -from typing import Any, Optional - -import mlx.core as mx -import mlx.nn as nn - -from .base import BaseModelArgs, create_attention_mask, scaled_dot_product_attention - - -@dataclass -class ModelArgs(BaseModelArgs): - model_type: str - hidden_size: int - num_hidden_layers: int - intermediate_size: int - num_attention_heads: int - num_key_value_heads: int - norm_epsilon: float = 1e-5 - vocab_size: int = 49152 - rope_theta: float = 100000 - tie_word_embeddings: bool = True - - -class Attention(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - - dim = args.hidden_size - self.n_heads = n_heads = args.num_attention_heads - self.n_kv_heads = n_kv_heads = args.num_key_value_heads - - head_dim = args.hidden_size // args.num_attention_heads - self.scale = head_dim**-0.5 - - self.q_proj = nn.Linear(dim, n_heads * head_dim, bias=True) - self.k_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=True) - self.v_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=True) - self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=True) - self.rope = nn.RoPE(head_dim, traditional=False, base=args.rope_theta) - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - B, L, D = x.shape - - queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x) - - # Prepare the queries, keys and values for the attention computation - queries = queries.reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3) - keys = keys.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - values = values.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3) - - if cache is not None: - queries = self.rope(queries, offset=cache.offset) - keys = self.rope(keys, offset=cache.offset) - keys, values = cache.update_and_fetch(keys, values) - else: - queries = self.rope(queries) - keys = self.rope(keys) - - output = scaled_dot_product_attention( - queries, keys, values, cache=cache, scale=self.scale, mask=mask - ) - - output = output.transpose(0, 2, 1, 3).reshape(B, L, -1) - return self.o_proj(output) - - -class MLP(nn.Module): - def __init__(self, dim, hidden_dim): - super().__init__() - self.c_fc = nn.Linear(dim, hidden_dim, bias=True) - self.c_proj = nn.Linear(hidden_dim, dim, bias=True) - - def __call__(self, x): - return self.c_proj(nn.gelu(self.c_fc(x))) - - -class TransformerBlock(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.hidden_size = args.hidden_size - self.n_heads = args.num_attention_heads - - self.self_attn = Attention(args) - self.mlp = MLP(args.hidden_size, args.intermediate_size) - self.input_layernorm = nn.LayerNorm(args.hidden_size, eps=args.norm_epsilon) - self.post_attention_layernorm = nn.LayerNorm( - args.hidden_size, eps=args.norm_epsilon - ) - self.args = args - - def __call__( - self, - x: mx.array, - mask: Optional[mx.array] = None, - cache: Optional[Any] = None, - ) -> mx.array: - r = self.self_attn(self.input_layernorm(x), mask, cache) - h = x + r - r = self.mlp(self.post_attention_layernorm(h)) - out = h + r - return out - - -class Starcoder2Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.vocab_size = args.vocab_size - self.num_hidden_layers = args.num_hidden_layers - assert self.vocab_size > 0 - self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size) - self.layers = [ - TransformerBlock(args=args) for _ in range(args.num_hidden_layers) - ] - self.norm = nn.LayerNorm(args.hidden_size, eps=args.norm_epsilon) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - h = self.embed_tokens(inputs) - - if mask is None: - mask = create_attention_mask(h, cache) - - if cache is None: - cache = [None] * len(self.layers) - - for layer, c in zip(self.layers, cache): - h = layer(h, mask, c) - - return self.norm(h) - - -class Model(nn.Module): - def __init__(self, args: ModelArgs): - super().__init__() - self.args = args - self.model_type = args.model_type - self.model = Starcoder2Model(args) - if not args.tie_word_embeddings: - self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=False) - - def __call__( - self, - inputs: mx.array, - mask: mx.array = None, - cache=None, - ): - out = self.model(inputs, mask, cache) - if self.args.tie_word_embeddings: - out = self.model.embed_tokens.as_linear(out) - else: - out = self.lm_head(out) - return out - - @property - def layers(self): - return self.model.layers diff --git a/llms/mlx_lm/models/su_rope.py b/llms/mlx_lm/models/su_rope.py deleted file mode 100644 index 6340c77b..00000000 --- a/llms/mlx_lm/models/su_rope.py +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -import math -from typing import List, Union - -import mlx.core as mx -import mlx.nn as nn - - -class SuScaledRotaryEmbedding(nn.Module): - def __init__( - self, - dims: int, - base: float = 10000.0, - max_position_embeddings: int = 131072, - original_max_position_embeddings: int = 4096, - short_factor: Union[List[float], float] = 1.0, - long_factor: Union[List[float], float] = 1.0, - short_mscale: float = None, - long_mscale: float = None, - ): - """ - Phi3Su Scaled Rotary Embedding layer for Phi-3 models. - - Args: - dims (int): The feature dimensions to be rotated. - base (int, optional): Base for the exponential scaling. - max_position_embeddings (int, optional): The maximum sequence - length that this model was trained with. This is used to determine - the size of the original RoPE embeddings when using long scaling. - Default: ``131072``. - original_max_position_embeddings (int, optional): The maximum - sequence length that this model was trained with. This is used to - determine the size of the original RoPE embeddings when using long - scaling. Default: ``4096``. - short_factor (float or list[float], optional): List of scaling - factors for sequences of length lesser than - ``original_max_position_embeddings``. Default: ``1.0``. - long_factor (float or list[float], optional): List of scaling - factors for sequences of length greater than - ``original_max_position_embeddings``. Default: ``1.0``. - short_mscale (float, optional): Scale the input prior to embedding. - long_mscale (float, optional): Scale the input prior to embedding. - """ - super().__init__() - freqs = base ** (mx.arange(0, dims, 2, dtype=mx.float32) / dims) - self._freqs = mx.array(long_factor, dtype=mx.float32) * freqs - self.original_max_position_embeddings = original_max_position_embeddings - self.scale = long_mscale or math.sqrt( - 1 - + math.log(max_position_embeddings / original_max_position_embeddings) - / math.log(original_max_position_embeddings) - ) - self.dim = dims - - def __call__(self, x, offset: int = 0): - x[..., : self.dim] = self.scale * x[..., : self.dim] - return mx.fast.rope( - x, - self.dim, - traditional=False, - base=None, - scale=1.0, - offset=offset, - freqs=self._freqs, - ) diff --git a/llms/mlx_lm/models/switch_layers.py b/llms/mlx_lm/models/switch_layers.py deleted file mode 100644 index 4a157473..00000000 --- a/llms/mlx_lm/models/switch_layers.py +++ /dev/null @@ -1,167 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -import math - -import mlx.core as mx -import mlx.nn as nn - - -class QuantizedSwitchLinear(nn.Module): - def __init__( - self, - input_dims: int, - output_dims: int, - num_experts: int, - bias: bool = True, - group_size: int = 64, - bits: int = 4, - ): - super().__init__() - - scale = math.sqrt(1 / input_dims) - self.weight, self.scales, self.biases = mx.quantize( - mx.random.uniform( - low=-scale, - high=scale, - shape=(num_experts, output_dims, input_dims), - ), - group_size=group_size, - bits=bits, - ) - - if bias: - self.bias = mx.zeros((num_experts, output_dims)) - - self.group_size = group_size - self.bits = bits - - # Freeze this model's parameters - self.freeze() - - def unfreeze(self, *args, **kwargs): - """Wrap unfreeze so that we unfreeze any layers we might contain but - our parameters will remain frozen.""" - super().unfreeze(*args, **kwargs) - self.freeze(recurse=False) - - @property - def input_dims(self): - return self.scales.shape[2] * self.group_size - - @property - def output_dims(self): - return self.weight.shape[1] - - @property - def num_experts(self): - return self.weight.shape[0] - - def __call__(self, x, indices): - x = mx.gather_qmm( - x, - self["weight"], - self["scales"], - self["biases"], - rhs_indices=indices, - transpose=True, - group_size=self.group_size, - bits=self.bits, - ) - if "bias" in self: - x = x + mx.expand_dims(self["bias"][indices], -2) - return x - - -class SwitchLinear(nn.Module): - def __init__( - self, input_dims: int, output_dims: int, num_experts: int, bias: bool = True - ): - super().__init__() - scale = math.sqrt(1 / input_dims) - self.weight = mx.random.uniform( - low=-scale, - high=scale, - shape=(num_experts, output_dims, input_dims), - ) - - if bias: - self.bias = mx.zeros((num_experts, output_dims)) - - @property - def input_dims(self): - return self.weight.shape[2] - - @property - def output_dims(self): - return self.weight.shape[1] - - @property - def num_experts(self): - return self.weight.shape[0] - - def __call__(self, x, indices): - x = mx.gather_mm(x, self["weight"].swapaxes(-1, -2), rhs_indices=indices) - if "bias" in self: - x = x + mx.expand_dims(self["bias"][indices], -2) - return x - - def to_quantized(self, group_size: int = 64, bits: int = 4): - num_experts, output_dims, input_dims = self.weight.shape - ql = QuantizedSwitchLinear( - input_dims, output_dims, num_experts, False, group_size, bits - ) - ql.weight, ql.scales, ql.biases = mx.quantize(self.weight, group_size, bits) - if "bias" in self: - ql.bias = self.bias - return ql - - -class SwitchGLU(nn.Module): - def __init__( - self, - input_dims: int, - hidden_dims: int, - num_experts: int, - activation=nn.silu, - bias: bool = False, - ): - super().__init__() - - self.gate_proj = SwitchLinear(input_dims, hidden_dims, num_experts, bias=bias) - self.up_proj = SwitchLinear(input_dims, hidden_dims, num_experts, bias=bias) - self.down_proj = SwitchLinear(hidden_dims, input_dims, num_experts, bias=bias) - self.activation = activation - - def __call__(self, x, indices) -> mx.array: - x = mx.expand_dims(x, (-2, -3)) - - x_up = self.up_proj(x, indices) - x_gate = self.gate_proj(x, indices) - x = self.down_proj(self.activation(x_gate) * x_up, indices) - - return x.squeeze(-2) - - -class SwitchMLP(nn.Module): - def __init__( - self, - input_dims: int, - hidden_dims: int, - num_experts: int, - activation=nn.gelu_approx, - bias: bool = False, - ): - super().__init__() - - self.fc1 = SwitchLinear(input_dims, hidden_dims, num_experts, bias=bias) - self.fc2 = SwitchLinear(hidden_dims, input_dims, num_experts, bias=bias) - self.activation = activation - - def __call__(self, x, indices) -> mx.array: - x = mx.expand_dims(x, (-2, -3)) - - x = self.fc1(x, indices) - x = self.activation(x) - x = self.fc2(x, indices) - - return x.squeeze(-2) diff --git a/llms/mlx_lm/py.typed b/llms/mlx_lm/py.typed deleted file mode 100644 index 8b137891..00000000 --- a/llms/mlx_lm/py.typed +++ /dev/null @@ -1 +0,0 @@ - diff --git a/llms/mlx_lm/requirements.txt b/llms/mlx_lm/requirements.txt deleted file mode 100644 index 72e1ef89..00000000 --- a/llms/mlx_lm/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -mlx>=0.22.0 -numpy -transformers[sentencepiece]>=4.39.3 -protobuf -pyyaml -jinja2 diff --git a/llms/mlx_lm/sample_utils.py b/llms/mlx_lm/sample_utils.py deleted file mode 100644 index efc5b556..00000000 --- a/llms/mlx_lm/sample_utils.py +++ /dev/null @@ -1,257 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -import math -from functools import partial -from typing import Callable, Dict, Optional - -import mlx.core as mx - - -def make_sampler( - temp: float = 0.0, - top_p: float = 0.0, - min_p: float = 0.0, - min_tokens_to_keep: int = 1, - top_k: int = -1, -) -> Callable[mx.array, mx.array]: - """ - Make a sampler function for use with ``generate_step``. - - Args: - temp (float): The temperature for sampling, if 0 the argmax is used. - Default: ``0``. - top_p (float, optional): Nulceus sampling, higher means model considers - more less likely words. - min_p (float, optional): The minimum value (scaled by the top token's - probability) that a token probability must have to be considered. - min_tokens_to_keep (int, optional): Minimum number of tokens that cannot - be filtered by min_p sampling. - top_k (int, optional): The top k tokens ranked by probability to constrain - the sampling to. - - Returns: - Callable[mx.array, mx.array]: - A sampler which takes log-probabilities and returns tokens. - """ - if temp == 0: - return lambda x: mx.argmax(x, axis=-1) - - # Create sampler chain - sampling_methods = [] - if top_k > 0: - sampling_methods.append(lambda x: apply_top_k(x, top_k)) - if top_p > 0 and top_p < 1.0: - sampling_methods.append(lambda x: apply_top_p(x, top_p)) - if min_p != 0.0: - sampling_methods.append(lambda x: apply_min_p(x, min_p, min_tokens_to_keep)) - - # Apply the sampling methods - def sampler(logits): - for method in sampling_methods: - logits = method(logits) - - # Return the sampled token - return categorical_sampling(logits, temp) - - return sampler - - -def make_logits_processors( - logit_bias: Optional[Dict[int, float]] = None, - repetition_penalty: Optional[float] = None, - repetition_context_size: Optional[int] = 20, -): - """ - Make logits processors for use with ``generate_step``. - - Args: - repetition_penalty (float, optional): The penalty factor for repeating - tokens. - repetition_context_size (int, optional): The number of tokens to - consider for repetition penalty. Default: ``20``. - logit_bias (dictionary, optional): Additive logit bias. - - Returns: - List[Callable[[mx.array, mx.array], mx.array]]: - A list of logits processors. Each processor in the list is a - callable which takes an array of tokens and an array of logits - and returns the updated logits. - """ - logits_processors = [] - if logit_bias: - indices = mx.array(list(logit_bias.keys())) - values = mx.array(list(logit_bias.values())) - - def logit_bias_processor(_, logits): - logits[:, indices] += values - return logits - - logits_processors.append(logit_bias_processor) - - if repetition_penalty and repetition_penalty != 0.0: - logits_processors.append( - make_repetition_penalty(repetition_penalty, repetition_context_size) - ) - return logits_processors - - -@partial(mx.compile, inputs=mx.random.state, outputs=mx.random.state) -def apply_top_k( - logprobs: mx.array, - top_k: int, -) -> mx.array: - """ - Sample from only the top K tokens ranked by probability. - - Args: - logprobs: A vector of log probabilities. - top_k (int): Top k tokens to sample from. - """ - vocab_size = logprobs.shape[-1] - if not isinstance(top_k, int) or not (0 < top_k < vocab_size): - raise ValueError( - f"`top_k` has to be an integer in the (0, {vocab_size}] interval," - f" but is {top_k}." - ) - mask_idx = mx.argpartition(-logprobs, kth=top_k - 1, axis=-1)[..., top_k:] - masked_logprobs = mx.put_along_axis( - logprobs, mask_idx, mx.array(-float("inf"), logprobs.dtype), axis=-1 - ) - return masked_logprobs - - -@partial(mx.compile, inputs=mx.random.state, outputs=mx.random.state) -def apply_min_p( - logprobs: mx.array, - min_p: float, - min_tokens_to_keep: int = 1, -) -> mx.array: - """ - Apply min-p sampling to the logprobs. - - Min-p keeps all tokens that are above a minimum probability, scaled by the - probability of the most likely token. As a result, the filter is more - aggressive given a very high-probability token. - - Args: - logprobs: A vector of log probabilities. - min_p (float): Minimum token probability. Typical values are in the - 0.01-0.2 range, comparably selective as setting `top_p` in the - 0.99-0.8 range. - min_tokens_to_keep (int, optional): Minimum number of tokens that cannot - be filtered. Default: ``1``. - - """ - if not (0 <= min_p <= 1.0): - raise ValueError( - f"`min_p` has to be a float in the [0, 1] interval, but is {min_p}" - ) - if not isinstance(min_tokens_to_keep, int) or (min_tokens_to_keep < 1): - raise ValueError( - f"`min_tokens_to_keep` has to be a positive integer, but is {min_tokens_to_keep}" - ) - # reference implementation: https://github.com/huggingface/transformers/blob/main/src/transformers/generation/logits_process.py#L531-L605 - - # Indices sorted in decreasing order - sorted_indices = mx.argsort(-logprobs, axis=-1) - sorted_logprobs = mx.take_along_axis(logprobs, sorted_indices, axis=-1) - - # Top probability - top_logprobs = sorted_logprobs[:, 0:1] - - # Calculate the min_p threshold - scaled_min_p = top_logprobs + math.log(min_p) - - # Mask tokens that have a probability less than the scaled min_p - tokens_to_remove = sorted_logprobs < scaled_min_p - tokens_to_remove[..., :min_tokens_to_keep] = False - - # Create pool of tokens with probability less than scaled min_p - selected_logprobs = mx.where(tokens_to_remove, -float("inf"), sorted_logprobs) - - # Create a mapping to rearrange back to original indices - # Use argsort of sorted_indices to get the inverse permutation - inverse_indices = mx.argsort(sorted_indices, axis=-1) - - # Rearrange selected_logprobs back to original order - original_order_logprobs = mx.take_along_axis( - selected_logprobs, inverse_indices, axis=-1 - ) - - return original_order_logprobs - - -@partial(mx.compile, inputs=mx.random.state, outputs=mx.random.state) -def apply_top_p(logits: mx.array, top_p: float) -> mx.array: - """ - Apply top-p (nucleus) sampling to logits. - - Args: - logits: The logits from the model's output. - top_p: The cumulative probability threshold for top-p filtering. - Returns: - token selected based on the top-p criterion. - """ - # referenced implementation from https://github.com/huggingface/transformers/blob/main/src/transformers/generation/logits_process.py#L449-L460 - probs = mx.softmax(logits, axis=-1) - - # sort probs in ascending order - sorted_indices = mx.argsort(probs, axis=-1) - sorted_probs = mx.take_along_axis(probs, sorted_indices, axis=-1) - - cumulative_probs = mx.cumsum(sorted_probs, axis=-1) - - # select tokens with cumulative probs below threshold - top_probs = mx.where( - cumulative_probs > 1 - top_p, - sorted_probs, - 0, - ) - - # Create a mapping to rearrange back to original indices - # Use argsort of sorted_indices to get the inverse permutation - inverse_indices = mx.argsort(sorted_indices, axis=-1) - - # Rearrange top_probs back to original order - original_order_probs = mx.take_along_axis(top_probs, inverse_indices, axis=-1) - - # Convert back to logits and return - return mx.log(original_order_probs) - - -@partial(mx.compile, inputs=mx.random.state, outputs=mx.random.state) -def categorical_sampling(logits, temp): - return mx.random.categorical(logits * (1 / temp)) - - -def make_repetition_penalty(penalty: float, context_size: int = 20): - """ - Make repetition penalty processor. - - Paper: https://arxiv.org/abs/1909.05858 - - Args: - penalty (float): The repetition penalty factor to be applied. - context_size (int): The number of previous tokens to use. - Default: ``20``. - - Returns: - Callable[[mx.array, List[int]], mx.array]: - The repetition penalty processor. - """ - if penalty < 0 or not isinstance(penalty, (int, float)): - raise ValueError(f"penalty must be a non-negative float, got {penalty}") - - def repetition_penalty_processor(tokens, logits): - if len(tokens) > 0: - tokens = tokens[-context_size:] - selected_logits = logits[:, tokens] - selected_logits = mx.where( - selected_logits < 0, - selected_logits * penalty, - selected_logits / penalty, - ) - logits[:, tokens] = selected_logits - return logits - - return repetition_penalty_processor diff --git a/llms/mlx_lm/server.py b/llms/mlx_lm/server.py deleted file mode 100644 index de02704d..00000000 --- a/llms/mlx_lm/server.py +++ /dev/null @@ -1,785 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -import argparse -import json -import logging -import platform -import time -import uuid -import warnings -from dataclasses import dataclass, field -from http.server import BaseHTTPRequestHandler, HTTPServer -from pathlib import Path -from typing import ( - Any, - Dict, - List, - Literal, - NamedTuple, - Optional, - Sequence, - Tuple, - Union, -) - -import mlx.core as mx -from huggingface_hub import scan_cache_dir - -from ._version import __version__ -from .models.cache import make_prompt_cache -from .sample_utils import make_logits_processors, make_sampler -from .utils import load, stream_generate - - -def get_system_fingerprint(): - gpu_arch = mx.metal.device_info()["architecture"] if mx.metal.is_available() else "" - return f"{__version__}-{mx.__version__}-{platform.platform()}-{gpu_arch}" - - -class StopCondition(NamedTuple): - stop_met: bool - trim_length: int - - -def stopping_criteria( - tokens: List[int], - stop_id_sequences: List[List[int]], - eos_token_id: Union[int, None], -) -> StopCondition: - """ - Determines whether the token generation should stop based on predefined - conditions. - - Args: - tokens (List[int]): The current sequence of generated tokens. - stop_id_sequences (List[List[[int]]): A list of integer lists, each - representing a sequence of token IDs. If the end of the `tokens` - list matches any of these sequences, the generation should stop. - eos_token_id (Union[int, None]): The token ID that represents the - end-of-sequence. If the last token in `tokens` matches this, the - generation should stop. - - Returns: - StopCondition: A named tuple indicating whether the stop condition has - been met (`stop_met`) and how many tokens should be trimmed from the - end if it has (`trim_length`). - """ - if tokens and tokens[-1] == eos_token_id: - return StopCondition(stop_met=True, trim_length=0) - - for stop_ids in stop_id_sequences: - if len(tokens) >= len(stop_ids): - if tokens[-len(stop_ids) :] == stop_ids: - return StopCondition(stop_met=True, trim_length=len(stop_ids)) - - return StopCondition(stop_met=False, trim_length=0) - - -def sequence_overlap(s1: Sequence, s2: Sequence) -> bool: - """ - Checks if a suffix of s1 has overlap with a prefix of s2 - - Args: - s1 (Sequence): The first sequence - s2 (Sequence): The second sequence - - Returns: - bool: If the two sequences have overlap - """ - max_overlap = min(len(s1), len(s2)) - return any(s1[-i:] == s2[:i] for i in range(1, max_overlap + 1)) - - -def convert_chat(messages: List[dict], role_mapping: Optional[dict] = None): - default_role_mapping = { - "system_prompt": ( - "A chat between a curious user and an artificial intelligence " - "assistant. The assistant follows the given rules no matter what." - ), - "system": "ASSISTANT's RULE: ", - "user": "USER: ", - "assistant": "ASSISTANT: ", - "stop": "\n", - } - role_mapping = role_mapping if role_mapping is not None else default_role_mapping - - prompt = "" - for line in messages: - role_prefix = role_mapping.get(line["role"], "") - stop = role_mapping.get("stop", "") - content = line.get("content", "") - prompt += f"{role_prefix}{content}{stop}" - - prompt += role_mapping.get("assistant", "") - return prompt.rstrip() - - -def process_message_content(messages): - """ - Convert message content to a format suitable for `apply_chat_template`. - - The function operates on messages in place. It converts the 'content' field - to a string instead of a list of text fragments. - - Args: - message_list (list): A list of dictionaries, where each dictionary may - have a 'content' key containing a list of dictionaries with 'type' and - 'text' keys. - - Raises: - ValueError: If the 'content' type is not supported or if 'text' is missing. - - """ - for message in messages: - content = message["content"] - if isinstance(content, list): - text_fragments = [ - fragment["text"] for fragment in content if fragment["type"] == "text" - ] - if len(text_fragments) != len(content): - raise ValueError("Only 'text' content type is supported.") - message["content"] = "".join(text_fragments) - - -@dataclass -class PromptCache: - cache: List[Any] = field(default_factory=list) - model_key: Tuple[str, Optional[str]] = ("", None) - tokens: List[int] = field(default_factory=list) - - -class ModelProvider: - def __init__(self, cli_args: argparse.Namespace): - """Load models on demand and persist them across the whole process.""" - self.cli_args = cli_args - self.model_key = None - self.model = None - self.tokenizer = None - - # Preload the default model if it is provided - if self.cli_args.model is not None: - self.load("default_model") - - def _validate_model_path(self, model_path: str): - model_path = Path(model_path) - if model_path.exists() and not model_path.is_relative_to(Path.cwd()): - raise RuntimeError( - "Local models must be relative to the current working dir." - ) - - # Added in adapter_path to load dynamically - def load(self, model_path, adapter_path=None): - if self.model_key == (model_path, adapter_path): - return self.model, self.tokenizer - - # Remove the old model if it exists. - self.model = None - self.tokenizer = None - self.model_key = None - - # Building tokenizer_config - tokenizer_config = { - "trust_remote_code": True if self.cli_args.trust_remote_code else None - } - if self.cli_args.chat_template: - tokenizer_config["chat_template"] = self.cli_args.chat_template - - if model_path == "default_model" and self.cli_args.model is not None: - model, tokenizer = load( - self.cli_args.model, - adapter_path=( - adapter_path if adapter_path else self.cli_args.adapter_path - ), # if the user doesn't change the model but adds an adapter path - tokenizer_config=tokenizer_config, - ) - else: - self._validate_model_path(model_path) - model, tokenizer = load( - model_path, adapter_path=adapter_path, tokenizer_config=tokenizer_config - ) - - if self.cli_args.use_default_chat_template: - if tokenizer.chat_template is None: - tokenizer.chat_template = tokenizer.default_chat_template - - self.model_key = (model_path, adapter_path) - self.model = model - self.tokenizer = tokenizer - - return self.model, self.tokenizer - - -class APIHandler(BaseHTTPRequestHandler): - def __init__( - self, - model_provider: ModelProvider, - *args, - prompt_cache: Optional[PromptCache] = None, - system_fingerprint: Optional[str] = None, - **kwargs, - ): - """ - Create static request specific metadata - """ - self.created = int(time.time()) - self.model_provider = model_provider - self.prompt_cache = prompt_cache or PromptCache() - self.system_fingerprint = system_fingerprint or get_system_fingerprint() - super().__init__(*args, **kwargs) - - def _set_cors_headers(self): - self.send_header("Access-Control-Allow-Origin", "*") - self.send_header("Access-Control-Allow-Methods", "*") - self.send_header("Access-Control-Allow-Headers", "*") - - def _set_completion_headers(self, status_code: int = 200): - self.send_response(status_code) - self.send_header("Content-type", "application/json") - self._set_cors_headers() - - def _set_stream_headers(self, status_code: int = 200): - self.send_response(status_code) - self.send_header("Content-type", "text/event-stream") - self.send_header("Cache-Control", "no-cache") - self._set_cors_headers() - - def do_OPTIONS(self): - self._set_completion_headers(204) - self.end_headers() - - def do_POST(self): - """ - Respond to a POST request from a client. - """ - endpoints = { - "/v1/completions": self.handle_text_completions, - "/v1/chat/completions": self.handle_chat_completions, - "/chat/completions": self.handle_chat_completions, - } - - if self.path not in endpoints: - self._set_completion_headers(404) - self.end_headers() - self.wfile.write(b"Not Found") - return - - # Fetch and parse request body - content_length = int(self.headers["Content-Length"]) - raw_body = self.rfile.read(content_length) - self.body = json.loads(raw_body.decode()) - indent = "\t" # Backslashes can't be inside of f-strings - logging.debug(f"Incoming Request Body: {json.dumps(self.body, indent=indent)}") - assert isinstance( - self.body, dict - ), f"Request should be dict, but got {type(self.body)}" - - # Extract request parameters from the body - self.stream = self.body.get("stream", False) - self.stream_options = self.body.get("stream_options", None) - self.requested_model = self.body.get("model", "default_model") - self.adapter = self.body.get("adapters", None) - self.max_tokens = self.body.get("max_completion_tokens", None) - if self.max_tokens is None: - self.max_tokens = self.body.get("max_tokens", 512) - self.temperature = self.body.get("temperature", 0.0) - self.top_p = self.body.get("top_p", 1.0) - self.repetition_penalty = self.body.get("repetition_penalty", 1.0) - self.repetition_context_size = self.body.get("repetition_context_size", 20) - self.logit_bias = self.body.get("logit_bias", None) - self.logprobs = self.body.get("logprobs", -1) - self.validate_model_parameters() - - # Load the model if needed - try: - self.model, self.tokenizer = self.model_provider.load( - self.requested_model, self.adapter - ) - except: - self._set_completion_headers(404) - self.end_headers() - self.wfile.write(b"Not Found") - return - - # Get stop id sequences, if provided - stop_words = self.body.get("stop") - stop_words = stop_words or [] - stop_words = [stop_words] if isinstance(stop_words, str) else stop_words - stop_id_sequences = [ - self.tokenizer.encode(stop_word, add_special_tokens=False) - for stop_word in stop_words - ] - - # Send header type - ( - self._set_stream_headers(200) - if self.stream - else self._set_completion_headers(200) - ) - - # Call endpoint specific method - prompt = endpoints[self.path]() - self.handle_completion(prompt, stop_id_sequences) - - def validate_model_parameters(self): - """ - Validate the model parameters passed in the request for the correct types and values. - """ - if not isinstance(self.stream, bool): - raise ValueError("stream must be a boolean") - - if not isinstance(self.max_tokens, int) or self.max_tokens < 0: - raise ValueError("max_tokens must be a non-negative integer") - - if not isinstance(self.temperature, (float, int)) or self.temperature < 0: - raise ValueError("temperature must be a non-negative float") - - if not isinstance(self.top_p, (float, int)) or self.top_p < 0 or self.top_p > 1: - raise ValueError("top_p must be a float between 0 and 1") - - if ( - not isinstance(self.repetition_penalty, (float, int)) - or self.repetition_penalty < 0 - ): - raise ValueError("repetition_penalty must be a non-negative float") - - if self.logprobs != -1 and not (0 < self.logprobs <= 10): - raise ValueError( - f"logprobs must be between 1 and 10 but got {self.logprobs:,}" - ) - - if ( - not isinstance(self.repetition_context_size, int) - or self.repetition_context_size < 0 - ): - raise ValueError("repetition_context_size must be a non-negative integer") - - if self.logit_bias is not None: - if not isinstance(self.logit_bias, dict): - raise ValueError("logit_bias must be a dict of int to float") - - try: - self.logit_bias = {int(k): v for k, v in self.logit_bias.items()} - except ValueError: - raise ValueError("logit_bias must be a dict of int to float") - - if not isinstance(self.requested_model, str): - raise ValueError("model must be a string") - if self.adapter is not None and not isinstance(self.adapter, str): - raise ValueError("adapter must be a string") - - def generate_response( - self, - text: str, - finish_reason: Union[Literal["length", "stop"], None], - prompt_token_count: Optional[int] = None, - completion_token_count: Optional[int] = None, - token_logprobs: Optional[List[float]] = None, - top_tokens: Optional[List[Dict[int, float]]] = None, - tokens: Optional[List[int]] = None, - ) -> dict: - """ - Generate a single response packet based on response type (stream or - not), completion type and parameters. - - Args: - text (str): Text generated by model - finish_reason (Union[Literal["length", "stop"], None]): The reason the - response is being sent: "length", "stop" or `None`. - prompt_token_count (Optional[int]): The number of tokens in the prompt, - used to populate the "usage" field (not used when stream). - completion_token_count (Optional[int]): The number of tokens in the - response, used to populate the "usage" field (not used when stream). - token_logprobs (Optional[List[float]]): The log probabilities per token, - in token order. - top_tokens (Optional[List[Dict[int, float]]]): List of dictionaries mapping - tokens to logprobs for the top N tokens at each token position. - tokens (Optional[List[int]]): List of tokens to return with logprobs structure - - Returns: - dict: A dictionary containing the response, in the same format as - OpenAI's API. - """ - token_logprobs = token_logprobs if token_logprobs else [] - top_logprobs = top_tokens if top_tokens else [] - - # Static response - response = { - "id": self.request_id, - "system_fingerprint": self.system_fingerprint, - "object": self.object_type, - "model": self.requested_model, - "created": self.created, - "choices": [ - { - "index": 0, - "logprobs": { - "token_logprobs": token_logprobs, - "top_logprobs": top_logprobs, - "tokens": tokens, - }, - "finish_reason": finish_reason, - } - ], - } - - if not self.stream: - if not ( - isinstance(prompt_token_count, int) - and isinstance(completion_token_count, int) - ): - raise ValueError( - "Response type is complete, but token counts not provided" - ) - - response["usage"] = { - "prompt_tokens": prompt_token_count, - "completion_tokens": completion_token_count, - "total_tokens": prompt_token_count + completion_token_count, - } - - choice = response["choices"][0] - - # Add dynamic response - if self.object_type.startswith("chat.completion"): - key_name = "delta" if self.stream else "message" - choice[key_name] = {"role": "assistant", "content": text} - elif self.object_type == "text_completion": - choice.update(text=text) - else: - ValueError(f"Unsupported response type: {self.object_type}") - - return response - - def get_prompt_cache(self, prompt): - cache_len = len(self.prompt_cache.tokens) - if ( - self.prompt_cache.model_key != self.model_provider.model_key - or cache_len >= len(prompt) - or self.prompt_cache.tokens != prompt[:cache_len] - ): - self.prompt_cache.model_key = self.model_provider.model_key - self.prompt_cache.cache = make_prompt_cache(self.model_provider.model) - else: - prompt = prompt[cache_len:] - self.prompt_cache.tokens.extend(prompt) - return prompt - - def handle_completion( - self, - prompt: List[int], - stop_id_sequences: List[List[int]], - ): - """ - Generate a response to a prompt and send it to the client in a single batch. - - Args: - prompt (List[int]): The tokenized prompt. - stop_id_sequences (List[List[int]]): A list of stop words passed - to the stopping_criteria function - """ - tokens = [] - finish_reason = "length" - stop_sequence_suffix = None - if self.stream: - self.end_headers() - logging.debug(f"Starting stream:") - else: - logging.debug(f"Starting completion:") - token_logprobs = [] - top_tokens = [] - - prompt = self.get_prompt_cache(prompt) - - text = "" - tic = time.perf_counter() - sampler = make_sampler(self.temperature, top_p=self.top_p) - logits_processors = make_logits_processors( - self.logit_bias, self.repetition_penalty, self.repetition_context_size - ) - for gen_response in stream_generate( - model=self.model, - tokenizer=self.tokenizer, - prompt=prompt, - max_tokens=self.max_tokens, - sampler=sampler, - logits_processors=logits_processors, - prompt_cache=self.prompt_cache.cache, - ): - segment = gen_response.text - text += segment - logging.debug(text) - token = gen_response.token - logprobs = gen_response.logprobs - tokens.append(token) - - if self.logprobs > 0: - sorted_indices = mx.argpartition(-logprobs, kth=self.logprobs - 1) - top_indices = sorted_indices[: self.logprobs] - top_logprobs = logprobs[top_indices] - top_token_info = zip(top_indices.tolist(), top_logprobs.tolist()) - top_tokens.append(tuple(top_token_info)) - - token_logprobs.append(logprobs[token].item()) - - stop_condition = stopping_criteria( - tokens, stop_id_sequences, self.tokenizer.eos_token_id - ) - if stop_condition.stop_met: - finish_reason = "stop" - if stop_condition.trim_length: - stop_sequence_suffix = self.tokenizer.decode( - tokens[-stop_condition.trim_length :] - ) - text = text[: -len(stop_sequence_suffix)] - break - - if self.stream: - # If the end of tokens overlaps with a stop sequence, generate new - # tokens until we know if the stop sequence is hit or not - if any( - ( - sequence_overlap(tokens, sequence) - for sequence in stop_id_sequences - ) - ): - continue - elif segment: - response = self.generate_response(segment, None) - self.wfile.write(f"data: {json.dumps(response)}\n\n".encode()) - self.wfile.flush() - - self.prompt_cache.tokens.extend(tokens) - - logging.debug(f"Prompt: {gen_response.prompt_tps:.3f} tokens-per-sec") - logging.debug(f"Generation: {gen_response.generation_tps:.3f} tokens-per-sec") - logging.debug(f"Peak memory: {gen_response.peak_memory:.3f} GB") - - if self.stream: - response = self.generate_response(segment, finish_reason) - self.wfile.write(f"data: {json.dumps(response)}\n\n".encode()) - self.wfile.flush() - if self.stream_options is not None and self.stream_options["include_usage"]: - response = self.completion_usage_response(len(prompt), len(tokens)) - self.wfile.write(f"data: {json.dumps(response)}\n\n".encode()) - self.wfile.flush() - self.wfile.write("data: [DONE]\n\n".encode()) - self.wfile.flush() - else: - response = self.generate_response( - text, - finish_reason, - len(prompt), - len(tokens), - token_logprobs=token_logprobs, - top_tokens=top_tokens, - tokens=tokens, - ) - response_json = json.dumps(response).encode() - indent = "\t" # Backslashes can't be inside of f-strings - logging.debug(f"Outgoing Response: {json.dumps(response, indent=indent)}") - - # Send an additional Content-Length header when it is known - self.send_header("Content-Length", str(len(response_json))) - self.end_headers() - self.wfile.write(response_json) - self.wfile.flush() - - def completion_usage_response( - self, - prompt_token_count: Optional[int] = None, - completion_token_count: Optional[int] = None, - ): - response = { - "id": self.request_id, - "system_fingerprint": self.system_fingerprint, - "object": "chat.completion", - "model": self.requested_model, - "created": self.created, - "choices": [], - "usage": { - "prompt_tokens": prompt_token_count, - "completion_tokens": completion_token_count, - "total_tokens": prompt_token_count + completion_token_count, - }, - } - return response - - def handle_chat_completions(self) -> List[int]: - """ - Handle a chat completion request. - - Returns: - mx.array: A mx.array of the tokenized prompt from the request body - """ - body = self.body - assert "messages" in body, "Request did not contain messages" - - # Determine response type - self.request_id = f"chatcmpl-{uuid.uuid4()}" - self.object_type = "chat.completion.chunk" if self.stream else "chat.completion" - if self.tokenizer.chat_template: - messages = body["messages"] - process_message_content(messages) - prompt = self.tokenizer.apply_chat_template( - messages, - body.get("tools", None), - add_generation_prompt=True, - ) - else: - prompt = convert_chat(body["messages"], body.get("role_mapping")) - prompt = self.tokenizer.encode(prompt) - - return prompt - - def handle_text_completions(self) -> List[int]: - """ - Handle a text completion request. - - Returns: - mx.array: A mx.array of the tokenized prompt from the request body - """ - # Determine response type - self.request_id = f"cmpl-{uuid.uuid4()}" - self.object_type = "text_completion" - assert "prompt" in self.body, "Request did not contain a prompt" - return self.tokenizer.encode(self.body["prompt"]) - - def do_GET(self): - """ - Respond to a GET request from a client. - """ - if self.path == "/v1/models": - self.handle_models_request() - else: - self._set_completion_headers(404) - self.end_headers() - self.wfile.write(b"Not Found") - - def handle_models_request(self): - """ - Handle a GET request for the /v1/models endpoint. - """ - self._set_completion_headers(200) - self.end_headers() - - # Scan the cache directory for downloaded mlx models - hf_cache_info = scan_cache_dir() - downloaded_models = [ - repo for repo in hf_cache_info.repos if "mlx" in repo.repo_id - ] - - # Create a list of available models - models = [ - { - "id": repo.repo_id, - "object": "model", - "created": self.created, - } - for repo in downloaded_models - ] - - response = {"object": "list", "data": models} - - response_json = json.dumps(response).encode() - self.wfile.write(response_json) - self.wfile.flush() - - -def run( - host: str, - port: int, - model_provider: ModelProvider, - server_class=HTTPServer, - handler_class=APIHandler, -): - server_address = (host, port) - prompt_cache = PromptCache() - httpd = server_class( - server_address, - lambda *args, **kwargs: handler_class( - model_provider, - prompt_cache=prompt_cache, - system_fingerprint=get_system_fingerprint(), - *args, - **kwargs, - ), - ) - warnings.warn( - "mlx_lm.server is not recommended for production as " - "it only implements basic security checks." - ) - logging.info(f"Starting httpd at {host} on port {port}...") - httpd.serve_forever() - - -def main(): - parser = argparse.ArgumentParser(description="MLX Http Server.") - parser.add_argument( - "--model", - type=str, - help="The path to the MLX model weights, tokenizer, and config", - ) - parser.add_argument( - "--adapter-path", - type=str, - help="Optional path for the trained adapter weights and config.", - ) - parser.add_argument( - "--host", - type=str, - default="127.0.0.1", - help="Host for the HTTP server (default: 127.0.0.1)", - ) - parser.add_argument( - "--port", - type=int, - default=8080, - help="Port for the HTTP server (default: 8080)", - ) - parser.add_argument( - "--trust-remote-code", - action="store_true", - help="Enable trusting remote code for tokenizer", - ) - parser.add_argument( - "--log-level", - type=str, - default="INFO", - choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], - help="Set the logging level (default: INFO)", - ) - parser.add_argument( - "--cache-limit-gb", - type=int, - default=None, - help="Set the MLX cache limit in GB", - required=False, - ) - parser.add_argument( - "--chat-template", - type=str, - default="", - help="Specify a chat template for the tokenizer", - required=False, - ) - parser.add_argument( - "--use-default-chat-template", - action="store_true", - help="Use the default chat template", - ) - args = parser.parse_args() - - logging.basicConfig( - level=getattr(logging, args.log_level.upper(), None), - format="%(asctime)s - %(levelname)s - %(message)s", - ) - - if args.cache_limit_gb is not None: - logging.debug(f"Setting cache limit to {args.cache_limit_gb} GB") - mx.metal.set_cache_limit(args.cache_limit_gb * 1024 * 1024 * 1024) - - run(args.host, args.port, ModelProvider(args)) - - -if __name__ == "__main__": - main() diff --git a/llms/mlx_lm/tokenizer_utils.py b/llms/mlx_lm/tokenizer_utils.py deleted file mode 100644 index b33d504b..00000000 --- a/llms/mlx_lm/tokenizer_utils.py +++ /dev/null @@ -1,376 +0,0 @@ -import json -from functools import partial -from typing import List - -from transformers import AutoTokenizer - - -class StreamingDetokenizer: - """The streaming detokenizer interface so that we can detokenize one token at a time. - - Example usage is as follows: - - detokenizer = ... - - # Reset the tokenizer state - detokenizer.reset() - - for token in generate(...): - detokenizer.add_token(token.item()) - - # Contains the whole text so far. Some tokens may not be included - # since it contains whole words usually. - detokenizer.text - - # Contains the printable segment (usually a word) since the last - # time it was accessed - detokenizer.last_segment - - # Contains all the tokens added so far - detokenizer.tokens - - # Make sure that we detokenize any remaining tokens - detokenizer.finalize() - - # Now detokenizer.text should match tokenizer.decode(detokenizer.tokens) - """ - - __slots__ = ("text", "tokens", "offset") - - def reset(self): - raise NotImplementedError() - - def add_token(self, token): - raise NotImplementedError() - - def finalize(self): - raise NotImplementedError() - - @property - def last_segment(self): - """Return the last segment of readable text since last time this property was accessed.""" - text = self.text - segment = text[self.offset :] - self.offset = len(text) - return segment - - -class NaiveStreamingDetokenizer(StreamingDetokenizer): - """NaiveStreamingDetokenizer relies on the underlying tokenizer - implementation and should work with every tokenizer. - - Its complexity is O(T^2) where T is the longest line since it will - repeatedly detokenize the same tokens until a new line is generated. - """ - - def __init__(self, tokenizer): - self._tokenizer = tokenizer - self._tokenizer.decode([0]) - self.reset() - - def reset(self): - self.offset = 0 - self.tokens = [] - self._text = "" - self._current_tokens = [] - self._current_text = "" - - def add_token(self, token): - self._current_tokens.append(token) - self.tokens.append(token) - - def finalize(self): - self._text += self._tokenizer.decode(self._current_tokens) - self._current_tokens = [] - self._current_text = "" - - @property - def text(self): - if self._current_tokens: - self._current_text = self._tokenizer.decode(self._current_tokens) - if ( - self._tokenizer.clean_up_tokenization_spaces - and self._current_text[-1] == " " - ): - self._current_text = self._current_text[:-1] - if self._current_text and self._current_text[-1] == "\n": - self._text += self._current_text - self._current_tokens.clear() - self._current_text = "" - return self._text + self._current_text - - -class SPMStreamingDetokenizer(StreamingDetokenizer): - """A streaming detokenizer for SPM models. - - It adds tokens to the text if the next token starts with the special SPM - underscore which results in linear complexity. - """ - - def __init__(self, tokenizer, trim_space=True): - self.trim_space = trim_space - self._sep = "\u2581".encode() - - # Extract the tokens in a list from id to text - self.tokenmap = [""] * (max(tokenizer.vocab.values()) + 1) - for value, tokenid in tokenizer.vocab.items(): - if value.startswith("<0x"): - # Replace bytes with their value - self.tokenmap[tokenid] = bytes([int(value[3:5], 16)]) - else: - self.tokenmap[tokenid] = value.encode() - - self.reset() - - def reset(self): - self.offset = 0 - self._unflushed = b"" - self.text = "" - self.tokens = [] - - def _try_flush(self, force=False): - text = self._unflushed.replace(self._sep, b" ").decode("utf-8", "replace") - if not force and text.endswith("\ufffd"): - return - if not self.text and self.trim_space and text and text[0] == " ": - text = text[1:] - self.text += text - self._unflushed = b"" - - def add_token(self, token): - self.tokens.append(token) - v = self.tokenmap[token] - self._unflushed += v - self._try_flush() - - def finalize(self): - self._try_flush(force=True) - self._unflushed = b"" - - -class BPEStreamingDetokenizer(StreamingDetokenizer): - """A streaming detokenizer for OpenAI style BPE models. - - It adds tokens to the text if the next token starts with a space similar to - the SPM detokenizer. - """ - - _byte_decoder = None - _space_matches = (".", "?", "!", ",", "n't", "'m", "'s", "'ve", "'re") - - def __init__(self, tokenizer): - self.clean_spaces = tokenizer.clean_up_tokenization_spaces - - # Extract the tokens in a list from id to text - self.tokenmap = [None] * len(tokenizer.vocab) - for value, tokenid in tokenizer.vocab.items(): - self.tokenmap[tokenid] = value - - self.reset() - - # Make the BPE byte decoder from - # https://github.com/openai/gpt-2/blob/master/src/encoder.py - self.make_byte_decoder() - - def reset(self): - self.offset = 0 - self._unflushed = "" - self.text = "" - self.tokens = [] - - def _decode_bytes(self, seq): - barr = bytearray() - for c in seq: - res = self._byte_decoder.get(c, False) - if res: - barr.append(res) - else: - barr.extend(bytes(c, "utf-8")) - return barr.decode("utf-8", "replace") - - def _maybe_trim_space(self, current_text): - if len(current_text) == 0: - return current_text - elif current_text[0] != " ": - return current_text - elif not self.text: - return current_text[1:] - elif self.clean_spaces and current_text[1:].startswith(self._space_matches): - return current_text[1:] - return current_text - - def add_token(self, token): - self.tokens.append(token) - v = self.tokenmap[token] - self._unflushed += v - text = self._decode_bytes(self._unflushed) - - # For multi-byte utf-8 wait until they are complete - # For single spaces wait until the next token to clean it if needed - if not text.endswith("\ufffd") and not ( - len(v) == 1 and self._byte_decoder[v[0]] == 32 - ): - self.text += self._maybe_trim_space(text) - self._unflushed = "" - - def finalize(self): - current_text = bytearray(self._byte_decoder[c] for c in self._unflushed).decode( - "utf-8", - "replace", - ) - self.text += self._maybe_trim_space(current_text) - self._unflushed = "" - - @classmethod - def make_byte_decoder(cls): - """See https://github.com/openai/gpt-2/blob/master/src/encoder.py for the rationale.""" - if cls._byte_decoder is not None: - return - - char_to_bytes = {} - limits = [ - 0, - ord("!"), - ord("~") + 1, - ord("¡"), - ord("¬") + 1, - ord("®"), - ord("ÿ") + 1, - ] - n = 0 - for i, (start, stop) in enumerate(zip(limits, limits[1:])): - if i % 2 == 0: - for b in range(start, stop): - char_to_bytes[chr(2**8 + n)] = b - n += 1 - else: - for b in range(start, stop): - char_to_bytes[chr(b)] = b - cls._byte_decoder = char_to_bytes - - -class TokenizerWrapper: - """A wrapper that combines an HF tokenizer and a detokenizer. - - Accessing any attribute other than the ``detokenizer`` is forwarded to the - huggingface tokenizer. - """ - - def __init__( - self, tokenizer, detokenizer_class=NaiveStreamingDetokenizer, eos_token_ids=None - ): - self._tokenizer = tokenizer - self._detokenizer = detokenizer_class(tokenizer) - self._eos_token_ids = ( - set(eos_token_ids) - if eos_token_ids is not None - else {tokenizer.eos_token_id} - ) - - def add_eos_token(self, token: str): - token_id = None - try: - token_id = int(token) - except ValueError: - token_id = self._tokenizer.convert_tokens_to_ids(token) - - if token_id is None: - raise ValueError(f"'{token}' is not a token for this tokenizer") - - self._eos_token_ids.add(token_id) - - def __getattr__(self, attr): - if attr == "detokenizer": - return self._detokenizer - elif attr == "eos_token_ids": - return self._eos_token_ids - elif attr.startswith("_"): - return self.__getattribute__(attr) - else: - return getattr(self._tokenizer, attr) - - def __setattr__(self, attr, value): - if attr in {"detokenizer", "eos_token_ids"}: - if attr == "detokenizer": - raise AttributeError("Cannot set the detokenizer.") - elif attr == "eos_token_ids": - self._eos_token_ids = set(value) if value is not None else set() - elif attr.startswith("_"): - super().__setattr__(attr, value) - else: - setattr(self._tokenizer, attr, value) - - -def _match(a, b): - if type(a) != type(b): - return False - if isinstance(a, dict): - return len(a) == len(b) and all(k in b and _match(a[k], b[k]) for k in a) - if isinstance(a, list): - return len(a) == len(b) and all(_match(ai, bi) for ai, bi in zip(a, b)) - - return a == b - - -def _is_spm_decoder(decoder): - _target_description = { - "type": "Sequence", - "decoders": [ - {"type": "Replace", "pattern": {"String": "▁"}, "content": " "}, - {"type": "ByteFallback"}, - {"type": "Fuse"}, - {"type": "Strip", "content": " ", "start": 1, "stop": 0}, - ], - } - return _match(_target_description, decoder) - - -def _is_spm_decoder_no_space(decoder): - _target_description = { - "type": "Sequence", - "decoders": [ - {"type": "Replace", "pattern": {"String": "▁"}, "content": " "}, - {"type": "ByteFallback"}, - {"type": "Fuse"}, - ], - } - return _match(_target_description, decoder) - - -def _is_bpe_decoder(decoder): - return isinstance(decoder, dict) and decoder.get("type", None) == "ByteLevel" - - -def load_tokenizer(model_path, tokenizer_config_extra={}, eos_token_ids=None): - """Load a huggingface tokenizer and try to infer the type of streaming - detokenizer to use. - - Note, to use a fast streaming tokenizer, pass a local file path rather than - a Hugging Face repo ID. - """ - detokenizer_class = NaiveStreamingDetokenizer - - tokenizer_file = model_path / "tokenizer.json" - if tokenizer_file.exists(): - with open(tokenizer_file, "r", encoding="utf-8") as fid: - tokenizer_content = json.load(fid) - if "decoder" in tokenizer_content: - if _is_spm_decoder(tokenizer_content["decoder"]): - detokenizer_class = SPMStreamingDetokenizer - elif _is_spm_decoder_no_space(tokenizer_content["decoder"]): - detokenizer_class = partial(SPMStreamingDetokenizer, trim_space=False) - elif _is_bpe_decoder(tokenizer_content["decoder"]): - detokenizer_class = BPEStreamingDetokenizer - - if isinstance(eos_token_ids, int): - eos_token_ids = [eos_token_ids] - return TokenizerWrapper( - AutoTokenizer.from_pretrained(model_path, **tokenizer_config_extra), - detokenizer_class, - eos_token_ids=eos_token_ids, - ) - - -def no_bos_or_eos(sequence: List, bos: int, eos: int) -> List: - removed_bos = sequence if sequence[0] != bos else sequence[1:] - return removed_bos[:-1] if removed_bos[-1] == eos else removed_bos diff --git a/llms/mlx_lm/tuner/__init__.py b/llms/mlx_lm/tuner/__init__.py deleted file mode 100644 index 2e6d2f90..00000000 --- a/llms/mlx_lm/tuner/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .trainer import TrainingArgs, evaluate, train -from .utils import linear_to_lora_layers diff --git a/llms/mlx_lm/tuner/datasets.py b/llms/mlx_lm/tuner/datasets.py deleted file mode 100644 index a6f3bd29..00000000 --- a/llms/mlx_lm/tuner/datasets.py +++ /dev/null @@ -1,273 +0,0 @@ -import itertools -import json -import types -from pathlib import Path -from typing import Any, Dict, List, Optional - -from transformers import PreTrainedTokenizer - - -class Dataset: - """ - Light-weight wrapper to hold a dataset. - """ - - def __init__( - self, - data: List[Dict[str, str]], - tokenizer: PreTrainedTokenizer, - text_key: str = "text", - ): - self._data = [tokenizer.encode(d[text_key]) for d in data] - for d in self._data: - if d[-1] != tokenizer.eos_token_id: - d.append(tokenizer.eos_token_id) - - def __getitem__(self, idx: int): - return self._data[idx] - - def __len__(self): - return len(self._data) - - -class ChatDataset: - """ - A dataset for chat data in the format of {"messages": [...]} - https://platform.openai.com/docs/guides/fine-tuning/example-format - """ - - def __init__( - self, - data: List[Dict[str, str]], - tokenizer: PreTrainedTokenizer, - chat_key: str = "messages", - mask_prompt: bool = False, - ): - self._data = [] - for d in data: - messages = d[chat_key] - tools = d.get("tools", None) - tokens = tokenizer.apply_chat_template(messages, tools=tools) - if mask_prompt: - messages = messages[:-1] - offset = len(tokenizer.apply_chat_template(messages, tools=tools)) - self._data.append((tokens, offset)) - else: - self._data.append(tokens) - - def __getitem__(self, idx: int): - return self._data[idx] - - def __len__(self): - return len(self._data) - - -class CompletionsDataset: - """ - A dataset for prompt-completion data in the format of {"prompt": ..., "completion": ...} - or using user-provided keys for prompt and completion values - https://platform.openai.com/docs/guides/fine-tuning/example-format - """ - - def __init__( - self, - data: List[Dict[str, str]], - tokenizer: PreTrainedTokenizer, - prompt_key: str, - completion_key: str, - mask_prompt: bool, - ): - self._data = [] - for d in data: - tokens = tokenizer.apply_chat_template( - [ - {"role": "user", "content": d[prompt_key]}, - {"role": "assistant", "content": d[completion_key]}, - ], - ) - if mask_prompt: - offset = len( - tokenizer.apply_chat_template( - [{"role": "user", "content": d[prompt_key]}] - ) - ) - self._data.append((tokens, offset)) - else: - self._data.append(tokens) - - def __getitem__(self, idx: int): - return self._data[idx] - - def __len__(self): - return len(self._data) - - -class ConcatenatedDataset: - def __init__(self, data: List[Any]): - self._data = list(itertools.chain(*data)) - - def __getitem__(self, idx: int): - return self._data[idx] - - def __len__(self): - return len(self._data) - - -def create_dataset( - data, - tokenizer: PreTrainedTokenizer, - config, -): - mask_prompt = getattr(config, "mask_prompt", False) - prompt_feature = getattr(config, "prompt_feature", "prompt") - text_feature = getattr(config, "text_feature", "text") - completion_feature = getattr(config, "completion_feature", "completion") - chat_feature = getattr(config, "chat_feature", "messages") - sample = data[0] - if prompt_feature in sample and completion_feature in sample: - return CompletionsDataset( - data, tokenizer, prompt_feature, completion_feature, mask_prompt - ) - elif chat_feature in sample: - return ChatDataset( - data, tokenizer, chat_key=chat_feature, mask_prompt=mask_prompt - ) - elif text_feature in sample: - if mask_prompt: - raise ValueError("Prompt masking not supported for text dataset.") - return Dataset(data, tokenizer, text_key=text_feature) - else: - raise ValueError( - "Unsupported data format, check the supported formats here:\n" - "https://github.com/ml-explore/mlx-examples/blob/main/llms/mlx_lm/LORA.md#data." - ) - - -def load_local_dataset( - data_path: Path, - tokenizer: PreTrainedTokenizer, - config, -): - def load_subset(path): - if not path.exists(): - return [] - with open(path, "r") as fid: - data = [json.loads(l) for l in fid] - return create_dataset(data, tokenizer, config) - - names = ("train", "valid", "test") - train, valid, test = [load_subset(data_path / f"{n}.jsonl") for n in names] - return train, valid, test - - -def load_hf_dataset( - data_id: str, - tokenizer: PreTrainedTokenizer, - config, -): - from datasets import exceptions, load_dataset - - try: - dataset = load_dataset(data_id) - - names = ("train", "valid", "test") - - train, valid, test = [ - ( - create_dataset(dataset[n], tokenizer, config) - if n in dataset.keys() - else [] - ) - for n in names - ] - - except exceptions.DatasetNotFoundError: - raise ValueError(f"Not found Hugging Face dataset: {data_id} .") - - return train, valid, test - - -def load_custom_hf_dataset(args, tokenizer: PreTrainedTokenizer): - import datasets - - def create_hf_dataset(dataset_name, config, split, hf_config): - ds = datasets.load_dataset( - dataset_name, - split=split, - **hf_config, - ) - return create_dataset(ds, tokenizer, config) - - dataset_collection = args.hf_dataset - if isinstance(dataset_collection, dict): - dataset_collection = [dataset_collection] - - collection = [] - for ds in dataset_collection: - ds_name = ds["name"] - print(f"Loading Hugging Face dataset {ds_name}.") - ds["mask_prompt"] = getattr(args, "mask_prompt", False) - config = types.SimpleNamespace(**ds) - hf_config = ds.get("config", {}) - if args.train: - train_split = ds.get("train_split", "train[:80%]") - valid_split = ds.get("valid_split", "train[-10%:]") - train = create_hf_dataset( - ds_name, - config, - train_split, - hf_config, - ) - valid = create_hf_dataset( - ds_name, - config, - valid_split, - hf_config, - ) - else: - train, valid = [], [] - - if args.test: - test_split = ds.get("test_split") - test = create_hf_dataset( - ds_name, - config, - test_split, - hf_config, - ) - else: - test = [] - - collection.append((train, valid, test)) - - if len(collection) == 1: - return collection[0] - - # Otherwise concatenate them - return tuple(map(ConcatenatedDataset, zip(*collection))) - - -def load_dataset(args, tokenizer: PreTrainedTokenizer): - if getattr(args, "hf_dataset", False): - train, valid, test = load_custom_hf_dataset(args, tokenizer) - else: - data_path = Path(args.data) - if data_path.exists(): - train, valid, test = load_local_dataset(data_path, tokenizer, args) - else: - print(f"Loading Hugging Face dataset {args.data}.") - train, valid, test = load_hf_dataset(args.data, tokenizer, args) - - if args.train and len(train) == 0: - raise ValueError( - "Training set not found or empty. Must provide training set for fine-tuning." - ) - if args.train and len(valid) == 0: - raise ValueError( - "Validation set not found or empty. Must provide validation set for fine-tuning." - ) - if args.test and len(test) == 0: - raise ValueError( - "Test set not found or empty. Must provide test set for evaluation." - ) - return train, valid, test diff --git a/llms/mlx_lm/tuner/dora.py b/llms/mlx_lm/tuner/dora.py deleted file mode 100644 index aba1f6f4..00000000 --- a/llms/mlx_lm/tuner/dora.py +++ /dev/null @@ -1,228 +0,0 @@ -# Copyright © 2024 Apple Inc. - -import math - -import mlx.core as mx -import mlx.nn as nn - - -class DoRALinear(nn.Module): - @staticmethod - def from_base( - linear: nn.Linear, - r: int = 8, - dropout: float = 0.0, - scale: float = 20.0, - ): - # TODO remove when input_dims and output_dims are attributes - # on linear and quantized linear - output_dims, input_dims = linear.weight.shape - if isinstance(linear, nn.QuantizedLinear): - input_dims *= 32 // linear.bits - dora_lin = DoRALinear( - input_dims=input_dims, - output_dims=output_dims, - r=r, - dropout=dropout, - scale=scale, - ) - dora_lin.set_linear(linear) - return dora_lin - - def fuse(self, de_quantize: bool = False): - linear = self.linear - bias = "bias" in linear - weight = self._dequantized_weight() - - # Use the same type as the linear weight - dtype = weight.dtype - - output_dims, input_dims = weight.shape - fused_linear = nn.Linear(input_dims, output_dims, bias=False) - - lora_b = (self.scale * self.lora_b.T).astype(dtype) - lora_a = self.lora_a.T.astype(dtype) - weight = weight + lora_b @ lora_a - norm_scale = self.m / mx.linalg.norm(weight, axis=1) - fused_linear.weight = norm_scale[:, None] * weight - - if bias: - fused_linear.bias = linear.bias - - if self._is_quantized() and not de_quantize: - fused_linear = nn.QuantizedLinear.from_linear( - fused_linear, - linear.group_size, - linear.bits, - ) - return fused_linear - - def __init__( - self, - input_dims: int, - output_dims: int, - r: int = 8, - dropout: float = 0.0, - scale: float = 20.0, - bias: bool = False, - ): - super().__init__() - - # Regular linear layer weights - self.set_linear(nn.Linear(input_dims, output_dims, bias=bias)) - self.dropout = nn.Dropout(p=dropout) - - # Scale for low-rank update - self.scale = scale - - # Low rank lora weights - scale = 1 / math.sqrt(input_dims) - self.lora_a = mx.random.uniform( - low=-scale, - high=scale, - shape=(input_dims, r), - ) - self.lora_b = mx.zeros(shape=(r, output_dims)) - - def set_linear(self, linear): - """ - Set the self.linear layer and recompute self.m. - """ - self.linear = linear - self.m = mx.linalg.norm(self._dequantized_weight().astype(mx.float32), axis=1) - - def _dequantized_weight(self): - """ - Return the weight of linear layer and dequantize it if is quantized - """ - weight = self.linear.weight - if self._is_quantized(): - weight = mx.dequantize( - weight, - self.linear.scales, - self.linear.biases, - self.linear.group_size, - self.linear.bits, - ) - return weight - - def _is_quantized(self): - return isinstance(self.linear, nn.QuantizedLinear) - - def __call__(self, x): - # Regular LoRA (without a bias) - w = self._dequantized_weight() - y = x @ w.T - - z = (self.dropout(x) @ self.lora_a) @ self.lora_b - out = y + (self.scale * z).astype(x.dtype) - - # Compute the norm of the adapted weights - adapted = w + (self.scale * self.lora_b.T) @ self.lora_a.T - denom = mx.stop_gradient(mx.linalg.norm(adapted, axis=1)) - - # Remove the norm and scale by the learned magnitude - out = (self.m / denom).astype(x.dtype) * out - - if "bias" in self.linear: - out = out + self.linear.bias - return out - - -class DoRAEmbedding(nn.Module): - def from_base( - embedding: nn.Embedding, - r: int = 8, - dropout: float = 0.0, - scale: float = 20.0, - ): - num_embeddings, dims = embedding.weight.shape - - # TODO support quantized weights in DoRALinear - if isinstance(embedding, nn.QuantizedLinear): - raise ValueError("DoRAEmbedding does not yet support quantization.") - dora_embedding = DoRAEmbedding( - num_embeddings=num_embeddings, - dims=dims, - r=r, - dropout=dropout, - scale=scale, - ) - dora_embedding.set_embedding(embedding) - return dora_embedding - - def fuse(self, de_quantize: bool = False): - embedding = self.embedding - weight = embedding.weight - - # Use the same type as the linear weight if not quantized - dtype = weight.dtype - - num_embeddings, dims = weight.shape - fused_embedding = nn.Embedding(num_embeddings, dims) - - lora_a = (self.scale * self.lora_a).astype(dtype) - lora_b = self.lora_b.astype(dtype) - weight = weight + lora_a @ lora_b - norm_scale = self.m / mx.linalg.norm(weight, axis=1) - fused_embedding.weight = norm_scale[:, None] * weight - - return fused_embedding - - def __init__( - self, - num_embeddings: int, - dims: int, - r: int = 8, - dropout: float = 0.0, - scale: float = 20.0, - ): - super().__init__() - - # Regular embedding layer weights - self.set_embedding(nn.Embedding(num_embeddings, dims)) - self.dropout = nn.Dropout(p=dropout) - - # Scale for low-rank update - self.scale = scale - - # Low rank lora weights - scale = 1 / math.sqrt(num_embeddings) - self.lora_a = mx.random.uniform( - low=-scale, - high=scale, - shape=(num_embeddings, r), - ) - self.lora_b = mx.zeros(shape=(r, dims)) - - def set_embedding(self, embedding: nn.Module): - self.embedding = embedding - self.m = mx.linalg.norm(embedding.weight, axis=1) - - def __call__(self, x): - y = self.embedding(x) - z = self.scale * self.lora_a[x] @ self.lora_b - out = y + self.dropout(z).astype(y.dtype) - - # Compute the norm of the adapted weights for the individual embeddings - adapted = y + z - denom = mx.stop_gradient(mx.linalg.norm(adapted, axis=-1)) - - # Remove the norm and scale by the learned magnitude - out = (self.m[x] / denom)[..., None] * out - - return out - - def as_linear(self, x): - y = self.embedding.as_linear(x) - z = (self.dropout(x) @ self.lora_b.T) @ self.lora_a.T - out = y + (self.scale * z).astype(x.dtype) - - # Compute the norm of the adapted weights - adapted = self.embedding.weight + (self.scale * self.lora_a) @ self.lora_b - denom = mx.stop_gradient(mx.linalg.norm(adapted, axis=1)) - - # Remove the norm and scale by the learned magnitude - out = (self.m / denom) * out - - return out diff --git a/llms/mlx_lm/tuner/lora.py b/llms/mlx_lm/tuner/lora.py deleted file mode 100644 index c788cb73..00000000 --- a/llms/mlx_lm/tuner/lora.py +++ /dev/null @@ -1,285 +0,0 @@ -# Copyright © 2024 Apple Inc. - -import math - -import mlx.core as mx -import mlx.nn as nn - -from ..models.switch_layers import QuantizedSwitchLinear, SwitchLinear - - -class LoRALinear(nn.Module): - @staticmethod - def from_base( - linear: nn.Linear, - r: int = 8, - dropout: float = 0.0, - scale: float = 20.0, - ): - # TODO remove when input_dims and output_dims are attributes - # on linear and quantized linear - output_dims, input_dims = linear.weight.shape - if isinstance(linear, nn.QuantizedLinear): - input_dims *= 32 // linear.bits - lora_lin = LoRALinear( - input_dims=input_dims, - output_dims=output_dims, - r=r, - dropout=dropout, - scale=scale, - ) - lora_lin.linear = linear - return lora_lin - - def fuse(self, de_quantize: bool = False): - linear = self.linear - bias = "bias" in linear - weight = linear.weight - is_quantized = isinstance(linear, nn.QuantizedLinear) - - # Use the same type as the linear weight if not quantized - dtype = weight.dtype - - if is_quantized: - dtype = linear.scales.dtype - weight = mx.dequantize( - weight, - linear.scales, - linear.biases, - linear.group_size, - linear.bits, - ) - output_dims, input_dims = weight.shape - fused_linear = nn.Linear(input_dims, output_dims, bias=bias) - - lora_b = (self.scale * self.lora_b.T).astype(dtype) - lora_a = self.lora_a.T.astype(dtype) - fused_linear.weight = weight + lora_b @ lora_a - if bias: - fused_linear.bias = linear.bias - - if is_quantized and not de_quantize: - fused_linear = nn.QuantizedLinear.from_linear( - fused_linear, - linear.group_size, - linear.bits, - ) - - return fused_linear - - def __init__( - self, - input_dims: int, - output_dims: int, - r: int = 8, - dropout: float = 0.0, - scale: float = 20.0, - bias: bool = False, - ): - super().__init__() - - # Regular linear layer weights - self.linear = nn.Linear(input_dims, output_dims, bias=bias) - - self.dropout = nn.Dropout(p=dropout) - - # Scale for low-rank update - self.scale = scale - - # Low rank lora weights - scale = 1 / math.sqrt(input_dims) - self.lora_a = mx.random.uniform( - low=-scale, - high=scale, - shape=(input_dims, r), - ) - self.lora_b = mx.zeros(shape=(r, output_dims)) - - def __call__(self, x): - y = self.linear(x) - z = (self.dropout(x) @ self.lora_a) @ self.lora_b - return y + (self.scale * z).astype(x.dtype) - - -class LoRASwitchLinear(nn.Module): - @staticmethod - def from_base( - linear: nn.Module, - r: int = 8, - dropout: float = 0.0, - scale: float = 20.0, - ): - lora_lin = LoRASwitchLinear( - input_dims=linear.input_dims, - output_dims=linear.output_dims, - num_experts=linear.num_experts, - r=r, - dropout=dropout, - scale=scale, - ) - lora_lin.linear = linear - return lora_lin - - def fuse(self, de_quantize: bool = False): - linear = self.linear - bias = "bias" in linear - weight = linear.weight - is_quantized = isinstance(linear, QuantizedSwitchLinear) - - # Use the same type as the linear weight if not quantized - dtype = weight.dtype - - if is_quantized: - dtype = mx.float16 - weight = mx.dequantize( - weight, - linear.scales, - linear.biases, - linear.group_size, - linear.bits, - ) - num_experts, output_dims, input_dims = weight.shape - fused_linear = SwitchLinear(input_dims, output_dims, num_experts, bias=bias) - - lora_b = (self.scale * self.lora_b).astype(dtype) - lora_a = self.lora_a.reshape(num_experts, -1, input_dims).astype(dtype) - fused_linear.weight = weight + lora_b @ lora_a - if bias: - fused_linear.bias = linear.bias - - if is_quantized and not de_quantize: - fused_linear = fused_linear.to_quantized(linear.group_size, linear.bits) - - return fused_linear - - def __init__( - self, - input_dims: int, - output_dims: int, - num_experts: int, - r: int = 8, - dropout: float = 0.0, - scale: float = 20.0, - bias: bool = False, - ): - super().__init__() - - # Regular linear layer weights - self.linear = SwitchLinear(input_dims, output_dims, num_experts, bias=bias) - - self.dropout = nn.Dropout(p=dropout) - - # Scale for low-rank update - self.scale = scale - - # Low rank lora weights - scale = 1 / math.sqrt(input_dims) - self.lora_a = mx.random.uniform( - low=-scale, - high=scale, - shape=(r * num_experts, input_dims), - ) - self.lora_b = mx.zeros(shape=(num_experts, output_dims, r)) - self.num_experts = num_experts - - def __call__(self, x, indices): - shape = x.shape[:-3] + (self.num_experts, -1) - - y = self.linear(x, indices) - z = (self.dropout(x) @ self.lora_a.T).reshape(shape) - z = mx.take_along_axis(z, indices[..., None], axis=-2) - z = z[..., None, :] @ self.lora_b[indices].swapaxes(-2, -1) - - return y + (self.scale * z).astype(x.dtype) - - -class LoRAEmbedding(nn.Module): - @staticmethod - def from_base( - embedding: nn.Embedding, - r: int = 8, - dropout: float = 0.0, - scale: float = 20.0, - ): - num_embeddings, dims = embedding.weight.shape - if isinstance(embedding, nn.QuantizedEmbedding): - dims *= 32 // embedding.bits - lora_embedding = LoRAEmbedding( - num_embeddings=num_embeddings, - dims=dims, - r=r, - dropout=dropout, - scale=scale, - ) - lora_embedding.embedding = embedding - return lora_embedding - - def fuse(self, de_quantize: bool = False): - embedding = self.embedding - weight = embedding.weight - is_quantized = isinstance(embedding, nn.QuantizedEmbedding) - - # Use the same type as the linear weight if not quantized - dtype = weight.dtype - - if is_quantized: - dtype = embedding.scales.dtype - weight = mx.dequantize( - weight, - embedding.scales, - embedding.biases, - embedding.group_size, - embedding.bits, - ) - num_embeddings, dims = weight.shape - fused_embedding = nn.Embedding(num_embeddings, dims) - - lora_a = (self.scale * self.lora_a).astype(dtype) - lora_b = self.lora_b.astype(dtype) - fused_embedding.weight = weight + lora_a @ lora_b - - if is_quantized and not de_quantize: - fused_embedding = nn.QuantizedEmbedding.from_embedding( - fused_embedding, - embedding.group_size, - embedding.bits, - ) - - return fused_embedding - - def __init__( - self, - num_embeddings: int, - dims: int, - r: int = 8, - dropout: float = 0.0, - scale: float = 20.0, - ): - super().__init__() - - # Regular embedding layer - self.embedding = nn.Embedding(num_embeddings, dims) - self.dropout = nn.Dropout(p=dropout) - - # Scale for low-rank update - self.scale = scale - - # Low rank lora weights - scale = 1 / math.sqrt(num_embeddings) - self.lora_a = mx.random.uniform( - low=-scale, - high=scale, - shape=(num_embeddings, r), - ) - self.lora_b = mx.zeros(shape=(r, dims)) - - def __call__(self, x): - y = self.embedding(x) - z = self.dropout(self.lora_a[x] @ self.lora_b) - out = y + (self.scale * z).astype(y.dtype) - return out - - def as_linear(self, x): - y = self.embedding.as_linear(x) - z = (self.dropout(x) @ self.lora_b.T) @ self.lora_a.T - return y + (self.scale * z).astype(x.dtype) diff --git a/llms/mlx_lm/tuner/trainer.py b/llms/mlx_lm/tuner/trainer.py deleted file mode 100644 index 64e26af8..00000000 --- a/llms/mlx_lm/tuner/trainer.py +++ /dev/null @@ -1,343 +0,0 @@ -# Copyright © 2024 Apple Inc. - -import glob -import shutil -import time -from dataclasses import dataclass, field -from pathlib import Path -from typing import List, Optional, Tuple - -import mlx.core as mx -import mlx.nn as nn -import numpy as np -from mlx.nn.utils import average_gradients -from mlx.utils import tree_flatten -from transformers import PreTrainedTokenizer - -from .datasets import CompletionsDataset - - -def grad_checkpoint(layer): - """ - Update all instances of type(layer) to use gradient checkpointing. - """ - fn = type(layer).__call__ - - def checkpointed_fn(model, *args, **kwargs): - def inner_fn(params, *args, **kwargs): - model.update(params) - return fn(model, *args, **kwargs) - - return mx.checkpoint(inner_fn)(model.trainable_parameters(), *args, **kwargs) - - type(layer).__call__ = checkpointed_fn - - -@dataclass -class TrainingArgs: - batch_size: int = field(default=4, metadata={"help": "Minibatch size."}) - iters: int = field(default=100, metadata={"help": "Iterations to train for."}) - val_batches: int = field( - default=25, - metadata={ - "help": "Number of validation batches, -1 uses the entire validation set." - }, - ) - steps_per_report: int = field( - default=10, - metadata={"help": "Number of training steps between loss reporting."}, - ) - steps_per_eval: int = field( - default=200, metadata={"help": "Number of training steps between validations."} - ) - steps_per_save: int = field( - default=100, metadata={"help": "Save the model every number steps"} - ) - max_seq_length: int = field( - default=2048, metadata={"help": "Maximum sequence length."} - ) - adapter_file: str = field( - default="adapters.safetensors", - metadata={"help": "Save/load path for the trained adapter weights."}, - ) - grad_checkpoint: bool = field( - default=False, - metadata={"help": "Use gradient checkpointing to reduce memory use."}, - ) - - -def default_loss(model, batch, lengths): - inputs = batch[:, :-1] - targets = batch[:, 1:] - - logits = model(inputs) - logits = logits.astype(mx.float32) - - steps = mx.arange(1, targets.shape[1] + 1) - mask = mx.logical_and(steps >= lengths[:, 0:1], steps <= lengths[:, 1:]) - - ce = nn.losses.cross_entropy(logits, targets) * mask - ntoks = mask.sum() - ce = ce.sum() / ntoks - - return ce, ntoks - - -def iterate_batches( - dataset, - tokenizer, - batch_size, - max_seq_length, - train=False, -): - # Sort by length: - idx = sorted(range(len(dataset)), key=lambda idx: len(dataset[idx])) - if len(dataset) < batch_size: - raise ValueError( - f"Dataset must have at least batch_size={batch_size}" - f" examples but only has {len(dataset)}." - ) - - # If running in distributed mode (N machines) then each one should skip N-1 - # samples - step = mx.distributed.init().size() - if batch_size % step != 0: - raise ValueError("The batch size must be divisible by the number of workers") - - # Make the batches: - batch_idx = [ - idx[i : i + batch_size : step] - for i in range(0, len(idx) - batch_size + 1, batch_size) - ] - - while True: - indices = np.random.permutation(len(batch_idx)) - for i in indices: - batch = [dataset[j] for j in batch_idx[i]] - if len(batch[0]) == 2: - batch, offsets = zip(*batch) - else: - offsets = [0] * len(batch) - lengths = [len(x) for x in batch] - if max(lengths) > max_seq_length: - print( - f"[WARNING] Some sequences are longer than {max_seq_length} tokens. " - f"The longest sentence {max(lengths)} will be truncated to {max_seq_length}. " - "Consider pre-splitting your data to save memory." - ) - - # Pad to the nearest multiple of 8 or the maximum length - pad_to = 8 - max_length_in_batch = pad_to * ((max(lengths) + pad_to - 1) // pad_to) - max_length_in_batch = min(max_length_in_batch, max_seq_length) - - batch_arr = np.zeros((batch_size // step, max_length_in_batch), np.int32) - - for j in range(batch_size // step): - truncated_length = min(lengths[j], max_seq_length) - batch_arr[j, :truncated_length] = batch[j][:truncated_length] - lengths[j] = ( - truncated_length # Update lengths to match truncated lengths - ) - batch = mx.array(batch_arr) - yield batch, mx.array(list(zip(offsets, lengths))) - - if not train: - break - - -def evaluate( - model, - dataset, - tokenizer, - batch_size, - num_batches, - max_seq_length=2048, - loss: callable = default_loss, - iterate_batches: callable = iterate_batches, -): - all_losses = mx.array(0.0) - ntokens = mx.array(0) - - index_iterator = iter(range(num_batches)) if num_batches != -1 else iter(int, 1) - - for _, batch in zip( - index_iterator, - iterate_batches( - dataset=dataset, - tokenizer=tokenizer, - batch_size=batch_size, - max_seq_length=max_seq_length, - ), - ): - losses, toks = loss(model, *batch) - all_losses += losses * toks - ntokens += toks - mx.eval(all_losses, ntokens) - - all_losses = mx.distributed.all_sum(all_losses, stream=mx.cpu) - ntokens = mx.distributed.all_sum(ntokens, stream=mx.cpu) - - return (all_losses / ntokens).item() - - -class TrainingCallback: - - def on_train_loss_report(self, train_info: dict): - """Called to report training loss at specified intervals.""" - pass - - def on_val_loss_report(self, val_info: dict): - """Called to report validation loss at specified intervals or the beginning.""" - pass - - -def train( - model, - tokenizer, - optimizer, - train_dataset, - val_dataset, - args: TrainingArgs = TrainingArgs(), - loss: callable = default_loss, - iterate_batches: callable = iterate_batches, - training_callback: TrainingCallback = None, -): - print(f"Starting training..., iters: {args.iters}") - world = mx.distributed.init() - world_size = world.size() - rank = world.rank() - if world_size > 1: - print(f"Node {rank} of {world_size}") - - if args.grad_checkpoint: - grad_checkpoint(model.layers[0]) - - state = [model.state, optimizer.state] - - def step(batch): - # Forward and backward pass - (lvalue, toks), grad = loss_value_and_grad(model, *batch) - - # All reduce the gradients if running in distributed mode - grad = average_gradients(grad) - - # Model update - optimizer.update(model, grad) - - return lvalue, toks - - loss_value_and_grad = nn.value_and_grad(model, loss) - - losses = 0 - n_tokens = 0 - steps = 0 - trained_tokens = 0 - train_time = 0 - # Main training loop - for it, batch in zip( - range(1, args.iters + 1), - iterate_batches( - dataset=train_dataset, - tokenizer=tokenizer, - batch_size=args.batch_size, - max_seq_length=args.max_seq_length, - train=True, - ), - ): - tic = time.perf_counter() - # Report validation loss if needed, the first validation loss - # is always measured before any training. - if it == 1 or it % args.steps_per_eval == 0 or it == args.iters: - tic = time.perf_counter() - val_loss = evaluate( - model=model, - dataset=val_dataset, - loss=loss, - tokenizer=tokenizer, - batch_size=args.batch_size, - num_batches=args.val_batches, - max_seq_length=args.max_seq_length, - iterate_batches=iterate_batches, - ) - val_time = time.perf_counter() - tic - if rank == 0: - print( - f"Iter {it}: " - f"Val loss {val_loss:.3f}, " - f"Val took {val_time:.3f}s", - flush=True, - ) - - if training_callback is not None: - val_info = { - "iteration": it, - "val_loss": val_loss, - "val_time": val_time, - } - training_callback.on_val_loss_report(val_info) - - tic = time.perf_counter() - - lvalue, toks = step(batch) - losses += lvalue - n_tokens += toks - steps += 1 - mx.eval(state, losses, n_tokens) - train_time += time.perf_counter() - tic - - # Report training loss if needed - if it % args.steps_per_report == 0 or it == args.iters: - train_loss = mx.distributed.all_sum(losses, stream=mx.cpu).item() - train_loss /= steps * mx.distributed.init().size() - n_tokens = mx.distributed.all_sum(n_tokens, stream=mx.cpu).item() - learning_rate = optimizer.learning_rate.item() - it_sec = args.steps_per_report / train_time - tokens_sec = float(n_tokens) / train_time - trained_tokens += n_tokens - peak_mem = mx.metal.get_peak_memory() / 1e9 - if rank == 0: - print( - f"Iter {it}: Train loss {train_loss:.3f}, " - f"Learning Rate {learning_rate:.3e}, " - f"It/sec {it_sec:.3f}, " - f"Tokens/sec {tokens_sec:.3f}, " - f"Trained Tokens {trained_tokens}, " - f"Peak mem {peak_mem:.3f} GB", - flush=True, - ) - - if training_callback is not None: - train_info = { - "iteration": it, - "train_loss": train_loss, - "learning_rate": learning_rate, - "iterations_per_second": it_sec, - "tokens_per_second": tokens_sec, - "trained_tokens": trained_tokens, - "peak_memory": peak_mem, - } - training_callback.on_train_loss_report(train_info) - - losses = 0 - n_tokens = 0 - steps = 0 - train_time = 0 - - # Save adapter weights - if it % args.steps_per_save == 0: - adapter_weights = dict(tree_flatten(model.trainable_parameters())) - mx.save_safetensors(str(args.adapter_file), adapter_weights) - checkpoint = ( - Path(args.adapter_file).parent / f"{it:07d}_adapters.safetensors" - ) - mx.save_safetensors(str(checkpoint), adapter_weights) - print( - f"Iter {it}: Saved adapter weights to " - f"{args.adapter_file} and {checkpoint}." - ) - - # Save final weights - adapter_weights = dict(tree_flatten(model.trainable_parameters())) - mx.save_safetensors(str(args.adapter_file), adapter_weights) - print(f"Saved final weights to {args.adapter_file}.") diff --git a/llms/mlx_lm/tuner/utils.py b/llms/mlx_lm/tuner/utils.py deleted file mode 100644 index cc7c6c20..00000000 --- a/llms/mlx_lm/tuner/utils.py +++ /dev/null @@ -1,276 +0,0 @@ -# Copyright © 2024 Apple Inc. -import json -import types -from pathlib import Path -from typing import Dict - -import mlx.core as mx -import mlx.nn as nn -import mlx.optimizers as opt -from mlx.utils import tree_flatten, tree_unflatten - -from ..models.switch_layers import QuantizedSwitchLinear, SwitchLinear -from .dora import DoRAEmbedding, DoRALinear -from .lora import LoRAEmbedding, LoRALinear, LoRASwitchLinear - - -def build_schedule(schedule_config: Dict): - """ - Build a learning rate schedule from the given config. - """ - schedule_fn = getattr(opt.schedulers, schedule_config["name"]) - arguments = schedule_config["arguments"] - initial_lr = arguments[0] - bound_schedule_fn = schedule_fn(*arguments) - if warmup_steps := schedule_config.get("warmup", 0): - warmup_init = schedule_config.get("warmup_init", 0.0) - warmup_fn = opt.schedulers.linear_schedule( - warmup_init, initial_lr, warmup_steps - ) - return opt.schedulers.join_schedules( - [warmup_fn, bound_schedule_fn], [warmup_steps + 1] - ) - else: - return bound_schedule_fn - - -def linear_to_lora_layers( - model: nn.Module, - num_layers: int, - config: Dict, - use_dora: bool = False, -): - """ - Convert some of the models linear layers to lora layers. - - Args: - model (nn.Module): The neural network model. - num_layers (int): The number of blocks to convert to lora layers - starting from the last layer. - config (dict): More configuration parameters for LoRA, including the - rank, scale, and optional layer keys. - use_dora (bool): If True, uses DoRA instead of LoRA. - Default: ``False`` - """ - - def to_lora(layer): - if isinstance(layer, (nn.Linear, nn.QuantizedLinear)): - LoRALayer = DoRALinear if use_dora else LoRALinear - elif isinstance(layer, (SwitchLinear, QuantizedSwitchLinear)): - if use_dora: - raise ValueError(f"{type(layer).__name__} doesn't support DoRA yet.") - LoRALayer = LoRASwitchLinear - elif isinstance(layer, (nn.Embedding, nn.QuantizedEmbedding)): - LoRALayer = DoRAEmbedding if use_dora else LoRAEmbedding - else: - raise ValueError( - f"Can't convert layer of type {type(layer).__name__} to LoRA" - ) - - return LoRALayer.from_base( - layer, - r=config["rank"], - scale=config["scale"], - dropout=config["dropout"], - ) - - keys = config.get("keys", None) - if keys is not None: - keys = set(keys) - elif model.model_type in [ - "mistral", - "llama", - "phi", - "mixtral", - "nemotron", - "stablelm", - "hunyuan", - "qwen2", - "qwen2_moe", - "phimoe", - "gemma", - "gemma2", - "granite", - "helium", - "starcoder2", - "cohere", - "cohere2", - "minicpm", - "deepseek", - "olmo2", - "olmoe", - "internlm3", - ]: - keys = set(["self_attn.q_proj", "self_attn.v_proj"]) - if model.model_type in ["mixtral", "phimoe"]: - keys.add("block_sparse_moe.gate") - if model.model_type == "qwen2_moe": - keys.add("mlp.gate") - keys.add("mlp.shared_expert_gate") - if model.model_type == "olmoe": - keys.add("mlp.gate") - - elif model.model_type == "gpt_bigcode": - keys = set(["attn.c_attn"]) - elif model.model_type == "gpt2": - keys = set(["attn.c_attn"]) - elif model.model_type == "gpt_neox": - keys = set(["attention.query_key_value"]) - elif model.model_type == "olmo": - keys = set(["att_proj"]) - elif model.model_type == "openelm": - keys = set(["attn.qkv_proj"]) - elif model.model_type == "phi3": - keys = set(["self_attn.qkv_proj"]) - elif model.model_type == "phi-msft": - keys = set(["mixer.Wqkv", "moe.gate"]) - elif model.model_type == "dbrx": - keys = set(["norm_attn_norm.attn.Wqkv", "ffn.router.layer"]) - elif model.model_type == "internlm2": - keys = set(["attention.wqkv", "attention.wo"]) - elif model.model_type == "deepseek_v2": - keys = set( - [ - "self_attn.q_proj", - "self_attn.q_a_proj", - "self_attn.q_b_proj", - "self_attn.kv_a_proj_with_mqa", - "self_attn.kv_b_proj", - ] - ) - elif model.model_type == "mamba": - keys = set( - [ - "mixer.in_proj", - "mixer.x_proj", - "mixer.dt_proj", - "mixer.out_proj", - ] - ) - elif model.model_type == "exaone": - keys = set(["attn.attention.q_proj", "attn.attention.v_proj"]) - else: - raise ValueError(f"Lora does not support {model.model_type}") - - for l in model.layers[-max(num_layers, 0) :]: - lora_layers = [(k, to_lora(m)) for k, m in l.named_modules() if k in keys] - if lora_layers: - l.update_modules(tree_unflatten(lora_layers)) - - lora_modules = [(k, to_lora(m)) for k, m in model.named_modules() if k in keys] - if lora_modules: - model.update_modules(tree_unflatten(lora_modules)) - - -def load_adapters(model: nn.Module, adapter_path: str) -> nn.Module: - """ - Load any fine-tuned adapters / layers. - - Args: - model (nn.Module): The neural network model. - adapter_path (str): Path to the adapter configuration file. - - Returns: - nn.Module: The updated model with LoRA layers applied. - """ - adapter_path = Path(adapter_path) - if not adapter_path.exists(): - raise FileNotFoundError(f"The adapter path does not exist: {adapter_path}") - with open(adapter_path / "adapter_config.json", "r") as fid: - config = types.SimpleNamespace(**json.load(fid)) - fine_tune_type = getattr(config, "fine_tune_type", "lora") - if fine_tune_type != "full": - linear_to_lora_layers( - model, - config.num_layers, - config.lora_parameters, - use_dora=(fine_tune_type == "dora"), - ) - model.load_weights(str(adapter_path / "adapters.safetensors"), strict=False) - return model - - -def dequantize(model: nn.Module) -> nn.Module: - """ - Dequantize the quantized linear layers in the model. - - Args: - model (nn.Module): The model with quantized linear layers. - - Returns: - nn.Module: The model with dequantized layers. - """ - de_quantize_layers = [] - for name, module in model.named_modules(): - if isinstance(module, nn.QuantizedLinear): - bias = "bias" in module - weight = module.weight - weight = mx.dequantize( - weight, - module.scales, - module.biases, - module.group_size, - module.bits, - ).astype(mx.float16) - output_dims, input_dims = weight.shape - linear = nn.Linear(input_dims, output_dims, bias=bias) - linear.weight = weight - if bias: - linear.bias = module.bias - de_quantize_layers.append((name, linear)) - if isinstance(module, nn.QuantizedEmbedding): - weight = mx.dequantize( - module.weight, - module.scales, - module.biases, - module.group_size, - module.bits, - ).astype(mx.float16) - num_embeddings, dims = weight.shape - emb = nn.Embedding(num_embeddings, dims) - emb.weight = weight - de_quantize_layers.append((name, emb)) - - if len(de_quantize_layers) > 0: - model.update_modules(tree_unflatten(de_quantize_layers)) - return model - - -def remove_lora_layers(model: nn.Module) -> nn.Module: - """ - Remove the LoRA layers from the model. - - Args: - model (nn.Module): The model with LoRA layers. - - Returns: - nn.Module: The model without LoRA layers. - """ - reset_layers = [] - for name, module in model.named_modules(): - if isinstance(module, LoRALinear): - reset_layers.append((name, module.linear)) - if len(reset_layers) > 0: - model.update_modules(tree_unflatten(reset_layers)) - return model - - -def nparams(module): - if hasattr(module, "bits"): - n = 0 if not hasattr(module, "bias") else module.bias.size - return n + module.weight.size * 32 // module.bits - return sum(v.size for _, v in tree_flatten(module.parameters())) - - -def print_trainable_parameters(model): - leaf_modules = tree_flatten( - model.leaf_modules(), is_leaf=lambda m: isinstance(m, nn.Module) - ) - total_p = sum(nparams(m) for _, m in leaf_modules) / 10**6 - trainable_p = ( - sum(v.size for _, v in tree_flatten(model.trainable_parameters())) / 10**6 - ) - print( - f"Trainable parameters: {(trainable_p * 100 / total_p):.3f}% " - f"({trainable_p:.3f}M/{total_p:.3f}M)" - ) diff --git a/llms/mlx_lm/utils.py b/llms/mlx_lm/utils.py deleted file mode 100644 index 05fac92f..00000000 --- a/llms/mlx_lm/utils.py +++ /dev/null @@ -1,1118 +0,0 @@ -# Copyright © 2023-2024 Apple Inc. - -import contextlib -import copy -import functools -import glob -import importlib -import json -import logging -import os -import shutil -import time -from dataclasses import dataclass -from pathlib import Path -from textwrap import dedent -from typing import ( - Any, - Callable, - Dict, - Generator, - List, - NamedTuple, - Optional, - Tuple, - Type, - Union, -) - -import mlx.core as mx -import mlx.nn as nn - -if os.getenv("MLXLM_USE_MODELSCOPE", "False").lower() == "true": - try: - from modelscope import snapshot_download - except ImportError: - raise ImportError( - "Please run `pip install modelscope` to activate the ModelScope." - ) -else: - from huggingface_hub import snapshot_download - -from mlx.utils import tree_flatten, tree_reduce -from transformers import PreTrainedTokenizer - -# Local imports -from .models import cache -from .sample_utils import make_logits_processors, make_sampler -from .tokenizer_utils import TokenizerWrapper, load_tokenizer -from .tuner.utils import dequantize as dequantize_model -from .tuner.utils import load_adapters, nparams - -# Constants -MODEL_REMAPPING = { - "mistral": "llama", # mistral is compatible with llama - "phi-msft": "phixtral", - "falcon_mamba": "mamba", -} - -MAX_FILE_SIZE_GB = 5 - -# A stream on the default device just for generation -generation_stream = mx.new_stream(mx.default_device()) - - -class ModelNotFoundError(Exception): - def __init__(self, message): - self.message = message - super().__init__(self.message) - - -@dataclass -class GenerationResponse: - """ - The output of :func:`stream_generate`. - - Args: - text (str): The next segment of decoded text. This can be an empty string. - token (int): The next token. - from_draft (bool): Whether the token was generated by the draft model. - logprobs (mx.array): A vector of log probabilities. - prompt_tokens (int): The number of tokens in the prompt. - prompt_tps (float): The prompt processing tokens-per-second. - generation_tokens (int): The number of generated tokens. - generation_tps (float): The tokens-per-second for generation. - peak_memory (float): The peak memory used so far in GB. - finish_reason (str): The reason the response is being sent: "length", "stop" or `None` - """ - - text: str - token: int - logprobs: mx.array - from_draft: bool - prompt_tokens: int - prompt_tps: float - generation_tokens: int - generation_tps: float - peak_memory: float - finish_reason: Optional[str] = None - - -@contextlib.contextmanager -def wired_limit(model: nn.Module, streams: Optional[List[mx.Stream]] = None): - """ - A context manager to temporarily change the wired limit. - - Note, the wired limit should not be changed during an async eval. If an - async eval could be running pass in the streams to synchronize with prior - to exiting the context manager. - """ - model_bytes = tree_reduce( - lambda acc, x: acc + x.nbytes if isinstance(x, mx.array) else acc, model, 0 - ) - max_rec_size = mx.metal.device_info()["max_recommended_working_set_size"] - if model_bytes > 0.9 * max_rec_size: - model_mb = model_bytes // 2**20 - max_rec_mb = max_rec_size // 2**20 - print( - f"[WARNING] Generating with a model that requires {model_mb} MB " - f"which is close to the maximum recommended size of {max_rec_mb} " - "MB. This can be slow. See the documentation for possible work-arounds: " - "https://github.com/ml-explore/mlx-examples/tree/main/llms#large-models" - ) - old_limit = mx.metal.set_wired_limit(max_rec_size) - try: - yield None - finally: - if streams is not None: - for s in streams: - mx.synchronize(s) - else: - mx.synchronize() - mx.metal.set_wired_limit(old_limit) - - -def _get_classes(config: dict): - """ - Retrieve the model and model args classes based on the configuration. - - Args: - config (dict): The model configuration. - - Returns: - A tuple containing the Model class and the ModelArgs class. - """ - model_type = config["model_type"] - model_type = MODEL_REMAPPING.get(model_type, model_type) - try: - arch = importlib.import_module(f"mlx_lm.models.{model_type}") - except ImportError: - msg = f"Model type {model_type} not supported." - logging.error(msg) - raise ValueError(msg) - - return arch.Model, arch.ModelArgs - - -def compute_bits_per_weight(model): - model_bytes = tree_reduce( - lambda acc, x: acc + x.nbytes if isinstance(x, mx.array) else acc, model, 0 - ) - leaf_modules = tree_flatten( - model.leaf_modules(), is_leaf=lambda m: isinstance(m, nn.Module) - ) - model_params = sum(nparams(m) for _, m in leaf_modules) - return model_bytes * 8 / model_params - - -def get_model_path(path_or_hf_repo: str, revision: Optional[str] = None) -> Path: - """ - Ensures the model is available locally. If the path does not exist locally, - it is downloaded from the Hugging Face Hub. - - Args: - path_or_hf_repo (str): The local path or Hugging Face repository ID of the model. - revision (str, optional): A revision id which can be a branch name, a tag, or a commit hash. - - Returns: - Path: The path to the model. - """ - model_path = Path(path_or_hf_repo) - - if not model_path.exists(): - try: - model_path = Path( - snapshot_download( - path_or_hf_repo, - revision=revision, - allow_patterns=[ - "*.json", - "*.safetensors", - "*.py", - "tokenizer.model", - "*.tiktoken", - "tiktoken.model", - "*.txt", - "*.jsonl", - ], - ) - ) - except: - raise ModelNotFoundError( - f"Model not found for path or HF repo: {path_or_hf_repo}.\n" - "Please make sure you specified the local path or Hugging Face" - " repo id correctly.\nIf you are trying to access a private or" - " gated Hugging Face repo, make sure you are authenticated:\n" - "https://huggingface.co/docs/huggingface_hub/en/guides/cli#huggingface-cli-login" - ) from None - return model_path - - -def maybe_quantize_kv_cache(prompt_cache, quantized_kv_start, kv_group_size, kv_bits): - if ( - kv_bits is not None - and not isinstance(prompt_cache[0], cache.QuantizedKVCache) - and prompt_cache[0].offset > quantized_kv_start - ): - for i in range(len(prompt_cache)): - if isinstance(prompt_cache[i], cache.KVCache): - prompt_cache[i] = prompt_cache[i].to_quantized( - group_size=kv_group_size, bits=kv_bits - ) - - -def generate_step( - prompt: mx.array, - model: nn.Module, - *, - max_tokens: int = 256, - sampler: Optional[Callable[mx.array, mx.array]] = None, - logits_processors: Optional[List[Callable[[mx.array, mx.array], mx.array]]] = None, - max_kv_size: Optional[int] = None, - prompt_cache: Optional[Any] = None, - prefill_step_size: int = 512, - kv_bits: Optional[int] = None, - kv_group_size: int = 64, - quantized_kv_start: int = 0, - prompt_progress_callback: Optional[Callable[int, int]] = None, -) -> Generator[Tuple[mx.array, mx.array], None, None]: - """ - A generator producing token ids based on the given prompt from the model. - - Args: - prompt (mx.array): The input prompt. - model (nn.Module): The model to use for generation. - max_tokens (int): The maximum number of tokens. Use``-1`` for an infinite - generator. Default: ``256``. - sampler (Callable[mx.array, mx.array], optional): A sampler for sampling a - token from a vector of log probabilities. Default: ``None``. - logits_processors (List[Callable[[mx.array, mx.array], mx.array]], optional): - A list of functions that take tokens and logits and return the processed - logits. Default: ``None``. - max_kv_size (int, optional): Maximum size of the key-value cache. Old - entries (except the first 4 tokens) will be overwritten. - prompt_cache (List[Any], optional): A pre-computed prompt cache. Note, if - provided, the cache will be updated in place. - prefill_step_size (int): Step size for processing the prompt. - kv_bits (int, optional): Number of bits to use for KV cache quantization. - None implies no cache quantization. Default: ``None``. - kv_group_size (int): Group size for KV cache quantization. Default: ``64``. - quantized_kv_start (int): Step to begin using a quantized KV cache. - when ``kv_bits`` is non-None. Default: ``0``. - prompt_prorgress_callback (Callable[int, int]): A call-back which takes the - prompt tokens processed so far and the total number of prompt tokens. - - Yields: - Tuple[mx.array, mx.array]: One token and a vector of log probabilities. - """ - - y = prompt - tokens = None - - # Create the KV cache for generation - if prompt_cache is None: - prompt_cache = cache.make_prompt_cache( - model, - max_kv_size=max_kv_size, - ) - elif len(prompt_cache) != len(model.layers): - raise ValueError("Wrong number of layers in the prompt cache.") - - prompt_progress_callback = prompt_progress_callback or (lambda *_: None) - - quantize_cache_fn = functools.partial( - maybe_quantize_kv_cache, - quantized_kv_start=quantized_kv_start, - kv_group_size=kv_group_size, - kv_bits=kv_bits, - ) - - sampler = sampler or (lambda x: mx.argmax(x, axis=-1)) - - def _step(y): - with mx.stream(generation_stream): - logits = model(y[None], cache=prompt_cache) - logits = logits[:, -1, :] - - if logits_processors: - nonlocal tokens - tokens = mx.concat([tokens, y]) if tokens is not None else y - - for processor in logits_processors: - logits = processor(tokens, logits) - - quantize_cache_fn(prompt_cache) - - logprobs = logits - mx.logsumexp(logits, keepdims=True) - y = sampler(logprobs) - return y, logprobs.squeeze(0) - - with mx.stream(generation_stream): - total_prompt_tokens = y.size - prompt_processed_tokens = 0 - while y.size > prefill_step_size: - model(y[:prefill_step_size][None], cache=prompt_cache) - quantize_cache_fn(prompt_cache) - mx.eval([c.state for c in prompt_cache]) - prompt_progress_callback(prompt_processed_tokens, total_prompt_tokens) - prompt_processed_tokens += prefill_step_size - y = y[prefill_step_size:] - mx.metal.clear_cache() - - y, logprobs = _step(y) - - mx.async_eval(y, logprobs) - n = 0 - while True: - if n != max_tokens: - next_y, next_logprobs = _step(y) - mx.async_eval(next_y, next_logprobs) - if n == 0: - mx.eval(y) - prompt_progress_callback(total_prompt_tokens, total_prompt_tokens) - if n == max_tokens: - break - yield y.item(), logprobs - if n % 256 == 0: - mx.metal.clear_cache() - y, logprobs = next_y, next_logprobs - n += 1 - - -def speculative_generate_step( - prompt: mx.array, - model: nn.Module, - draft_model: nn.Module, - *, - num_draft_tokens=2, - max_tokens: int = 256, - sampler: Optional[Callable[mx.array, mx.array]] = None, - logits_processors: Optional[List[Callable[[mx.array, mx.array], mx.array]]] = None, - prompt_cache: Optional[Any] = None, - prefill_step_size: int = 512, - kv_bits: Optional[int] = None, - kv_group_size: int = 64, - quantized_kv_start: int = 0, -) -> Generator[Tuple[mx.array, mx.array, bool], None, None]: - """ - A generator producing token ids based on the given prompt from the model. - - Args: - prompt (mx.array): The input prompt. - model (nn.Module): The model to use for generation. - draft_model (nn.Module): The draft model for speculative decoding. - num_draft_tokens (int, optional): The number of draft tokens for - speculative decoding. Default: ``2``. - max_tokens (int): The maximum number of tokens. Use``-1`` for an infinite - generator. Default: ``256``. - sampler (Callable[mx.array, mx.array], optional): A sampler for sampling a - token from a vector of log probabilities. Default: ``None``. - logits_processors (List[Callable[[mx.array, mx.array], mx.array]], optional): - A list of functions that take tokens and logits and return the processed - logits. Default: ``None``. - prompt_cache (List[Any], optional): A pre-computed prompt cache. Note, if - provided, the cache will be updated in place. The cache must be trimmable. - prefill_step_size (int): Step size for processing the prompt. - kv_bits (int, optional): Number of bits to use for KV cache quantization. - None implies no cache quantization. Default: ``None``. - kv_group_size (int): Group size for KV cache quantization. Default: ``64``. - quantized_kv_start (int): Step to begin using a quantized KV cache. - when ``kv_bits`` is non-None. Default: ``0``. - - Yields: - Tuple[mx.array, mx.array, bool]: One token, a vector of log probabilities, - and a bool indicating if the token was generated by the draft model - """ - - y = prompt.astype(mx.uint32) - prev_tokens = None - - # Create the KV cache for generation - if prompt_cache is None: - model_cache = cache.make_prompt_cache(model) - draft_cache = cache.make_prompt_cache(draft_model) - elif len(prompt_cache) != (len(model.layers) + len(draft_model.layers)): - raise ValueError("Wrong number of layers in the prompt cache.") - else: - model_cache = prompt_cache[: len(model.layers)] - draft_cache = prompt_cache[len(model.layers) :] - - sampler = sampler or (lambda x: mx.argmax(x, axis=-1)) - - quantize_cache_fn = functools.partial( - maybe_quantize_kv_cache, - quantized_kv_start=quantized_kv_start, - kv_group_size=kv_group_size, - kv_bits=kv_bits, - ) - - def _process_and_sample(tokens, logits): - if logits_processors: - for processor in logits_processors: - logits = processor(tokens, logits) - - logprobs = logits - mx.logsumexp(logits, axis=-1, keepdims=True) - y = sampler(logprobs) - return y, logprobs - - def _step(model, cache, y, n_predict=1): - with mx.stream(generation_stream): - logits = model(y[None], cache=cache) - logits = logits[:, -n_predict:, :] - - quantize_cache_fn(cache) - if logits_processors: - nonlocal prev_tokens - out_y, out_logprobs = [], [] - if n_predict > 1: - y = y[: -(n_predict - 1)] - for i in range(n_predict): - prev_tokens = ( - mx.concat([prev_tokens, y]) if prev_tokens is not None else y - ) - y, logprobs = _process_and_sample(prev_tokens, logits[:, i, :]) - out_y.append(y) - out_logprobs.append(logprobs) - return mx.concatenate(out_y, axis=0), mx.concatenate( - out_logprobs, axis=0 - ) - else: - return _process_and_sample(None, logits.squeeze(0)) - - def _prefill(model, cache, y): - while y.size > prefill_step_size: - model(y[:prefill_step_size][None], cache=cache) - quantize_cache_fn(cache) - mx.eval([c.state for c in cache]) - y = y[prefill_step_size:] - mx.metal.clear_cache() - return y - - def _rewind_cache(num_draft, num_accept): - cache.trim_prompt_cache(model_cache, num_draft - num_accept) - cache.trim_prompt_cache(draft_cache, max(num_draft - num_accept - 1, 0)) - - def _draft_generate(y, num_draft): - if num_draft == 0: - return mx.array([], mx.uint32) - ys = [] - for _ in range(num_draft): - y, _ = _step(draft_model, draft_cache, y) - mx.async_eval(y) - ys.append(y) - return mx.concatenate(ys) - - with mx.stream(generation_stream): - draft_y = _prefill(draft_model, draft_cache, y) - y = _prefill(model, model_cache, y) - - ntoks = 0 - # Set these so the finally block doesn't raise - num_draft = 0 - n = 0 - try: - while True: - num_draft = min(max_tokens - ntoks, num_draft_tokens) - draft_tokens = _draft_generate(draft_y, num_draft) - if prev_tokens is not None: - prev_tokens = prev_tokens[: prev_tokens.size - y.size - num_draft + 1] - y = mx.concatenate([y, draft_tokens]) - tokens, logprobs = _step(model, model_cache, y, num_draft + 1) - mx.eval(tokens, draft_tokens) - draft_tokens = draft_tokens.tolist() - tokens = tokens.tolist() - n = 0 - while n < num_draft: - tn, dtn, lpn = tokens[n], draft_tokens[n], logprobs[n] - if tn != dtn: - break - n += 1 - ntoks += 1 - yield tn, lpn, True - if ntoks == max_tokens: - break - if ntoks < max_tokens: - ntoks += 1 - yield tokens[n], logprobs[n], False - - if ntoks == max_tokens: - break - - y = mx.array([tokens[n]], mx.uint32) - draft_y = y - - # If we accepted all the draft tokens, include the last - # draft token in the next draft step since it hasn't been - # processed yet by the draft model - if n == num_draft: - draft_y = mx.concatenate( - [mx.array(draft_tokens[-1:], mx.uint32), draft_y] - ) - - if prev_tokens is not None: - prev_tokens = prev_tokens[: -max(num_draft - n, 1)] - _rewind_cache(num_draft, n) - finally: - _rewind_cache(num_draft, n) - - -def stream_generate( - model: nn.Module, - tokenizer: Union[PreTrainedTokenizer, TokenizerWrapper], - prompt: Union[str, mx.array, List[int]], - draft_model: Optional[nn.Module] = None, - **kwargs, -) -> Generator[GenerationResponse, None, None]: - """ - A generator producing text based on the given prompt from the model. - - Args: - model (nn.Module): The model to use for generation. - tokenizer (PreTrainedTokenizer): The tokenizer. - prompt (Union[str, mx.array, List[int]]): The input prompt string or - integer tokens. - draft_model (Optional[nn.Module]): An optional draft model. If provided - then speculative decoding is used. The draft model must use the same - tokenizer as the main model. Default: ``None``. - kwargs: The remaining options get passed to :func:`generate_step`. - See :func:`generate_step` for more details. - - Yields: - GenerationResponse: An instance containing the generated text segment and - associated metadata. See :class:`GenerationResponse` for details. - """ - if not isinstance(tokenizer, TokenizerWrapper): - tokenizer = TokenizerWrapper(tokenizer) - - if not isinstance(prompt, mx.array): - if isinstance(prompt, str): - # Try to infer if special tokens are needed - add_special_tokens = tokenizer.bos_token is None or not prompt.startswith( - tokenizer.bos_token - ) - prompt = tokenizer.encode(prompt, add_special_tokens=add_special_tokens) - prompt = mx.array(prompt) - - detokenizer = tokenizer.detokenizer - - if draft_model is None: - kwargs.pop("num_draft_tokens", None) - token_generator = generate_step(prompt, model, **kwargs) - # from_draft always false for non-speculative generation - token_generator = ( - (token, logprobs, False) for token, logprobs in token_generator - ) - else: - kwargs.pop("max_kv_size", None) - token_generator = speculative_generate_step( - prompt, model, draft_model, **kwargs - ) - with wired_limit(model, [generation_stream]): - detokenizer.reset() - tic = time.perf_counter() - for n, (token, logprobs, from_draft) in enumerate(token_generator): - if n == 0: - prompt_time = time.perf_counter() - tic - prompt_tps = prompt.size / prompt_time - tic = time.perf_counter() - if token in tokenizer.eos_token_ids: - break - - detokenizer.add_token(token) - - yield GenerationResponse( - text=detokenizer.last_segment, - token=token, - logprobs=logprobs, - from_draft=from_draft, - prompt_tokens=prompt.size, - prompt_tps=prompt_tps, - generation_tokens=n + 1, - generation_tps=(n + 1) / (time.perf_counter() - tic), - peak_memory=mx.metal.get_peak_memory() / 1e9, - finish_reason=None, - ) - - detokenizer.finalize() - yield GenerationResponse( - text=detokenizer.last_segment, - token=token, - logprobs=logprobs, - from_draft=from_draft, - prompt_tokens=prompt.size, - prompt_tps=prompt_tps, - generation_tokens=n + 1, - generation_tps=(n + 1) / (time.perf_counter() - tic), - peak_memory=mx.metal.get_peak_memory() / 1e9, - finish_reason="stop" if token in tokenizer.eos_token_ids else "length", - ) - - -def generate( - model: nn.Module, - tokenizer: Union[PreTrainedTokenizer, TokenizerWrapper], - prompt: Union[str, List[int]], - verbose: bool = False, - formatter: Optional[Callable] = None, - **kwargs, -) -> str: - """ - Generate a complete response from the model. - - Args: - model (nn.Module): The language model. - tokenizer (PreTrainedTokenizer): The tokenizer. - prompt (Union[str, List[int]]): The input prompt string or integer tokens. - verbose (bool): If ``True``, print tokens and timing information. - Default: ``False``. - kwargs: The remaining options get passed to :func:`stream_generate`. - See :func:`stream_generate` for more details. - """ - if formatter is not None: - print( - "[Warning] Text formatting is deprecated and no longer used. " - "The argument will be removed in a future version." - ) - if verbose: - print("=" * 10) - - text = "" - for response in stream_generate(model, tokenizer, prompt, **kwargs): - if verbose: - print(response.text, end="", flush=True) - text += response.text - - if verbose: - print() - print("=" * 10) - if len(text) == 0: - print("No text generated for this prompt") - return - print( - f"Prompt: {response.prompt_tokens} tokens, " - f"{response.prompt_tps:.3f} tokens-per-sec" - ) - print( - f"Generation: {response.generation_tokens} tokens, " - f"{response.generation_tps:.3f} tokens-per-sec" - ) - print(f"Peak memory: {response.peak_memory:.3f} GB") - return text - - -def load_config(model_path: Path) -> dict: - try: - with open(model_path / "config.json", "r") as f: - config = json.load(f) - except FileNotFoundError: - logging.error(f"Config file not found in {model_path}") - raise - return config - - -def load_model( - model_path: Path, - lazy: bool = False, - strict: bool = True, - model_config: dict = {}, - get_model_classes: Callable[[dict], Tuple[Type[nn.Module], Type]] = _get_classes, -) -> nn.Module: - """ - Load and initialize the model from a given path. - - Args: - model_path (Path): The path to load the model from. - lazy (bool): If False eval the model parameters to make sure they are - loaded in memory before returning, otherwise they will be loaded - when needed. Default: ``False`` - strict (bool): Whether or not to raise an exception if weights don't - match. Default: ``True`` - model_config (dict, optional): Optional configuration parameters for the - model. Defaults to an empty dictionary. - get_model_classes (Callable[[dict], Tuple[Type[nn.Module], Type]], optional): - A function that returns the model class and model args class given a config. - Defaults to the ``_get_classes`` function. - - Returns: - nn.Module: The loaded and initialized model. - - Raises: - FileNotFoundError: If the weight files (.safetensors) are not found. - ValueError: If the model class or args class are not found or cannot be instantiated. - """ - config = load_config(model_path) - config.update(model_config) - - weight_files = glob.glob(str(model_path / "model*.safetensors")) - - if not weight_files: - # Try weight for back-compat - weight_files = glob.glob(str(model_path / "weight*.safetensors")) - - if not weight_files and strict: - logging.error(f"No safetensors found in {model_path}") - raise FileNotFoundError(f"No safetensors found in {model_path}") - - weights = {} - for wf in weight_files: - weights.update(mx.load(wf)) - - model_class, model_args_class = get_model_classes(config=config) - - model_args = model_args_class.from_dict(config) - model = model_class(model_args) - - if hasattr(model, "sanitize"): - weights = model.sanitize(weights) - - if (quantization := config.get("quantization", None)) is not None: - - def class_predicate(p, m): - # Handle custom per layer quantizations - if p in config["quantization"]: - return config["quantization"][p] - if not hasattr(m, "to_quantized"): - return False - # Handle legacy models which may not have everything quantized - return f"{p}.scales" in weights - - nn.quantize( - model, - group_size=quantization["group_size"], - bits=quantization["bits"], - class_predicate=class_predicate, - ) - - model.load_weights(list(weights.items()), strict=strict) - - if not lazy: - mx.eval(model.parameters()) - - model.eval() - return model, config - - -def load( - path_or_hf_repo: str, - tokenizer_config={}, - model_config={}, - adapter_path: Optional[str] = None, - lazy: bool = False, -) -> Tuple[nn.Module, TokenizerWrapper]: - """ - Load the model and tokenizer from a given path or a huggingface repository. - - Args: - path_or_hf_repo (Path): The path or the huggingface repository to load the model from. - tokenizer_config (dict, optional): Configuration parameters specifically for the tokenizer. - Defaults to an empty dictionary. - model_config(dict, optional): Configuration parameters specifically for the model. - Defaults to an empty dictionary. - adapter_path (str, optional): Path to the LoRA adapters. If provided, applies LoRA layers - to the model. Default: ``None``. - lazy (bool): If ``False`` eval the model parameters to make sure they are - loaded in memory before returning, otherwise they will be loaded - when needed. Default: ``False`` - Returns: - Tuple[nn.Module, TokenizerWrapper]: A tuple containing the loaded model and tokenizer. - - Raises: - FileNotFoundError: If config file or safetensors are not found. - ValueError: If model class or args class are not found. - """ - model_path = get_model_path(path_or_hf_repo) - - model, config = load_model(model_path, lazy) - if adapter_path is not None: - model = load_adapters(model, adapter_path) - model.eval() - tokenizer = load_tokenizer( - model_path, tokenizer_config, eos_token_ids=config.get("eos_token_id", None) - ) - - return model, tokenizer - - -def fetch_from_hub( - model_path: Path, lazy: bool = False -) -> Tuple[nn.Module, dict, PreTrainedTokenizer]: - model, config = load_model(model_path, lazy) - tokenizer = load_tokenizer( - model_path, eos_token_ids=config.get("eos_token_id", None) - ) - return model, config, tokenizer - - -def make_shards(weights: dict, max_file_size_gb: int = MAX_FILE_SIZE_GB) -> list: - """ - Splits the weights into smaller shards. - - Args: - weights (dict): Model weights. - max_file_size_gb (int): Maximum size of each shard in gigabytes. - - Returns: - list: List of weight shards. - """ - max_file_size_bytes = max_file_size_gb << 30 - shards = [] - shard, shard_size = {}, 0 - for k, v in weights.items(): - if shard_size + v.nbytes > max_file_size_bytes: - shards.append(shard) - shard, shard_size = {}, 0 - shard[k] = v - shard_size += v.nbytes - shards.append(shard) - return shards - - -def upload_to_hub(path: str, upload_repo: str, hf_path: str): - """ - Uploads the model to Hugging Face hub. - - Args: - path (str): Local path to the model. - upload_repo (str): Name of the HF repo to upload to. - hf_path (str): Path to the original Hugging Face model. - """ - import os - - from huggingface_hub import HfApi, ModelCard, logging - - from . import __version__ - - card = ModelCard.load(hf_path) - card.data.tags = ["mlx"] if card.data.tags is None else card.data.tags + ["mlx"] - card.data.base_model = hf_path - card.text = dedent( - f""" - # {upload_repo} - - The Model [{upload_repo}](https://huggingface.co/{upload_repo}) was - converted to MLX format from [{hf_path}](https://huggingface.co/{hf_path}) - using mlx-lm version **{__version__}**. - - ## Use with mlx - - ```bash - pip install mlx-lm - ``` - - ```python - from mlx_lm import load, generate - - model, tokenizer = load("{upload_repo}") - - prompt = "hello" - - if tokenizer.chat_template is not None: - messages = [{{"role": "user", "content": prompt}}] - prompt = tokenizer.apply_chat_template( - messages, add_generation_prompt=True - ) - - response = generate(model, tokenizer, prompt=prompt, verbose=True) - ``` - """ - ) - card.save(os.path.join(path, "README.md")) - - logging.set_verbosity_info() - - api = HfApi() - api.create_repo(repo_id=upload_repo, exist_ok=True) - api.upload_large_folder( - folder_path=path, - repo_id=upload_repo, - repo_type="model", - ) - print(f"Upload successful, go to https://huggingface.co/{upload_repo} for details.") - - -def save_weights( - save_path: Union[str, Path], - weights: Dict[str, Any], - *, - donate_weights: bool = False, -) -> None: - """Save model weights into specified directory.""" - if isinstance(save_path, str): - save_path = Path(save_path) - save_path.mkdir(parents=True, exist_ok=True) - - shards = make_shards(weights) - shards_count = len(shards) - shard_file_format = ( - "model-{:05d}-of-{:05d}.safetensors" - if shards_count > 1 - else "model.safetensors" - ) - - total_size = sum(v.nbytes for v in weights.values()) - index_data = {"metadata": {"total_size": total_size}, "weight_map": {}} - - # Write the weights and make sure no references are kept other than the - # necessary ones - if donate_weights: - weights.clear() - del weights - - for i in range(len(shards)): - shard = shards[i] - shards[i] = None - shard_name = shard_file_format.format(i + 1, shards_count) - shard_path = save_path / shard_name - - mx.save_safetensors(str(shard_path), shard, metadata={"format": "mlx"}) - - for weight_name in shard.keys(): - index_data["weight_map"][weight_name] = shard_name - del shard - - index_data["weight_map"] = { - k: index_data["weight_map"][k] for k in sorted(index_data["weight_map"]) - } - - with open(save_path / "model.safetensors.index.json", "w") as f: - json.dump( - index_data, - f, - indent=4, - ) - - -def quantize_model( - model: nn.Module, - config: dict, - q_group_size: int, - q_bits: int, - quant_predicate: Optional[ - Callable[[str, nn.Module, dict], Union[bool, dict]] - ] = None, -) -> Tuple: - """ - Applies quantization to the model weights. - - Args: - model (nn.Module): The model to be quantized. - config (dict): Model configuration. - q_group_size (int): Group size for quantization. - q_bits (int): Bits per weight for quantization. - quant_predicate (Callable): A callable that decides how - to quantize each layer based on the path. - Accepts the layer `path`, the `module` and the model `config`. - Returns either a bool to signify quantize/no quantize or - a dict of quantization parameters to pass to `to_quantized`. - - Returns: - Tuple: Tuple containing quantized weights and config. - """ - quantized_config = copy.deepcopy(config) - quantized_config["quantization"] = {"group_size": q_group_size, "bits": q_bits} - - # Add any custom quantization parameters to the config as we go - def _class_predicate(p, m): - bool_or_params = quant_predicate(p, m, config) - quantized_config["quantization"][p] = bool_or_params - return bool_or_params - - nn.quantize( - model, - q_group_size, - q_bits, - class_predicate=_class_predicate if quant_predicate else None, - ) - # support hf model tree #957 - quantized_config["quantization_config"] = quantized_config["quantization"] - quantized_weights = dict(tree_flatten(model.parameters())) - - bpw = compute_bits_per_weight(model) - print(f"[INFO] Quantized model with {bpw:.3f} bits per weight.") - - return quantized_weights, quantized_config - - -def save_config( - config: dict, - config_path: Union[str, Path], -) -> None: - """Save the model configuration to the ``config_path``. - - The final configuration will be sorted before saving for better readability. - - Args: - config (dict): The model configuration. - config_path (Union[str, Path]): Model configuration file path. - """ - # Clean unused keys - config.pop("_name_or_path", None) - - # sort the config for better readability - config = dict(sorted(config.items())) - - # write the updated config to the config_path (if provided) - with open(config_path, "w") as fid: - json.dump(config, fid, indent=4) - - -def mixed_quant_predicate_builder( - low_bits: int = 4, high_bits: int = 4, group_size: int = 64 -) -> Callable[[str, nn.Module, dict], Union[bool, dict]]: - def mixed_quant_predicate( - path: str, - module: nn.Module, - config: dict, - ) -> Union[bool, dict]: - """Implements mixed quantization predicates with similar choices to, for example, llama.cpp's Q4_K_M. - Ref: https://github.com/ggerganov/llama.cpp/blob/917786f43d0f29b7c77a0c56767c0fa4df68b1c5/src/llama.cpp#L5265 - By Alex Barron: https://gist.github.com/barronalex/84addb8078be21969f1690c1454855f3 - """ - - if not hasattr(module, "to_quantized"): - return False - - index = int(path.split(".")[2]) if len(path.split(".")) > 2 else 0 - - num_layers = config["num_hidden_layers"] - use_more_bits = ( - index < num_layers // 8 - or index >= 7 * num_layers // 8 - or (index - num_layers // 8) % 3 == 2 - ) - if "v_proj" in path and use_more_bits: - return {"group_size": group_size, "bits": high_bits} - if "down_proj" in path and use_more_bits: - return {"group_size": group_size, "bits": high_bits} - if "lm_head" in path: - return {"group_size": group_size, "bits": high_bits} - - return {"group_size": group_size, "bits": low_bits} - - return mixed_quant_predicate - - -mixed_3_6 = mixed_quant_predicate_builder(low_bits=3) -mixed_2_6 = mixed_quant_predicate_builder(low_bits=2) - - -def convert( - hf_path: str, - mlx_path: str = "mlx_model", - quantize: bool = False, - q_group_size: int = 64, - q_bits: int = 4, - dtype: str = "float16", - upload_repo: str = None, - revision: Optional[str] = None, - dequantize: bool = False, - quant_predicate: Optional[ - Callable[[str, nn.Module, dict], Union[bool, dict]] - ] = None, -): - # Check the save path is empty - if isinstance(mlx_path, str): - mlx_path = Path(mlx_path) - - if mlx_path.exists(): - raise ValueError( - f"Cannot save to the path {mlx_path} as it already exists." - " Please delete the file/directory or specify a new path to save to." - ) - - print("[INFO] Loading") - model_path = get_model_path(hf_path, revision=revision) - model, config, tokenizer = fetch_from_hub(model_path, lazy=True) - - weights = dict(tree_flatten(model.parameters())) - dtype = getattr(mx, dtype) - weights = {k: v.astype(dtype) for k, v in weights.items()} - - if quantize and dequantize: - raise ValueError("Choose either quantize or dequantize, not both.") - - if quantize: - print("[INFO] Quantizing") - model.load_weights(list(weights.items())) - weights, config = quantize_model( - model, config, q_group_size, q_bits, quant_predicate=quant_predicate - ) - - if dequantize: - print("[INFO] Dequantizing") - model = dequantize_model(model) - weights = dict(tree_flatten(model.parameters())) - - del model - save_weights(mlx_path, weights, donate_weights=True) - - py_files = glob.glob(str(model_path / "*.py")) - for file in py_files: - shutil.copy(file, mlx_path) - - tokenizer.save_pretrained(mlx_path) - - save_config(config, config_path=mlx_path / "config.json") - - if upload_repo is not None: - upload_to_hub(mlx_path, upload_repo, hf_path) diff --git a/llms/setup.py b/llms/setup.py deleted file mode 100644 index e6fddbae..00000000 --- a/llms/setup.py +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright © 2024 Apple Inc. - -import sys -from pathlib import Path - -from setuptools import setup - -package_dir = Path(__file__).parent / "mlx_lm" -with open(package_dir / "requirements.txt") as fid: - requirements = [l.strip() for l in fid.readlines()] - -sys.path.append(str(package_dir)) -from _version import __version__ - -setup( - name="mlx-lm", - version=__version__, - description="LLMs on Apple silicon with MLX and the Hugging Face Hub", - long_description=open("README.md", encoding="utf-8").read(), - long_description_content_type="text/markdown", - readme="README.md", - author_email="mlx@group.apple.com", - author="MLX Contributors", - url="https://github.com/ml-explore/mlx-examples", - license="MIT", - install_requires=requirements, - packages=["mlx_lm", "mlx_lm.models", "mlx_lm.tuner"], - python_requires=">=3.8", - extras_require={ - "test": ["datasets"], - "evaluate": ["lm-eval", "tqdm"], - }, - entry_points={ - "console_scripts": [ - "mlx_lm.cache_prompt = mlx_lm.cache_prompt:main", - "mlx_lm.chat = mlx_lm.chat:main", - "mlx_lm.convert = mlx_lm.convert:main", - "mlx_lm.evaluate = mlx_lm.evaluate:main", - "mlx_lm.fuse = mlx_lm.fuse:main", - "mlx_lm.generate = mlx_lm.generate:main", - "mlx_lm.lora = mlx_lm.lora:main", - "mlx_lm.merge = mlx_lm.merge:main", - "mlx_lm.server = mlx_lm.server:main", - "mlx_lm.manage = mlx_lm.manage:main", - ] - }, -) diff --git a/llms/tests/test_datsets.py b/llms/tests/test_datsets.py deleted file mode 100644 index 5edab8bf..00000000 --- a/llms/tests/test_datsets.py +++ /dev/null @@ -1,113 +0,0 @@ -# Copyright © 2024 Apple Inc. - -import json -import os -import tempfile -import types -import unittest - -from mlx_lm.tuner import datasets -from transformers import AutoTokenizer - -HF_MODEL_PATH = "mlx-community/Qwen1.5-0.5B-Chat-4bit" - - -class TestDatasets(unittest.TestCase): - - @classmethod - def setUpClass(cls): - cls.test_dir_fid = tempfile.TemporaryDirectory() - cls.test_dir = cls.test_dir_fid.name - if not os.path.isdir(cls.test_dir): - os.mkdir(cls.test_dir_fid.name) - - @classmethod - def tearDownClass(cls): - cls.test_dir_fid.cleanup() - - def save_data(self, data): - for ds in ["train", "valid"]: - with open(os.path.join(self.test_dir, f"{ds}.jsonl"), "w") as fid: - for l in data: - json.dump(l, fid) - fid.write("\n") - - def test_text(self): - data = {"text": "This is an example for the model."} - self.save_data(4 * [data]) - args = types.SimpleNamespace(train=True, test=False, data=self.test_dir) - tokenizer = AutoTokenizer.from_pretrained(HF_MODEL_PATH) - train, valid, test = datasets.load_dataset(args, tokenizer) - self.assertEqual(len(train), 4) - self.assertEqual(len(valid), 4) - self.assertEqual(len(test), 0) - self.assertTrue(len(train[0]) > 0) - self.assertTrue(len(valid[0]) > 0) - self.assertTrue(isinstance(train, datasets.Dataset)) - - def test_completions(self): - data = {"prompt": "What is the capital of France?", "completion": "Paris."} - self.save_data(4 * [data]) - args = types.SimpleNamespace(train=True, test=False, data=self.test_dir) - tokenizer = AutoTokenizer.from_pretrained(HF_MODEL_PATH) - train, valid, test = datasets.load_dataset(args, tokenizer) - self.assertEqual(len(train), 4) - self.assertEqual(len(valid), 4) - self.assertEqual(len(test), 0) - self.assertTrue(len(train[0]) > 0) - self.assertTrue(len(valid[0]) > 0) - self.assertTrue(isinstance(train, datasets.CompletionsDataset)) - - def test_chat(self): - data = { - "messages": [ - {"role": "system", "content": "You are a helpful assistant."}, - {"role": "user", "content": "Hello."}, - {"role": "assistant", "content": "How can I assistant you today."}, - ] - } - self.save_data(4 * [data]) - args = types.SimpleNamespace(train=True, test=False, data=self.test_dir) - tokenizer = AutoTokenizer.from_pretrained(HF_MODEL_PATH) - train, valid, test = datasets.load_dataset(args, tokenizer) - self.assertEqual(len(train), 4) - self.assertEqual(len(valid), 4) - self.assertEqual(len(test), 0) - self.assertTrue(len(train[0]) > 0) - self.assertTrue(len(valid[0]) > 0) - self.assertTrue(isinstance(train, datasets.ChatDataset)) - - def test_hf(self): - hf_args = { - "name": "billsum", - "prompt_feature": "text", - "completion_feature": "summary", - "train_split": "train[:2%]", - "valid_split": "train[-2%:]", - } - args = types.SimpleNamespace( - hf_dataset=hf_args, - test=False, - train=True, - ) - tokenizer = AutoTokenizer.from_pretrained(HF_MODEL_PATH) - train, valid, test = datasets.load_dataset(args, tokenizer) - self.assertTrue(len(train) > 0) - self.assertTrue(len(train[0]) > 0) - self.assertTrue(len(valid) > 0) - self.assertTrue(len(valid[0]) > 0) - self.assertEqual(len(test), 0) - - args = types.SimpleNamespace( - hf_dataset=[hf_args, hf_args], - test=False, - train=True, - ) - train_double, valid_double, test_double = datasets.load_dataset(args, tokenizer) - self.assertEqual(2 * len(train), len(train_double)) - self.assertEqual(2 * len(valid), len(valid_double)) - self.assertEqual(2 * len(test), len(test_double)) - - -if __name__ == "__main__": - unittest.main() diff --git a/llms/tests/test_finetune.py b/llms/tests/test_finetune.py deleted file mode 100644 index a6d53747..00000000 --- a/llms/tests/test_finetune.py +++ /dev/null @@ -1,447 +0,0 @@ -# Copyright © 2024 Apple Inc. - -import math -import sys -import unittest -from contextlib import contextmanager -from io import StringIO -from unittest.mock import MagicMock - -import mlx.core as mx -import mlx.nn as nn -import mlx.optimizers as opt -from mlx.utils import tree_flatten -from mlx_lm import lora, tuner -from mlx_lm.tuner.dora import DoRAEmbedding, DoRALinear -from mlx_lm.tuner.lora import LoRAEmbedding, LoRALinear -from mlx_lm.tuner.trainer import evaluate -from mlx_lm.tuner.utils import build_schedule - - -@contextmanager -def swapped_with_identity(obj, func): - old_func = getattr(obj, func) - setattr(obj, func, lambda x, **kwargs: x) - yield - setattr(obj, func, old_func) - - -class TestLora(unittest.TestCase): - def setUp(self): - self.capturedOutput = StringIO() - sys.stdout = self.capturedOutput - - def tearDown(self): - sys.stdout = sys.__stdout__ - - def test_llama(self): - from mlx_lm.models import llama - - args = llama.ModelArgs( - model_type="llama", - hidden_size=1024, - num_hidden_layers=4, - intermediate_size=2048, - num_attention_heads=4, - rms_norm_eps=1e-5, - vocab_size=10_000, - tie_word_embeddings=False, - ) - - lora_layers = 4 - - def check_config(params, expected_trainable_parameters=None): - n_keys = 2 - if "keys" in params: - n_keys = len(params["keys"]) - model = llama.Model(args) - model.freeze() - tuner.utils.linear_to_lora_layers(model, lora_layers, params) - trainable_params = sum( - v.size for _, v in tree_flatten(model.trainable_parameters()) - ) - - expected_trainable_parameters = expected_trainable_parameters or ( - lora_layers * params["rank"] * args.hidden_size * 2 * n_keys - ) - self.assertEqual(trainable_params, expected_trainable_parameters) - - params = {"rank": 8, "alpha": 16, "dropout": 0.0, "scale": 10.0} - check_config(params) - - params["rank"] = 1 - check_config(params) - - params["keys"] = ["self_attn.k_proj"] - check_config(params) - - params["keys"] = ["lm_head"] - check_config( - params, - expected_trainable_parameters=( - params["rank"] * (args.hidden_size + args.vocab_size) - ), - ) - - params["keys"] = ["model.embed_tokens"] - check_config( - params, - expected_trainable_parameters=( - params["rank"] * (args.hidden_size + args.vocab_size) - ), - ) - - def test_gpt_neox(self): - from mlx_lm.models import gpt_neox - - args = gpt_neox.ModelArgs( - model_type="gpt_neox", - max_position_embeddings=2048, - hidden_size=6144, - num_attention_heads=64, - num_hidden_layers=44, - layer_norm_eps=1e-5, - vocab_size=50432, - rotary_emb_base=10_000, - rotary_pct=0.25, - ) - - num_lora_layers = 4 - params = {"rank": 8, "alpha": 16, "dropout": 0.0, "scale": 10.0} - - model = gpt_neox.Model(args) - model.freeze() - tuner.utils.linear_to_lora_layers(model, num_lora_layers, params) - - def test_lora_embedding(self): - num_embeddings = 256 - dims = 512 - tokens = mx.array([1, 2, 3]) - - embedding = nn.QuantizedEmbedding(num_embeddings, dims) - dequantized_weight = mx.dequantize( - embedding.weight, - embedding.scales, - embedding.biases, - embedding.group_size, - embedding.bits, - ) - lora_emb = LoRAEmbedding.from_base(embedding, r=8, dropout=0, scale=10) - new_embedding = lora_emb.fuse(de_quantize=True) - self.assertTrue(mx.array_equal(dequantized_weight, new_embedding.weight)) - self.assertTrue(mx.array_equal(embedding(tokens), lora_emb(tokens))) - - # as_linear - attn_output = mx.random.uniform(shape=(dims,)) - embedding_lin_out = lora_emb.as_linear(attn_output) - self.assertEqual(embedding_lin_out.shape, (num_embeddings,)) - self.assertTrue( - mx.array_equal(embedding_lin_out, embedding.as_linear(attn_output)) - ) - - # change the value of lora_b and the embeddings will no longer be equal - lora_emb.lora_b = mx.random.uniform(shape=lora_emb.lora_b.shape) - new_embedding = lora_emb.fuse(de_quantize=True) - self.assertFalse(mx.array_equal(dequantized_weight, new_embedding.weight)) - self.assertFalse(mx.array_equal(embedding(tokens), lora_emb(tokens))) - - -class TestDora(unittest.TestCase): - def test_dora_embedding(self): - num_embeddings = 256 - dims = 512 - tokens = mx.array([1, 2, 3]) - - embedding = nn.Embedding(num_embeddings, dims) - - dora_emb = DoRAEmbedding.from_base(embedding, r=8, dropout=0, scale=10) - new_embedding = dora_emb.fuse() - self.assertTrue(mx.array_equal(embedding.weight, new_embedding.weight)) - self.assertTrue(mx.array_equal(embedding(tokens), dora_emb(tokens))) - - # as_linear - attn_output = mx.random.uniform(shape=(dims,)) - embedding_lin_out = dora_emb.as_linear(attn_output) - self.assertEqual(embedding_lin_out.shape, (num_embeddings,)) - self.assertTrue( - mx.array_equal(embedding_lin_out, embedding.as_linear(attn_output)) - ) - - # change the value of lora_b and the embeddings will no longer be equal - dora_emb.lora_b = mx.random.uniform(shape=dora_emb.lora_b.shape) - new_embedding = dora_emb.fuse() - self.assertFalse(mx.array_equal(embedding.weight, new_embedding.weight)) - self.assertFalse(mx.array_equal(embedding(tokens), dora_emb(tokens))) - - def test_llama(self): - from mlx_lm.models import llama - - hidden_size = 1024 - intermediate_size = 2048 - args = llama.ModelArgs( - model_type="llama", - hidden_size=hidden_size, - num_hidden_layers=4, - intermediate_size=intermediate_size, - num_attention_heads=4, - rms_norm_eps=1e-5, - vocab_size=10_000, - ) - - dora_layers = 4 - - def check_config(params): - n_keys = 2 - if "keys" in params: - n_keys = len(params["keys"]) - model = llama.Model(args) - model.freeze() - tuner.utils.linear_to_lora_layers(model, dora_layers, params, use_dora=True) - trainable_params = sum( - v.size for _, v in tree_flatten(model.trainable_parameters()) - ) - self.assertEqual( - trainable_params, - dora_layers - * (params["rank"] * hidden_size * 2 * n_keys + n_keys * hidden_size), - ) - - params = {"rank": 8, "alpha": 16, "dropout": 0.0, "scale": 10.0} - check_config(params) - - params["rank"] = 1 - check_config(params) - - params["keys"] = ["self_attn.k_proj"] - check_config(params) - - def test_dora_m_parameter(self): - dora_lin = DoRALinear(input_dims=100, output_dims=100) - self.assertTrue( - mx.allclose(dora_lin.m, mx.linalg.norm(dora_lin.linear.weight, axis=1)) - ) - - # Recomputes m when changing Linear - inital_m = dora_lin.m - lin = nn.Linear(10, 10) - dora_lin.set_linear(lin) - self.assertTrue(mx.allclose(dora_lin.m, mx.linalg.norm(lin.weight, axis=1))) - - # Works with quantized weights - quantized_linear = nn.QuantizedLinear(512, 512) - dora_lin.set_linear(quantized_linear) - dequantized_weight = mx.dequantize( - quantized_linear.weight, - quantized_linear.scales, - quantized_linear.biases, - quantized_linear.group_size, - quantized_linear.bits, - ) - self.assertTrue( - mx.allclose(dora_lin.m, mx.linalg.norm(dequantized_weight, axis=1)) - ) - - def test_dora_from_linear(self): - in_dims = 256 - out_dims = 256 - r = 4 - - linear = nn.Linear(in_dims, out_dims) - dora_lin = DoRALinear.from_base(linear, r) - self.assertTrue(mx.allclose(dora_lin.m, mx.linalg.norm(linear.weight, axis=1))) - self.assertEqual(dora_lin.lora_a.shape, (in_dims, r)) - self.assertEqual(dora_lin.lora_b.shape, (r, out_dims)) - self.assertEqual(dora_lin.m.shape, (out_dims,)) - - quantized_linear = nn.QuantizedLinear(in_dims, out_dims) - dequantized_weight = mx.dequantize( - quantized_linear.weight, - quantized_linear.scales, - quantized_linear.biases, - quantized_linear.group_size, - quantized_linear.bits, - ) - dora_quant_lin = DoRALinear.from_base(quantized_linear, r) - self.assertTrue( - mx.allclose(dora_quant_lin.m, mx.linalg.norm(dequantized_weight, axis=1)) - ) - self.assertEqual(dora_quant_lin.lora_a.shape, (in_dims, r)) - self.assertEqual(dora_quant_lin.lora_b.shape, (r, out_dims)) - self.assertEqual(dora_quant_lin.m.shape, (out_dims,)) - - def test_dora_to_linear(self): - in_dims = 256 - out_dims = 256 - r = 4 - - linear = nn.Linear(in_dims, out_dims, bias=True) - dora_lin = DoRALinear.from_base(linear, r) - to_linear = dora_lin.fuse() - self.assertTrue(mx.allclose(linear.weight, to_linear.weight)) - self.assertTrue(mx.allclose(linear.bias, to_linear.bias)) - - def dequantize_weight(quantized_linear): - return mx.dequantize( - quantized_linear.weight, - quantized_linear.scales, - quantized_linear.biases, - quantized_linear.group_size, - quantized_linear.bits, - ) - - quantized_linear = nn.QuantizedLinear(in_dims, out_dims, bias=True) - dora_quantized_linear = DoRALinear.from_base(quantized_linear, r) - # Dequantize - to_linear_from_quantized = dora_quantized_linear.fuse(de_quantize=True) - self.assertTrue( - mx.allclose(quantized_linear.bias, to_linear_from_quantized.bias) - ) - self.assertTrue( - mx.allclose( - dequantize_weight(quantized_linear), to_linear_from_quantized.weight - ) - ) - - def test_dora_dtype(self): - in_dims = 256 - out_dims = 256 - r = 4 - - linear = nn.Linear(in_dims, out_dims, bias=True) - linear.set_dtype(mx.float16) - dora_lin = DoRALinear.from_base(linear, r) - - x = mx.random.uniform(shape=(2, 256)).astype(mx.float16) - self.assertEqual(dora_lin(x).dtype, mx.float16) - - -class TestScheduleConfig(unittest.TestCase): - def test_join(self): - config = {"name": "cosine_decay", "warmup": 100, "arguments": [1e-5, 100]} - cos_with_warmup = build_schedule(config) - self.assertIsNotNone(cos_with_warmup) - - self.assertEqual(cos_with_warmup(0), 0.0) - self.assertAlmostEqual(cos_with_warmup(101), 1e-5, delta=1e-1) - optimizer = opt.Adam(learning_rate=cos_with_warmup) - for _ in range(100): - optimizer.update({}, {}) - self.assertAlmostEqual(optimizer.learning_rate.item(), 1e-5, delta=1e-1) - for _ in range(100): - optimizer.update({}, {}) - expected_lr = 1e-5 * 0.5 * (1.0 + math.cos(math.pi * 200 / 10)) - self.assertAlmostEqual(optimizer.learning_rate.item(), expected_lr, delta=1e-1) - - def test_single_schedule(self): - - config = { - "name": "cosine_decay", - "arguments": [0.1, 10], - } - lr_schedule = build_schedule(config) - lr = lr_schedule(4) - expected_lr = 0.1 * 0.5 * (1.0 + math.cos(math.pi * 4 / 10)) - self.assertAlmostEqual(lr, expected_lr, delta=1e-7) - - def test_non_zero_warmup(self): - config = { - "name": "cosine_decay", - "warmup": 10, - "warmup_init": 1e-6, - "arguments": [1e-5, 20], - } - lr_schedule = build_schedule(config) - lr = lr_schedule(0) - self.assertAlmostEqual(lr, 1e-6, delta=1e-7) - - def test_malformed_config(self): - config = {"warmup": 100} - self.assertRaises(KeyError, build_schedule, config) - - config = {"cosine_decay": None} - self.assertRaises(KeyError, build_schedule, config) - - def test_evaluate_calls(self): - mock_model = MagicMock() - mock_dataset = MagicMock() - mock_tokenizer = MagicMock() - mock_default_loss = MagicMock() - mock_iterate_batches = MagicMock() - - mock_iterate_batches.return_value = [ - (MagicMock(), MagicMock()), - (MagicMock(), MagicMock()), - (MagicMock(), MagicMock()), - (MagicMock(), MagicMock()), - (MagicMock(), MagicMock()), - ] - - mock_default_loss.side_effect = [ - (MagicMock(return_value=0.5), MagicMock(return_value=100)), - (MagicMock(return_value=0.3), MagicMock(return_value=200)), - (MagicMock(return_value=0.2), MagicMock(return_value=150)), - (MagicMock(return_value=0.4), MagicMock(return_value=180)), - (MagicMock(return_value=0.6), MagicMock(return_value=120)), - ] - with swapped_with_identity(mx.distributed, "all_sum"): - evaluate( - model=mock_model, - dataset=mock_dataset, - tokenizer=mock_tokenizer, - batch_size=2, - num_batches=2, - max_seq_length=2048, - loss=mock_default_loss, - iterate_batches=mock_iterate_batches, - ) - - mock_iterate_batches.assert_called_once_with( - dataset=mock_dataset, - tokenizer=mock_tokenizer, - batch_size=2, - max_seq_length=2048, - ) - self.assertEqual(mock_default_loss.call_count, 2) - - def test_evaluate_infinite_batches(self): - mock_model = MagicMock() - mock_dataset = MagicMock() - mock_tokenizer = MagicMock() - mock_default_loss = MagicMock() - mock_iterate_batches = MagicMock() - - mock_iterate_batches.return_value = [ - (MagicMock(), MagicMock()), - (MagicMock(), MagicMock()), - (MagicMock(), MagicMock()), - ] - - mock_default_loss.side_effect = [ - (MagicMock(return_value=0.5), MagicMock(return_value=100)), - (MagicMock(return_value=0.3), MagicMock(return_value=200)), - (MagicMock(return_value=0.2), MagicMock(return_value=150)), - ] - - with swapped_with_identity(mx.distributed, "all_sum"): - evaluate( - model=mock_model, - dataset=mock_dataset, - tokenizer=mock_tokenizer, - batch_size=2, - num_batches=-1, - max_seq_length=2048, - loss=mock_default_loss, - iterate_batches=mock_iterate_batches, - ) - - mock_iterate_batches.assert_called_once_with( - dataset=mock_dataset, - tokenizer=mock_tokenizer, - batch_size=2, - max_seq_length=2048, - ) - self.assertEqual(mock_default_loss.call_count, 3) - - -if __name__ == "__main__": - unittest.main() diff --git a/llms/tests/test_generate.py b/llms/tests/test_generate.py deleted file mode 100644 index 7445a9b9..00000000 --- a/llms/tests/test_generate.py +++ /dev/null @@ -1,91 +0,0 @@ -# Copyright © 2024 Apple Inc. - -import unittest -from typing import List - -from mlx_lm.sample_utils import make_logits_processors -from mlx_lm.utils import ( - GenerationResponse, - generate, - load, - make_sampler, - stream_generate, -) - - -class TestGenerate(unittest.TestCase): - - @classmethod - def setUpClass(cls): - cls.HF_MODEL_PATH = "mlx-community/Qwen1.5-0.5B-Chat-4bit" - cls.model, cls.tokenizer = load(cls.HF_MODEL_PATH) - - def test_generate(self): - # Simple test that generation runs - text = generate( - self.model, self.tokenizer, "hello", max_tokens=5, verbose=False - ) - - def test_generate_with_logit_bias(self): - logit_bias = {0: 2000.0, 1: -20.0} - text = generate( - self.model, - self.tokenizer, - "hello", - max_tokens=5, - logits_processors=make_logits_processors(logit_bias), - verbose=False, - ) - self.assertEqual(text, "!!!!!") - - def test_generate_with_processor(self): - init_toks = self.tokenizer.encode("hello") - - all_toks = None - - def logits_processor(toks, logits): - nonlocal all_toks - all_toks = toks - return logits - - generate( - self.model, - self.tokenizer, - "hello", - max_tokens=5, - verbose=False, - logits_processors=[logits_processor], - ) - self.assertEqual(len(all_toks), len(init_toks) + 5) - - def test_stream_generate_speculative(self): - # Use same model as draft model, this is not a speed test - draft_model, _ = load(self.HF_MODEL_PATH) - - results: List[GenerationResponse] = [] - drafted: List[bool] = [] - - # make a determinate sampler - sampler = make_sampler(temp=0.0) - - for generation_result in stream_generate( - model=self.model, - tokenizer=self.tokenizer, - prompt="hello", - max_tokens=5, - draft_model=draft_model, - num_draft_tokens=2, - sampler=sampler, - ): - drafted.append(generation_result.from_draft) - results.append(generation_result) - - self.assertEqual(len(results), 5) - # since num_draft_tokens is 2 and draft model is the same, the - # first 2 generations should be drafts, the third should come - # from the target model, and last two should be drafts - self.assertEqual(drafted, [True, True, False, True, True]) - - -if __name__ == "__main__": - unittest.main() diff --git a/llms/tests/test_gguf.py b/llms/tests/test_gguf.py deleted file mode 100644 index 24ca64aa..00000000 --- a/llms/tests/test_gguf.py +++ /dev/null @@ -1,58 +0,0 @@ -import os -import tempfile -import unittest -from pathlib import Path -from unittest.mock import MagicMock, patch - -import mlx.core as mx -from mlx_lm.gguf import convert_to_gguf - - -class TestConvertToGGUFWithoutMocks(unittest.TestCase): - @classmethod - def setUpClass(cls): - cls.test_dir_fid = tempfile.TemporaryDirectory() - cls.test_dir = cls.test_dir_fid.name - cls.tokenizer_file_path = os.path.join(cls.test_dir, "tokenizer.json") - with open(cls.tokenizer_file_path, "w") as f: - f.write("{}") - - @classmethod - def tearDownClass(cls): - cls.test_dir_fid.cleanup() - - @patch("transformers.AutoTokenizer.from_pretrained") - @patch("mlx.core.save_gguf") - def test_convert_to_gguf( - self, - mock_save_gguf, - mock_from_pretrained, - ): - mock_tokenizer = MagicMock() - mock_tokenizer.vocab_size = 3 - mock_tokenizer.get_added_vocab.return_value = {} - mock_tokenizer.get_vocab.return_value = {"": 0, "hello": 1, "world": 2} - mock_tokenizer.all_special_tokens = [""] - mock_tokenizer.all_special_ids = [0] - mock_from_pretrained.return_value = mock_tokenizer - - model_path = Path(self.test_dir) - weights = { - "self_attn.q_proj.weight": mx.random.uniform(shape=[768, 768]), - } - config = { - "num_attention_heads": 1, - "num_hidden_layers": 1, - "hidden_size": 768, - "intermediate_size": 3072, - "_name_or_path": "test-llama", - } - output_file_path = "/fake/output/path/gguf_model.gguf" - - convert_to_gguf(model_path, weights, config, output_file_path) - called_args, _ = mock_save_gguf.call_args - self.assertEqual(called_args[0], output_file_path) - - -if __name__ == "__main__": - unittest.main() diff --git a/llms/tests/test_models.py b/llms/tests/test_models.py deleted file mode 100644 index b4e7aab8..00000000 --- a/llms/tests/test_models.py +++ /dev/null @@ -1,986 +0,0 @@ -# Copyright © 2024 Apple Inc. -import unittest - -import mlx.core as mx -import mlx.nn as nn -from mlx.utils import tree_map -from mlx_lm.models import rope_utils -from mlx_lm.models.base import create_causal_mask -from mlx_lm.models.cache import KVCache, RotatingKVCache, make_prompt_cache - - -class TestModels(unittest.TestCase): - - def test_kv_cache(self): - cache = KVCache() - - k = mx.ones((1, 4, 1, 32), mx.float16) - v = mx.ones((1, 4, 1, 32), mx.float16) - - k_up, v_up = cache.update_and_fetch(k, v) - self.assertTrue(mx.array_equal(k_up, k)) - self.assertTrue(mx.array_equal(v_up, v)) - self.assertEqual(cache.offset, 1) - - k = mx.ones((1, 4, cache.step, 32), mx.float16) - v = mx.ones((1, 4, cache.step, 32), mx.float16) - k_up, v_up = cache.update_and_fetch(k, v) - - expected = mx.ones((1, 4, cache.step + 1, 32), mx.float16) - self.assertTrue(mx.array_equal(k_up, expected)) - self.assertTrue(mx.array_equal(v_up, expected)) - self.assertEqual(cache.offset, cache.step + 1) - - def test_rotating_kv_cache(self): - b, h, d = 1, 2, 32 - cache = RotatingKVCache(max_size=8, step=4) - - k = mx.random.uniform(shape=(b, h, 2, d)) - v = mx.random.uniform(shape=(b, h, 2, d)) - - k_up, v_up = cache.update_and_fetch(k, v) - self.assertTrue(mx.array_equal(k_up, k)) - self.assertTrue(mx.array_equal(v_up, v)) - self.assertEqual(cache.offset, 2) - - k = mx.random.uniform(shape=(b, h, 5, d)) - v = mx.random.uniform(shape=(b, h, 5, d)) - k_up, v_up = cache.update_and_fetch(k, v) - self.assertTrue(mx.array_equal(k_up[..., 2:, :], k)) - self.assertTrue(mx.array_equal(v_up[..., 2:, :], v)) - - k = mx.random.uniform(shape=(b, h, 4, d)) - v = mx.random.uniform(shape=(b, h, 4, d)) - k_up, v_up = cache.update_and_fetch(k, v) - self.assertTrue(mx.array_equal(k_up[..., -4:, :], k)) - self.assertTrue(mx.array_equal(v_up[..., -4:, :], v)) - - idx = 0 - for _ in range(10): - k = mx.random.uniform(shape=(b, h, 1, d)) - v = mx.random.uniform(shape=(b, h, 1, d)) - k_up, v_up = cache.update_and_fetch(k, v) - self.assertTrue(mx.array_equal(k_up[..., idx : idx + 1, :], k)) - self.assertTrue(mx.array_equal(v_up[..., idx : idx + 1, :], v)) - idx += 1 - idx %= 8 - - # Try with nonzero keep - cache = RotatingKVCache(max_size=8, step=4, keep=2) - - # Check a large update - k = mx.random.uniform(shape=(b, h, 20, d)) - v = mx.random.uniform(shape=(b, h, 20, d)) - k_up, v_up = cache.update_and_fetch(k, v) - self.assertTrue(mx.array_equal(k_up, k)) - self.assertTrue(mx.array_equal(v_up, v)) - - # A bunch of small updates - self.assertEqual(cache.offset, 20) - idx = 2 - for i in range(10): - k = mx.random.uniform(shape=(b, h, 1, d)) - v = mx.random.uniform(shape=(b, h, 1, d)) - k_up, v_up = cache.update_and_fetch(k, v) - self.assertTrue(mx.array_equal(k_up[..., idx : idx + 1, :], k)) - self.assertTrue(mx.array_equal(v_up[..., idx : idx + 1, :], v)) - self.assertEqual(cache.offset, 21 + i) - idx += 1 - if idx >= 8: - idx = 2 - - def test_rotating_kv_cache_chat_mode(self): - # Test that the rotating kv cache can handle - # alternating prompt/prefill with generation - d = 4 - h = 2 - cache = RotatingKVCache(max_size=18, step=4) - - x = mx.random.uniform(shape=(1, h, 8, d)) - k, v = cache.update_and_fetch(x, x) - self.assertEqual(k.shape[2], 8) - self.assertEqual(cache.offset, 8) - - x = mx.random.uniform(shape=(1, h, 1, d)) - k, v = cache.update_and_fetch(x, x) - self.assertEqual(k.shape[2], 9) - self.assertEqual(cache.offset, 9) - self.assertTrue(mx.allclose(x, k[..., 8:9, :])) - - x = mx.random.uniform(shape=(1, h, 2, d)) - k, v = cache.update_and_fetch(x, x) - self.assertEqual(k.shape[2], 11) - self.assertEqual(cache.offset, 11) - self.assertTrue(mx.allclose(x, k[..., 9:11, :])) - - x = mx.random.uniform(shape=(1, h, 3, d)) - k, v = cache.update_and_fetch(x, x) - self.assertEqual(k.shape[2], 14) - self.assertEqual(cache.offset, 14) - self.assertTrue(mx.allclose(x, k[..., 11:14, :])) - - x = mx.random.uniform(shape=(1, h, 6, d)) - k, v = cache.update_and_fetch(x, x) - self.assertEqual(cache.offset, 20) - self.assertTrue(mx.allclose(x, k[..., -6:, :])) - - x = mx.random.uniform(shape=(1, h, 2, d)) - k, v = cache.update_and_fetch(x, x) - self.assertEqual(cache.offset, 22) - self.assertTrue(mx.allclose(x, k[..., -2:, :])) - - def test_causal_mask_lengths(self): - mx.random.seed(8) - B, N_q, T_q, N_kv, T_kv, D = (4, 8, 3, 2, 3, 2) - lengths = mx.array([1, 2, 3, 1]) - q = mx.random.uniform(shape=(B, N_q, T_q, D)) - k = mx.random.uniform(shape=(B, N_kv, T_kv, D)) - v = k - mask = create_causal_mask(T_q, 0, lengths=lengths) - - out1 = mx.fast.scaled_dot_product_attention(q, k, v, scale=1.0, mask=mask) - q[1, :, 2:] = mx.ones_like(q[1, :, 2:]) - k[1, :, 2:] = mx.ones_like(k[1, :, 2:]) - v[1, :, 2:] = mx.ones_like(v[1, :, 2:]) - out2 = mx.fast.scaled_dot_product_attention(q, k, v, scale=1.0, mask=mask) - self.assertTrue(mx.allclose(out1[1, :, :2], out2[1, :, :2])) - - def test_rope(self): - rope = rope_utils.initialize_rope(32, base=100, traditional=False) - self.assertTrue(isinstance(rope, nn.RoPE)) - - rope = rope_utils.initialize_rope( - 32, - base=100, - traditional=False, - scaling_config={"rope_type": "linear", "factor": 10.0}, - ) - self.assertTrue(isinstance(rope, nn.RoPE)) - - rope = rope_utils.initialize_rope( - 32, - base=100, - traditional=False, - scaling_config={"rope_type": "llama3", "factor": 2.0}, - ) - self.assertTrue(isinstance(rope, rope_utils.Llama3RoPE)) - - def model_test_runner(self, model, model_type, vocab_size, num_layers): - - self.assertEqual(len(model.layers), num_layers) - self.assertEqual(model.model_type, model_type) - - for t in [mx.float32, mx.float16]: - model.update(tree_map(lambda p: p.astype(t), model.parameters())) - - inputs = mx.array([[0, 1]]) - outputs = model(inputs) - self.assertEqual(outputs.shape, (1, 2, vocab_size)) - self.assertEqual(outputs.dtype, t) - - cache = make_prompt_cache(model) - outputs = model(inputs, cache=cache) - self.assertEqual(outputs.shape, (1, 2, vocab_size)) - self.assertEqual(outputs.dtype, t) - - if model_type not in ("mamba", "plamo2"): - mask = create_causal_mask(inputs.shape[1], 0).astype(t) - outputs = model(inputs, mask=mask) - self.assertEqual(outputs.shape, (1, 2, vocab_size)) - self.assertEqual(outputs.dtype, t) - - outputs = model(mx.argmax(outputs[0, -1:, :], keepdims=True), cache=cache) - self.assertEqual(outputs.shape, (1, 1, vocab_size)) - self.assertEqual(outputs.dtype, t) - - def test_llama(self): - from mlx_lm.models import llama - - args = llama.ModelArgs( - model_type="llama", - hidden_size=1024, - num_hidden_layers=4, - intermediate_size=2048, - num_attention_heads=4, - rms_norm_eps=1e-5, - vocab_size=10_000, - ) - model = llama.Model(args) - self.model_test_runner( - model, args.model_type, args.vocab_size, args.num_hidden_layers - ) - - def test_phi2(self): - from mlx_lm.models import phi - - args = phi.ModelArgs() - model = phi.Model(args) - self.model_test_runner( - model, args.model_type, args.vocab_size, args.num_hidden_layers - ) - - def test_phixtral(self): - from mlx_lm.models import phixtral - - args = phixtral.ModelArgs( - "phixtral", num_vocab=1000, num_layers=4, model_dim=1024 - ) - model = phixtral.Model(args) - self.model_test_runner(model, args.model_type, args.num_vocab, args.num_layers) - - def test_phi3(self): - from mlx_lm.models import phi3 - - args = phi3.ModelArgs( - model_type="phi3", - hidden_size=3072, - num_hidden_layers=32, - intermediate_size=8192, - num_attention_heads=32, - rms_norm_eps=1e-5, - vocab_size=32064, - ) - model = phi3.Model(args) - self.model_test_runner( - model, args.model_type, args.vocab_size, args.num_hidden_layers - ) - - def test_gemma(self): - from mlx_lm.models import gemma - - args = gemma.ModelArgs( - model_type="gemma", - hidden_size=1024, - num_hidden_layers=4, - intermediate_size=2048, - num_attention_heads=4, - head_dim=128, - rms_norm_eps=1e-5, - vocab_size=10_000, - num_key_value_heads=4, - ) - model = gemma.Model(args) - self.model_test_runner( - model, args.model_type, args.vocab_size, args.num_hidden_layers - ) - - def test_mixtral(self): - from mlx_lm.models import mixtral - - # Make a baby mixtral, because it will actually do the - # eval - args = mixtral.ModelArgs( - model_type="mixtral", - vocab_size=100, - hidden_size=32, - intermediate_size=128, - num_hidden_layers=2, - num_attention_heads=4, - num_experts_per_tok=2, - num_key_value_heads=2, - num_local_experts=4, - ) - model = mixtral.Model(args) - self.model_test_runner( - model, args.model_type, args.vocab_size, args.num_hidden_layers - ) - - @unittest.skip("requires ai2-olmo") - def test_olmo(self): - from mlx_lm.models import olmo - - args = olmo.ModelArgs( - model_type="olmo", - d_model=1024, - n_layers=4, - mlp_hidden_size=2048, - n_heads=2, - vocab_size=10_000, - embedding_size=10_000, - ) - model = olmo.Model(args) - self.model_test_runner( - model, - args.model_type, - args.vocab_size, - args.n_layers, - ) - - def test_qwen2_moe(self): - from mlx_lm.models import qwen2_moe - - args = qwen2_moe.ModelArgs( - model_type="qwen2_moe", - hidden_size=1024, - num_hidden_layers=4, - intermediate_size=2048, - num_attention_heads=4, - rms_norm_eps=1e-5, - vocab_size=10_000, - num_experts_per_tok=4, - num_experts=16, - moe_intermediate_size=1024, - shared_expert_intermediate_size=2048, - ) - model = qwen2_moe.Model(args) - self.model_test_runner( - model, args.model_type, args.vocab_size, args.num_hidden_layers - ) - - def test_qwen2(self): - from mlx_lm.models import qwen2 - - args = qwen2.ModelArgs( - model_type="qwen2", - hidden_size=1024, - num_hidden_layers=4, - intermediate_size=2048, - num_attention_heads=4, - rms_norm_eps=1e-5, - vocab_size=10_000, - ) - model = qwen2.Model(args) - self.model_test_runner( - model, args.model_type, args.vocab_size, args.num_hidden_layers - ) - - def test_qwen(self): - from mlx_lm.models import qwen - - args = qwen.ModelArgs( - model_type="qwen", - ) - model = qwen.Model(args) - self.model_test_runner( - model, args.model_type, args.vocab_size, args.num_hidden_layers - ) - - def test_plamo(self): - from mlx_lm.models import plamo - - args = plamo.ModelArgs( - model_type="plamo", - hidden_size=1024, - num_hidden_layers=4, - intermediate_size=2048, - num_attention_heads=8, - rms_norm_eps=1e-5, - vocab_size=10_000, - ) - model = plamo.Model(args) - self.model_test_runner( - model, args.model_type, args.vocab_size, args.num_hidden_layers - ) - - def test_plamo2(self): - from mlx_lm.models import plamo2 - - args = plamo2.ModelArgs( - model_type="plamo2", - hidden_size=1024, - num_hidden_layers=4, - intermediate_size=2048, - num_attention_heads=8, - rms_norm_eps=1e-5, - vocab_size=10_000, - ) - model = plamo2.Model(args) - self.model_test_runner( - model, args.model_type, args.vocab_size, args.num_hidden_layers - ) - - def test_stablelm(self): - from mlx_lm.models import stablelm - - args = stablelm.ModelArgs( - model_type="stablelm", - vocab_size=10_000, - hidden_size=1024, - num_attention_heads=4, - num_hidden_layers=4, - num_key_value_heads=2, - partial_rotary_factor=1.0, - intermediate_size=2048, - layer_norm_eps=1e-2, - rope_theta=10_000, - use_qkv_bias=False, - ) - model = stablelm.Model(args) - self.model_test_runner( - model, args.model_type, args.vocab_size, args.num_hidden_layers - ) - - # StableLM 2 - args = stablelm.ModelArgs( - model_type="stablelm", - vocab_size=10000, - hidden_size=512, - num_attention_heads=8, - num_hidden_layers=4, - num_key_value_heads=2, - partial_rotary_factor=0.25, - intermediate_size=1024, - layer_norm_eps=1e-5, - rope_theta=10000, - use_qkv_bias=True, - ) - model = stablelm.Model(args) - self.model_test_runner( - model, args.model_type, args.vocab_size, args.num_hidden_layers - ) - - def test_starcoder2(self): - from mlx_lm.models import starcoder2 - - args = starcoder2.ModelArgs( - model_type="starcoder2", - hidden_size=1024, - num_hidden_layers=4, - intermediate_size=2048, - num_attention_heads=4, - num_key_value_heads=4, - ) - model = starcoder2.Model(args) - self.model_test_runner( - model, args.model_type, args.vocab_size, args.num_hidden_layers - ) - - def test_cohere(self): - from mlx_lm.models import cohere - - args = cohere.ModelArgs( - model_type="cohere", - ) - model = cohere.Model(args) - self.model_test_runner( - model, args.model_type, args.vocab_size, args.num_hidden_layers - ) - - def test_dbrx(self): - from mlx_lm.models import dbrx - - args = dbrx.ModelArgs( - model_type="dbrx", - d_model=1024, - ffn_config={"ffn_hidden_size": 2048, "moe_num_experts": 4, "moe_top_k": 2}, - attn_config={"kv_n_heads": 2, "clip_qkv": True, "rope_theta": 10000}, - n_layers=4, - n_heads=4, - vocab_size=10_000, - ) - model = dbrx.Model(args) - self.model_test_runner(model, args.model_type, args.vocab_size, args.n_layers) - - def test_minicpm(self): - from mlx_lm.models import minicpm - - args = minicpm.ModelArgs( - model_type="minicpm", - hidden_size=1024, - dim_model_base=1024, - num_hidden_layers=4, - intermediate_size=2048, - num_attention_heads=4, - rms_norm_eps=1e-4, - vocab_size=10000, - num_key_value_heads=2, - scale_depth=1.0, - scale_emb=1.0, - ) - model = minicpm.Model(args) - self.model_test_runner( - model, args.model_type, args.vocab_size, args.num_hidden_layers - ) - - def test_mamba(self): - from mlx_lm.models import mamba - - args = mamba.ModelArgs( - model_type="mamba", - vocab_size=10000, - use_bias=False, - use_conv_bias=True, - conv_kernel=4, - hidden_size=768, - num_hidden_layers=24, - state_size=16, - intermediate_size=1536, - time_step_rank=48, - ) - model = mamba.Model(args) - self.model_test_runner( - model, args.model_type, args.vocab_size, args.num_hidden_layers - ) - - def test_gpt2(self): - from mlx_lm.models import gpt2 - - args = gpt2.ModelArgs( - model_type="gpt2", - n_ctx=1024, - n_embd=768, - n_head=12, - n_layer=12, - n_positions=1024, - layer_norm_epsilon=1e-5, - vocab_size=50256, - ) - model = gpt2.Model(args) - self.model_test_runner(model, args.model_type, args.vocab_size, args.n_layer) - - def test_gpt_neox(self): - from mlx_lm.models import gpt_neox - - args = gpt_neox.ModelArgs( - model_type="gpt_neox", - max_position_embeddings=2048, - hidden_size=6144, - num_attention_heads=64, - num_hidden_layers=44, - layer_norm_eps=1e-5, - vocab_size=50432, - rotary_emb_base=10_000, - rotary_pct=0.25, - ) - model = gpt_neox.Model(args) - self.model_test_runner( - model, args.model_type, args.vocab_size, args.num_hidden_layers - ) - - def test_openelm(self): - from mlx_lm.models import openelm - - args = openelm.ModelArgs( - model_type="openelm", - ffn_dim_divisor=256, - ffn_multipliers=[ - 0.5, - 0.73, - 0.97, - 1.2, - 1.43, - 1.67, - 1.9, - 2.13, - 2.37, - 2.6, - 2.83, - 3.07, - 3.3, - 3.53, - 3.77, - 4.0, - ], - head_dim=64, - model_dim=1280, - normalize_qk_projections=True, - num_kv_heads=[3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5], - num_query_heads=[ - 12, - 12, - 12, - 12, - 12, - 16, - 16, - 16, - 16, - 16, - 16, - 16, - 20, - 20, - 20, - 20, - ], - num_transformer_layers=16, - vocab_size=32000, - ) - - model = openelm.Model(args) - self.model_test_runner( - model, - args.model_type, - args.vocab_size, - len(args.ffn_multipliers), - ) - - def test_internlm2(self): - from mlx_lm.models import internlm2 - - args = internlm2.ModelArgs( - model_type="internlm2", - hidden_size=1024, - num_hidden_layers=4, - intermediate_size=2048, - num_attention_heads=4, - rms_norm_eps=1e-5, - vocab_size=10000, - ) - model = internlm2.Model(args) - self.model_test_runner( - model, args.model_type, args.vocab_size, args.num_hidden_layers - ) - - def test_llama3_1(self): - from mlx_lm.models import llama - - args = llama.ModelArgs( - model_type="llama", - hidden_size=1024, - num_hidden_layers=4, - intermediate_size=2048, - num_attention_heads=4, - rms_norm_eps=1e-5, - vocab_size=10_000, - max_position_embeddings=128, - mlp_bias=False, - num_key_value_heads=2, - rope_scaling={ - "factor": 8.0, - "low_freq_factor": 1.0, - "high_freq_factor": 4.0, - "original_max_position_embeddings": 8192, - "rope_type": "llama3", - }, - ) - model = llama.Model(args) - self.model_test_runner( - model, args.model_type, args.vocab_size, args.num_hidden_layers - ) - - def test_deepseek(self): - from mlx_lm.models import deepseek - - args = deepseek.ModelArgs( - model_type="deepseek", - vocab_size=1024, - hidden_size=128, - intermediate_size=256, - moe_intermediate_size=256, - num_hidden_layers=4, - num_attention_heads=8, - num_key_value_heads=4, - ) - model = deepseek.Model(args) - self.model_test_runner( - model, args.model_type, args.vocab_size, args.num_hidden_layers - ) - - def test_deepseek_v2(self): - from mlx_lm.models import deepseek_v2 - - args = deepseek_v2.ModelArgs( - model_type="deepseek_v2", - vocab_size=1024, - hidden_size=128, - intermediate_size=256, - moe_intermediate_size=256, - num_hidden_layers=4, - num_attention_heads=4, - num_key_value_heads=2, - kv_lora_rank=4, - q_lora_rank=4, - qk_rope_head_dim=32, - v_head_dim=16, - qk_nope_head_dim=32, - rope_scaling={ - "beta_fast": 32, - "beta_slow": 1, - "factor": 40, - "mscale": 1.0, - "mscale_all_dim": 1.0, - "original_max_position_embeddings": 4096, - "type": "yarn", - }, - ) - model = deepseek_v2.Model(args) - self.model_test_runner( - model, args.model_type, args.vocab_size, args.num_hidden_layers - ) - - def test_deepseek_v3(self): - from mlx_lm.models import deepseek_v3 - - args = deepseek_v3.ModelArgs( - model_type="deepseek_v3", - vocab_size=1024, - hidden_size=128, - intermediate_size=256, - moe_intermediate_size=256, - num_hidden_layers=4, - num_attention_heads=4, - num_key_value_heads=2, - n_routed_experts=4, - n_group=2, - topk_group=1, - num_experts_per_tok=2, - n_shared_experts=1, - kv_lora_rank=4, - q_lora_rank=4, - qk_rope_head_dim=32, - v_head_dim=16, - qk_nope_head_dim=32, - rope_scaling={ - "beta_fast": 32, - "beta_slow": 1, - "factor": 40, - "mscale": 1.0, - "mscale_all_dim": 1.0, - "original_max_position_embeddings": 4096, - "type": "yarn", - }, - ) - model = deepseek_v3.Model(args) - self.model_test_runner( - model, args.model_type, args.vocab_size, args.num_hidden_layers - ) - - def test_gemma2(self): - from mlx_lm.models import gemma2 - - args = gemma2.ModelArgs( - model_type="gemma2", - hidden_size=128, - num_hidden_layers=4, - intermediate_size=256, - num_attention_heads=2, - head_dim=32, - rms_norm_eps=1e-4, - vocab_size=1024, - num_key_value_heads=2, - ) - model = gemma2.Model(args) - self.model_test_runner( - model, args.model_type, args.vocab_size, args.num_hidden_layers - ) - - def test_gemma3_text(self): - from mlx_lm.models import gemma3_text - - args = gemma3_text.ModelArgs( - model_type="gemma3_text", - hidden_size=128, - num_hidden_layers=12, - intermediate_size=256, - num_attention_heads=4, - head_dim=32, - rms_norm_eps=1e-4, - num_key_value_heads=1, - sliding_window=1024, - sliding_window_pattern=6, - ) - model = gemma3_text.Model(args) - self.model_test_runner( - model, args.model_type, args.vocab_size, args.num_hidden_layers - ) - - def test_gpt_bigcode(self): - from mlx_lm.models import gpt_bigcode - - args = gpt_bigcode.ModelArgs( - model_type="gpt_bigcode", - n_embd=128, - n_layer=128, - n_inner=256, - n_head=4, - n_positions=1000, - layer_norm_epsilon=1e-5, - vocab_size=1024, - ) - model = gpt_bigcode.Model(args) - self.model_test_runner(model, args.model_type, args.vocab_size, args.n_layer) - - def test_nemotron(self): - from mlx_lm.models import nemotron - - args = nemotron.ModelArgs( - model_type="nemotron", - hidden_size=128, - hidden_act="gelu", - num_hidden_layers=4, - intermediate_size=256, - num_attention_heads=4, - norm_eps=1e-5, - vocab_size=1024, - num_key_value_heads=2, - ) - model = nemotron.Model(args) - self.model_test_runner( - model, args.model_type, args.vocab_size, args.num_hidden_layers - ) - - def test_phi3small(self): - from mlx_lm.models import phi3small - - args = phi3small.ModelArgs( - model_type="phi3small", - hidden_size=128, - dense_attention_every_n_layers=2, - ff_intermediate_size=256, - gegelu_limit=1.0, - num_hidden_layers=4, - num_attention_heads=4, - num_key_value_heads=2, - layer_norm_epsilon=1e-4, - vocab_size=1000, - ) - model = phi3small.Model(args) - self.model_test_runner( - model, args.model_type, args.vocab_size, args.num_hidden_layers - ) - - def test_phimoe(self): - from mlx_lm.models import phimoe - - args = phimoe.ModelArgs( - model_type="phimoe", - vocab_size=320, - hidden_size=128, - intermediate_size=256, - num_hidden_layers=4, - num_attention_heads=4, - num_key_value_heads=4, - rope_scaling={ - "long_factor": [1.0] * 16, - "long_mscale": 1.243163121016122, - "original_max_position_embeddings": 4096, - "short_factor": [1.0] * 16, - "short_mscale": 1.243163121016122, - "type": "longrope", - }, - ) - model = phimoe.Model(args) - self.model_test_runner( - model, args.model_type, args.vocab_size, args.num_hidden_layers - ) - - def test_recurrent_gemma(self): - from mlx_lm.models import recurrent_gemma - - args = recurrent_gemma.ModelArgs( - model_type="recurrent_gemma", - hidden_size=128, - attention_bias=False, - conv1d_width=3, - intermediate_size=256, - logits_soft_cap=1.0, - num_attention_heads=4, - num_hidden_layers=4, - num_key_value_heads=2, - rms_norm_eps=1e-4, - rope_theta=1000, - attention_window_size=1024, - vocab_size=1000, - block_types=["recurrent", "recurrent", "attention"], - ) - model = recurrent_gemma.Model(args) - self.model_test_runner( - model, args.model_type, args.vocab_size, args.num_hidden_layers - ) - - def test_hunyuan(self): - from mlx_lm.models import hunyuan - - args = hunyuan.ModelArgs( - model_type="hunyuan", - hidden_size=128, - attention_bias=False, - intermediate_size=256, - num_attention_heads=4, - num_hidden_layers=4, - num_key_value_heads=2, - rms_norm_eps=1e-4, - rope_theta=1000, - vocab_size=1000, - moe_topk=2, - num_experts=2, - num_shared_expert=1, - use_mixed_mlp_moe=True, - use_qk_norm=True, - rope_scaling={ - "alpha": 1000.0, - "factor": 1.0, - "type": "dynamic", - }, - use_cla=True, - cla_share_factor=2, - ) - model = hunyuan.Model(args) - self.model_test_runner( - model, args.model_type, args.vocab_size, args.num_hidden_layers - ) - - def test_olmo2(self): - from mlx_lm.models import olmo2 - - args = olmo2.ModelArgs( - model_type="olmo2", - hidden_size=128, - attention_bias=False, - intermediate_size=256, - num_attention_heads=4, - num_hidden_layers=4, - num_key_value_heads=2, - rms_norm_eps=1e-4, - rope_theta=1000, - vocab_size=1000, - ) - model = olmo2.Model(args) - self.model_test_runner( - model, args.model_type, args.vocab_size, args.num_hidden_layers - ) - - def test_exaone(self): - from mlx_lm.models import exaone - - args = exaone.ModelArgs( - model_type="exaone", - hidden_size=128, - num_layers=4, - intermediate_size=256, - num_attention_heads=8, - num_key_value_heads=2, - vocab_size=1000, - layer_norm_epsilon=1e-4, - rope_theta=10000, - ) - model = exaone.Model(args) - self.model_test_runner(model, args.model_type, args.vocab_size, args.num_layers) - - def test_cohere2(self): - from mlx_lm.models import cohere2 - - args = cohere2.ModelArgs( - model_type="cohere2", - hidden_size=4096, - head_dim=128, - num_hidden_layers=40, - sliding_window=4096, - sliding_window_pattern=4, - ) - model = cohere2.Model(args) - self.model_test_runner( - model, args.model_type, args.vocab_size, args.num_hidden_layers - ) - - def test_internlm3(self): - from mlx_lm.models import internlm3 - - args = internlm3.ModelArgs( - model_type="internlm3", - hidden_size=1024, - num_hidden_layers=4, - intermediate_size=2048, - num_attention_heads=4, - rms_norm_eps=1e-5, - vocab_size=10_000, - ) - model = internlm3.Model(args) - self.model_test_runner( - model, args.model_type, args.vocab_size, args.num_hidden_layers - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/llms/tests/test_prompt_cache.py b/llms/tests/test_prompt_cache.py deleted file mode 100644 index c1860892..00000000 --- a/llms/tests/test_prompt_cache.py +++ /dev/null @@ -1,305 +0,0 @@ -# Copyright © 2024 Apple Inc. - -import copy -import os -import tempfile -import unittest - -import mlx.core as mx -from mlx_lm.models.cache import ( - KVCache, - MambaCache, - QuantizedKVCache, - RotatingKVCache, - load_prompt_cache, - make_prompt_cache, - save_prompt_cache, - trim_prompt_cache, -) -from mlx_lm.utils import generate_step, load - -HF_MODEL_PATH = "mlx-community/Qwen1.5-0.5B-Chat-4bit" - - -class TestPromptCache(unittest.TestCase): - - @classmethod - def setUpClass(cls): - cls.test_dir_fid = tempfile.TemporaryDirectory() - cls.test_dir = cls.test_dir_fid.name - - @classmethod - def tearDownClass(cls): - cls.test_dir_fid.cleanup() - - def test_save_load(self): - cache = [KVCache() for _ in range(4)] - for c in cache: - x = mx.random.uniform(shape=(1, 8, 10, 4)) - c.update_and_fetch(x, x) - cache_file = os.path.join(self.test_dir, "prompt_cache.safetensors") - save_prompt_cache(cache_file, cache) - loaded_cache = load_prompt_cache(cache_file) - self.assertTrue(len(cache), len(loaded_cache)) - for c, lc in zip(cache, loaded_cache): - self.assertEqual(c.offset, lc.offset) - self.assertTrue(mx.array_equal(c.state[0], lc.state[0])) - self.assertTrue(mx.array_equal(c.state[1], lc.state[1])) - - # Test with metadata - cache_file = os.path.join(self.test_dir, "prompt_cache.safetensors") - metadata = {"a": "b", "c": "d"} - save_prompt_cache(cache_file, cache, metadata) - _, loaded_metadata = load_prompt_cache(cache_file, return_metadata=True) - self.assertEqual(metadata, loaded_metadata) - - def test_save_load_rotating_cache(self): - cache_file = os.path.join(self.test_dir, "prompt_cache.safetensors") - - # Test with rotating cache - cache = [RotatingKVCache(max_size=8, keep=2) for _ in range(4)] - for c in cache: - x = mx.random.uniform(shape=(1, 8, 10, 4)) - c.update_and_fetch(x, x) - - save_prompt_cache(cache_file, cache) - loaded_cache = load_prompt_cache(cache_file) - self.assertTrue(len(cache), len(loaded_cache)) - for c, lc in zip(cache, loaded_cache): - self.assertEqual(c.offset, lc.offset) - self.assertEqual(c.keep, lc.keep) - self.assertEqual(c.max_size, lc.max_size) - self.assertEqual(c.step, lc.step) - self.assertTrue(mx.array_equal(c.state[0], lc.state[0])) - self.assertTrue(mx.array_equal(c.state[1], lc.state[1])) - - # Do a couple single token updates to get a rotation - for _ in range(2): - for c in cache: - x = mx.random.uniform(shape=(1, 8, 1, 4)) - c.update_and_fetch(x, x) - - save_prompt_cache(cache_file, cache) - loaded_cache = load_prompt_cache(cache_file) - - for c, lc in zip(cache, loaded_cache): - x = mx.random.uniform(shape=(1, 8, 1, 4)) - k, v = c.update_and_fetch(x, x) - lk, lv = lc.update_and_fetch(x, x) - self.assertEqual(c.offset, lc.offset) - self.assertTrue(mx.array_equal(k, lk)) - self.assertTrue(mx.array_equal(v, lv)) - - def test_save_load_mixed_cache(self): - cache_file = os.path.join(self.test_dir, "prompt_cache.safetensors") - - cache = [MambaCache(), KVCache(), RotatingKVCache(8), MambaCache()] - for c in cache: - if isinstance(c, MambaCache): - c[0] = mx.random.uniform(shape=(4, 4, 4)) - c[1] = mx.random.uniform(shape=(4, 4, 4)) - else: - x = mx.random.uniform(shape=(4, 4, 7, 4)) - y = mx.random.uniform(shape=(4, 4, 7, 4)) - c.update_and_fetch(x, y) - - save_prompt_cache(cache_file, cache) - loaded_cache = load_prompt_cache(cache_file) - for c, lc in zip(cache, loaded_cache): - if isinstance(c, MambaCache): - self.assertTrue(mx.array_equal(c[0], lc[0])) - self.assertTrue(mx.array_equal(c[1], lc[1])) - else: - x = mx.random.uniform(shape=(4, 4, 1, 4)) - y = mx.random.uniform(shape=(4, 4, 1, 4)) - k, v = c.update_and_fetch(x, y) - lk, lv = lc.update_and_fetch(x, y) - self.assertEqual(c.offset, lc.offset) - self.assertTrue(mx.array_equal(k, lk)) - self.assertTrue(mx.array_equal(v, lv)) - - def test_cache_with_generate(self): - model, tokenizer = load(HF_MODEL_PATH) - prompt = tokenizer.encode("this is a prompt", return_tensors="mlx")[0] - results = list(generate_step(prompt, model, max_tokens=4)) - toks, all_logits = zip(*results) - - prompt_cache = make_prompt_cache(model) - i = 0 - for tok, logits in generate_step( - prompt, model, prompt_cache=prompt_cache, max_tokens=2 - ): - self.assertEqual(tok, toks[i]) - self.assertTrue(mx.allclose(logits, all_logits[i])) - i += 1 - - for tok, logits in generate_step( - mx.array([toks[i]]), model, prompt_cache=prompt_cache, max_tokens=1 - ): - i += 1 - self.assertEqual(tok, toks[i]) - self.assertTrue(mx.allclose(logits, all_logits[i])) - - def test_trim_cache(self): - cache = [KVCache() for _ in range(2)] - for c in cache: - x = mx.random.uniform(shape=(1, 8, 10, 4)) - c.update_and_fetch(x, x) - - # Trim - num_trimmed = trim_prompt_cache(cache, 7) - self.assertEqual(num_trimmed, 7) - - # Trim more tokens than remain - num_trimmed = trim_prompt_cache(cache, 4) - self.assertEqual(num_trimmed, 3) - - # Can't trim mamba cache - cache = [MambaCache() for _ in range(2)] - for c in cache: - c.state = mx.zeros((5, 5)) - num_trimmed = trim_prompt_cache(cache, 7) - self.assertEqual(num_trimmed, 0) - - # All cache's have to be trimmable - cache = [MambaCache(), KVCache()] - cache[0].state = mx.zeros((5, 5)) - x = mx.random.uniform(shape=(1, 8, 10, 4)) - cache[1].update_and_fetch(x, x) - num_trimmed = trim_prompt_cache(cache, 1) - self.assertEqual(num_trimmed, 0) - - cache = [RotatingKVCache(max_size=6) for _ in range(2)] - for c in cache: - x = mx.random.uniform(shape=(1, 8, 5, 4)) - c.update_and_fetch(x, x) - - num_trimmed = trim_prompt_cache(cache, 4) - self.assertEqual(num_trimmed, 4) - - # Can't trim fixed-size KV cache after processing - # more than max_kv_size tokens - for c in cache: - x = mx.random.uniform(shape=(1, 8, 10, 4)) - c.update_and_fetch(x, x) - - num_trimmed = trim_prompt_cache(cache, 4) - self.assertEqual(num_trimmed, 0) - - cache = [QuantizedKVCache() for _ in range(2)] - for c in cache: - x = mx.random.uniform(shape=(1, 8, 10, 64)) - c.update_and_fetch(x, x) - - num_trimmed = trim_prompt_cache(cache, 7) - self.assertEqual(num_trimmed, 7) - - # Trim more tokens than remain - num_trimmed = trim_prompt_cache(cache, 4) - self.assertEqual(num_trimmed, 3) - - def test_trim_cache_with_generate(self): - model, tokenizer = load(HF_MODEL_PATH) - prompt = tokenizer.encode("this is a prompt", return_tensors="mlx")[0] - - prompt_cache = make_prompt_cache(model) - - # Generate one token so we process the full prompt - last_tok, _ = next(generate_step(prompt, model, prompt_cache=prompt_cache)) - last_tok = mx.array([last_tok]) - - # Generate two more tokens - results = zip( - range(2), generate_step(last_tok, model, prompt_cache=prompt_cache) - ) - toks, all_logits = zip(*(r[1] for r in results)) - - # To get back to the cache just after processing the prompt, - # trim by 3 tokens - trim_prompt_cache(prompt_cache, 3) - - # Generate the same thing again - results = zip( - range(2), generate_step(last_tok, model, prompt_cache=prompt_cache) - ) - second_toks, second_all_logits = zip(*(r[1] for r in results)) - self.assertEqual(toks, second_toks) - self.assertTrue( - all(mx.allclose(l, l2) for l, l2 in zip(all_logits, second_all_logits)) - ) - - def test_cache_copying(self): - cache = [KVCache()] - - x = mx.random.uniform(shape=(1, 8, 10, 4)) - cache[0].update_and_fetch(x, x) - - y = mx.random.uniform(shape=(1, 8, 1, 4)) - cache[0].update_and_fetch(y, y) - - old_cache = copy.deepcopy(cache) - - trim_prompt_cache(cache, 1) - - self.assertTrue(old_cache[0].offset, 11) - self.assertTrue(cache[0].offset, 10) - - z = mx.random.uniform(shape=(1, 8, 1, 4)) - cache[0].update_and_fetch(z, z) - - self.assertTrue(mx.allclose(old_cache[0].keys[..., 10:11, :], y)) - self.assertTrue(mx.allclose(cache[0].keys[..., 10:11, :], z)) - - def test_save_load_quantized_cache(self): - cache = [QuantizedKVCache(bits=4, group_size=32) for _ in range(4)] - for c in cache: - x = mx.random.uniform(shape=(1, 8, 10, 32)) - c.update_and_fetch(x, x) - cache_file = os.path.join(self.test_dir, "prompt_cache.safetensors") - save_prompt_cache(cache_file, cache) - loaded_cache = load_prompt_cache(cache_file) - self.assertTrue(loaded_cache[0].bits == cache[0].bits) - self.assertTrue(loaded_cache[0].group_size == cache[0].group_size) - self.assertTrue(len(cache), len(loaded_cache)) - for c, lc in zip(cache, loaded_cache): - self.assertEqual(c.offset, lc.offset) - # Loop over quantized tuple - for i in range(3): - self.assertTrue(mx.array_equal(c.state[0][i], lc.state[0][i])) - self.assertTrue(mx.array_equal(c.state[1][i], lc.state[1][i])) - - # Test with metadata - cache_file = os.path.join(self.test_dir, "prompt_cache.safetensors") - metadata = {"a": "b", "c": "d"} - save_prompt_cache(cache_file, cache, metadata) - _, loaded_metadata = load_prompt_cache(cache_file, return_metadata=True) - self.assertEqual(metadata, loaded_metadata) - - def test_cache_to_quantized(self): - model, tokenizer = load(HF_MODEL_PATH) - prompt = tokenizer.encode("this is a prompt", return_tensors="mlx")[0] - results = zip(range(4), generate_step(prompt, model)) - toks, all_logits = zip(*(r[1] for r in results)) - - prompt_cache = make_prompt_cache(model) - i = 0 - for _, (tok, logits) in zip( - range(2), generate_step(prompt, model, prompt_cache=prompt_cache) - ): - self.assertEqual(tok, toks[i]) - self.assertTrue(mx.allclose(logits, all_logits[i])) - i += 1 - - prompt_cache = [c.to_quantized(bits=8, group_size=32) for c in prompt_cache] - - for _, (tok, logits) in zip( - range(1), - generate_step(mx.array([toks[i]]), model, prompt_cache=prompt_cache), - ): - i += 1 - self.assertEqual(tok, toks[i]) - self.assertTrue(mx.allclose(logits, all_logits[i], rtol=3e-2)) - - -if __name__ == "__main__": - unittest.main() diff --git a/llms/tests/test_sample_utils.py b/llms/tests/test_sample_utils.py deleted file mode 100644 index 7760c569..00000000 --- a/llms/tests/test_sample_utils.py +++ /dev/null @@ -1,98 +0,0 @@ -import unittest - -import mlx.core as mx -from mlx_lm.sample_utils import apply_min_p, apply_top_k, apply_top_p - - -class TestSampleUtils(unittest.TestCase): - def test_apply_top_p(self): - probs = mx.array([0.9, 0.0, 0.0, 0.1])[None] - logits = mx.log(probs) - - new_logits = apply_top_p(logits, 0.3) - actual_probs = mx.softmax(new_logits.squeeze()) - self.assertEqual(actual_probs.tolist(), [1.0, 0.0, 0.0, 0.0]) - - new_logits = apply_top_p(logits, 0.95) - actual_probs = mx.softmax(new_logits.squeeze()) - self.assertTrue(mx.allclose(probs.squeeze(), actual_probs)) - - probs = mx.array([0.0, 0.5, 0.4, 0.1])[None] - logits = mx.log(probs) - new_logits = apply_top_p(logits, 0.4) - actual_probs = mx.softmax(new_logits.squeeze()) - self.assertEqual(actual_probs.tolist(), [0.0, 1.0, 0.0, 0.0]) - - new_logits = apply_top_p(logits, 0.6) - actual_probs = mx.softmax(new_logits.squeeze()) - self.assertEqual( - [round(p, 4) for p in actual_probs.tolist()], [0.0, 0.5556, 0.4444, 0.0] - ) - - new_logits = apply_top_p(logits, 0.95) - actual_probs = mx.softmax(new_logits.squeeze()) - actual_rounded = [round(p, 4) for p in actual_probs.tolist()] - expected_rounded = [0.0, 0.5, 0.4, 0.1] - self.assertEqual(actual_rounded, expected_rounded) - self.assertAlmostEqual(sum(actual_probs.tolist()), 1.0) - - # Batch mode works - probs = mx.array([[0.9, 0.0, 0.0, 0.1], [0.0, 0.8, 0.1, 0.1]]) - logits = mx.log(probs) - new_logits = apply_top_p(logits, 0.5) - actual_probs = mx.softmax(new_logits, axis=-1) - self.assertEqual( - actual_probs.tolist(), [[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0]] - ) - - def test_apply_min_p(self): - probs = mx.array([0.9, 0.0, 0.0, 0.1])[None] - logits = mx.log(probs) - new_logits = apply_min_p(logits, 0.8) - actual_probs = mx.softmax(new_logits.squeeze()) - self.assertEqual(actual_probs.tolist(), [1.0, 0.0, 0.0, 0.0]) - - probs = mx.array([0.9, 0.0, 0.0, 0.1])[None] - logits = mx.log(probs) - new_logits = apply_min_p(logits, 0.05) - actual_probs = mx.softmax(new_logits.squeeze()) - self.assertTrue(mx.allclose(actual_probs, mx.squeeze(probs))) - - # Batch mode works - probs = mx.array([[0.9, 0.0, 0.0, 0.1], [0.0, 0.8, 0.0, 0.1]]) - logits = mx.log(probs) - new_logits = apply_min_p(logits, 0.7) - actual_probs = mx.softmax(new_logits, axis=-1) - self.assertEqual( - actual_probs.tolist(), [[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0]] - ) - - def test_apply_top_k(self): - probs = mx.array([0.9, 0.0, 0.0, 0.1])[None] - logits = mx.log(probs) - - new_logits = apply_top_k(logits, 1) - actual_probs = mx.softmax(new_logits.squeeze()) - self.assertEqual(actual_probs.tolist(), [1.0, 0.0, 0.0, 0.0]) - - probs = mx.array([0.6, 0.0, 0.1, 0.3])[None] - logits = mx.log(probs) - new_logits = apply_top_k(logits, 2) - actual_probs = mx.softmax(new_logits.squeeze()) - self.assertEqual( - [round(p, 4) for p in actual_probs.tolist()], [0.6667, 0.0, 0.0, 0.3333] - ) - - # Batch mode works - probs = mx.array([[0.9, 0.0, 0.0, 0.1], [0.0, 0.8, 0.0, 0.1]]) - logits = mx.log(probs) - - new_logits = apply_top_k(logits, 1) - actual_probs = mx.softmax(new_logits, axis=-1) - self.assertEqual( - actual_probs.tolist(), [[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0]] - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/llms/tests/test_server.py b/llms/tests/test_server.py deleted file mode 100644 index ecf95f78..00000000 --- a/llms/tests/test_server.py +++ /dev/null @@ -1,133 +0,0 @@ -# Copyright © 2024 Apple Inc. - -import http -import json -import threading -import unittest - -import requests -from mlx_lm.server import APIHandler -from mlx_lm.utils import load - - -class DummyModelProvider: - def __init__(self): - HF_MODEL_PATH = "mlx-community/Qwen1.5-0.5B-Chat-4bit" - self.model, self.tokenizer = load(HF_MODEL_PATH) - self.model_key = (HF_MODEL_PATH, None) - - def load(self, model, adapter=None): - assert model in ["default_model", "chat_model"] - return self.model, self.tokenizer - - -class TestServer(unittest.TestCase): - @classmethod - def setUpClass(cls): - cls.model_provider = DummyModelProvider() - cls.server_address = ("localhost", 0) - cls.httpd = http.server.HTTPServer( - cls.server_address, - lambda *args, **kwargs: APIHandler(cls.model_provider, *args, **kwargs), - ) - cls.port = cls.httpd.server_port - cls.server_thread = threading.Thread(target=cls.httpd.serve_forever) - cls.server_thread.daemon = True - cls.server_thread.start() - - @classmethod - def tearDownClass(cls): - cls.httpd.shutdown() - cls.httpd.server_close() - cls.server_thread.join() - - def test_handle_completions(self): - url = f"http://localhost:{self.port}/v1/completions" - - post_data = { - "model": "default_model", - "prompt": "Once upon a time", - "max_tokens": 10, - "temperature": 0.5, - "top_p": 0.9, - "repetition_penalty": 1.1, - "repetition_context_size": 20, - "stop": "stop sequence", - } - - response = requests.post(url, json=post_data) - - response_body = response.text - - self.assertIn("id", response_body) - self.assertIn("choices", response_body) - - def test_handle_chat_completions(self): - url = f"http://localhost:{self.port}/v1/chat/completions" - chat_post_data = { - "model": "chat_model", - "max_tokens": 10, - "temperature": 0.7, - "top_p": 0.85, - "repetition_penalty": 1.2, - "messages": [ - {"role": "system", "content": "You are a helpful assistant."}, - {"role": "user", "content": "Hello!"}, - ], - } - response = requests.post(url, json=chat_post_data) - response_body = response.text - self.assertIn("id", response_body) - self.assertIn("choices", response_body) - - def test_handle_chat_completions_with_content_fragments(self): - url = f"http://localhost:{self.port}/v1/chat/completions" - chat_post_data = { - "model": "chat_model", - "max_tokens": 10, - "temperature": 0.7, - "top_p": 0.85, - "repetition_penalty": 1.2, - "messages": [ - { - "role": "system", - "content": [ - {"type": "text", "text": "You are a helpful assistant."} - ], - }, - {"role": "user", "content": [{"type": "text", "text": "Hello!"}]}, - ], - } - response = requests.post(url, json=chat_post_data) - response_body = response.text - self.assertIn("id", response_body) - self.assertIn("choices", response_body) - - def test_handle_models(self): - url = f"http://localhost:{self.port}/v1/models" - response = requests.get(url) - self.assertEqual(response.status_code, 200) - response_body = json.loads(response.text) - self.assertEqual(response_body["object"], "list") - self.assertIsInstance(response_body["data"], list) - self.assertGreater(len(response_body["data"]), 0) - model = response_body["data"][0] - self.assertIn("id", model) - self.assertEqual(model["object"], "model") - self.assertIn("created", model) - - def test_sequence_overlap(self): - from mlx_lm.server import sequence_overlap - - self.assertTrue(sequence_overlap([1], [1])) - self.assertTrue(sequence_overlap([1, 2], [1, 2])) - self.assertTrue(sequence_overlap([1, 3], [3, 4])) - self.assertTrue(sequence_overlap([1, 2, 3], [2, 3])) - - self.assertFalse(sequence_overlap([1], [2])) - self.assertFalse(sequence_overlap([1, 2], [3, 4])) - self.assertFalse(sequence_overlap([1, 2, 3], [4, 1, 2, 3])) - - -if __name__ == "__main__": - unittest.main() diff --git a/llms/tests/test_tokenizers.py b/llms/tests/test_tokenizers.py deleted file mode 100644 index 3009d1b1..00000000 --- a/llms/tests/test_tokenizers.py +++ /dev/null @@ -1,98 +0,0 @@ -# Copyright © 2024 Apple Inc. - -import unittest -from pathlib import Path - -from huggingface_hub import snapshot_download -from mlx_lm.tokenizer_utils import ( - BPEStreamingDetokenizer, - NaiveStreamingDetokenizer, - SPMStreamingDetokenizer, - load_tokenizer, -) - - -class TestTokenizers(unittest.TestCase): - - def download_tokenizer(self, repo): - path = Path( - snapshot_download( - repo_id=repo, - allow_patterns=[ - "tokenizer.json", - "tokenizer_config.json", - "special_tokens_map.json", - "tokenizer.model", - ], - ) - ) - return load_tokenizer(path) - - def check_tokenizer(self, tokenizer): - def check(tokens): - expected_text = tokenizer.decode(tokens) - detokenizer = tokenizer.detokenizer - detokenizer.reset() - text = "" - for e, t in enumerate(tokens): - detokenizer.add_token(t) - seg = detokenizer.last_segment - text += seg - self.assertEqual(detokenizer.tokens, tokens[: e + 1]) - detokenizer.finalize() - text += detokenizer.last_segment - self.assertEqual(text, expected_text) - - tokens = tokenizer.encode("こんにちは!私の名前はAI") - check(tokens) - - tokens = tokenizer.encode("a ,b") - check(tokens) - - tokens = tokenizer.encode('{"why_its_funny" :"a_joke_explainer" ,"rating":3.5}') - check(tokens) - - tokens = tokenizer.encode("3 3") - check(tokens) - - tokens = tokenizer.encode("import 'package:flutter/material.dart';") - check(tokens) - - tokens = tokenizer.encode("hello\nworld") - check(tokens) - - def test_tokenizers(self): - tokenizer_repos = [ - ("mlx-community/Qwen1.5-0.5B-Chat-4bit", BPEStreamingDetokenizer), - ("mlx-community/Mistral-7B-v0.2-4bit", SPMStreamingDetokenizer), - ("mlx-community/Phi-3.5-mini-instruct-4bit", SPMStreamingDetokenizer), - ("mlx-community/Mistral-7B-Instruct-v0.3", SPMStreamingDetokenizer), - ("mlx-community/Llama-3.2-1B-Instruct-4bit", BPEStreamingDetokenizer), - ("mlx-community/Falcon3-7B-Instruct-4bit", BPEStreamingDetokenizer), - ] - for tokenizer_repo, expected_detokenizer in tokenizer_repos: - with self.subTest(tokenizer=tokenizer_repo): - tokenizer = self.download_tokenizer(tokenizer_repo) - tokenizer.decode([0, 1, 2]) - self.assertTrue(isinstance(tokenizer.detokenizer, expected_detokenizer)) - self.check_tokenizer(tokenizer) - - # Try one with a naive detokenizer - tokenizer = self.download_tokenizer("mlx-community/Llama-3.2-1B-Instruct-4bit") - tokenizer._detokenizer = NaiveStreamingDetokenizer(tokenizer) - self.check_tokenizer(tokenizer) - - def test_special_tokens(self): - tokenizer_repo = "mlx-community/DeepSeek-Coder-V2-Lite-Instruct-4bit-mlx" - tokenizer = self.download_tokenizer(tokenizer_repo) - - detokenizer = tokenizer.detokenizer - detokenizer.reset() - detokenizer.add_token(tokenizer.eos_token_id) - detokenizer.finalize() - - self.assertEqual(detokenizer.last_segment, tokenizer.eos_token) - - -if __name__ == "__main__": - unittest.main() diff --git a/llms/tests/test_tuner_utils.py b/llms/tests/test_tuner_utils.py deleted file mode 100644 index 0256683c..00000000 --- a/llms/tests/test_tuner_utils.py +++ /dev/null @@ -1,85 +0,0 @@ -# Copyright © 2024 Apple Inc. - -import sys -import unittest -from io import StringIO -from unittest.mock import MagicMock - -import mlx.nn as nn -from mlx_lm.tuner.lora import LoRALinear -from mlx_lm.tuner.utils import print_trainable_parameters - - -class TestTunerUtils(unittest.TestCase): - def setUp(self): - self.capturedOutput = StringIO() - sys.stdout = self.capturedOutput - - def tearDown(self): - sys.stdout = sys.__stdout__ - - def test_quantized_print_trainable_parameters(self): - model = MagicMock() - quantized_linear = MagicMock(spec=nn.QuantizedLinear) - quantized_linear.weight = MagicMock(size=1e6) - quantized_linear.bits = 8 - lora_linear = MagicMock(spec=LoRALinear) - lora_linear.weight = MagicMock(size=2e6) - lora_linear.parameters.return_value = [lora_linear.weight] - - linear = MagicMock(spec=nn.Linear) - linear.weight = MagicMock(size=3e6) - linear.parameters.return_value = [linear.weight] - - model.leaf_modules.return_value = { - "quantized_linear": quantized_linear, - "lora_linear": lora_linear, - "linear": linear, - } - - model.trainable_parameters.return_value = { - "layer1.weight": MagicMock(size=1e6), - "layer3.weight": MagicMock(size=2e6), - } - expected_output_8bits = "Trainable parameters: 33.333% (3.000M/9.000M)\n" - print_trainable_parameters(model) - self.assertEqual(self.capturedOutput.getvalue(), expected_output_8bits) - self.capturedOutput.truncate(0) - self.capturedOutput.seek(0) - - quantized_linear.weight = MagicMock(size=1e6) - quantized_linear.bits = 4 - expected_output_4bits = "Trainable parameters: 23.077% (3.000M/13.000M)\n" - print_trainable_parameters(model) - self.assertEqual(self.capturedOutput.getvalue(), expected_output_4bits) - self.capturedOutput.truncate(0) - self.capturedOutput.seek(0) - - def test_print_trainable_parameters(self): - model = MagicMock() - linear1 = MagicMock(spec=nn.Linear) - linear1.weight = MagicMock(size=1e6) - linear1.parameters.return_value = [linear1.weight] - linear2 = MagicMock(spec=nn.Linear) - linear2.weight = MagicMock(size=2e6) - linear2.parameters.return_value = [linear2.weight] - lora_linear = MagicMock(spec=LoRALinear) - lora_linear.weight = MagicMock(size=3e6) - lora_linear.parameters.return_value = [lora_linear.weight] - model.leaf_modules.return_value = { - "linear1": linear1, - "linear2": linear2, - "lora_linear": lora_linear, - } - - model.trainable_parameters.return_value = { - "layer1.weight": MagicMock(size=1e6), - "layer3.weight": MagicMock(size=2e6), - } - expected_output = "Trainable parameters: 50.000% (3.000M/6.000M)\n" - print_trainable_parameters(model) - self.assertEqual(self.capturedOutput.getvalue(), expected_output) - - -if __name__ == "__main__": - unittest.main() diff --git a/llms/tests/test_utils.py b/llms/tests/test_utils.py deleted file mode 100644 index 18cfa8c7..00000000 --- a/llms/tests/test_utils.py +++ /dev/null @@ -1,94 +0,0 @@ -# Copyright © 2024 Apple Inc. - -import os -import tempfile -import unittest - -import mlx.core as mx -import mlx.nn as nn -from mlx.utils import tree_flatten -from mlx_lm import utils - -HF_MODEL_PATH = "mlx-community/Qwen1.5-0.5B-Chat-4bit" - - -class TestUtils(unittest.TestCase): - - @classmethod - def setUpClass(cls): - cls.test_dir_fid = tempfile.TemporaryDirectory() - cls.test_dir = cls.test_dir_fid.name - if not os.path.isdir(cls.test_dir): - os.mkdir(cls.test_dir_fid.name) - - @classmethod - def tearDownClass(cls): - cls.test_dir_fid.cleanup() - - def test_load(self): - model, _ = utils.load(HF_MODEL_PATH) - - model_lazy, _ = utils.load(HF_MODEL_PATH, lazy=True) - - mx.eval(model_lazy.parameters()) - - p1 = model.layers[0].mlp.up_proj.weight - p2 = model_lazy.layers[0].mlp.up_proj.weight - self.assertTrue(mx.allclose(p1, p2)) - - def test_make_shards(self): - from mlx_lm.models import llama - - args = llama.ModelArgs( - model_type="llama", - hidden_size=2048, - num_hidden_layers=32, - intermediate_size=4096, - num_attention_heads=32, - rms_norm_eps=1e-5, - vocab_size=30_000, - ) - model = llama.Model(args) - weights = tree_flatten(model.parameters()) - gb = sum(p.nbytes for _, p in weights) // 2**30 - shards = utils.make_shards(dict(weights), 1) - self.assertTrue(gb <= len(shards) <= gb + 1) - - def test_quantize(self): - from mlx_lm.models import llama - - args = llama.ModelArgs( - model_type="llama", - hidden_size=1024, - num_hidden_layers=4, - intermediate_size=2048, - num_attention_heads=4, - rms_norm_eps=1e-5, - vocab_size=10_000, - ) - model = llama.Model(args) - weights, config = utils.quantize_model(model, {}, 64, 4) - self.assertTrue("model.layers.2.mlp.up_proj.scales" in weights) - self.assertTrue("model.layers.2.mlp.up_proj.biases" in weights) - self.assertEqual(config["quantization"]["group_size"], 64) - self.assertEqual(config["quantization"]["bits"], 4) - - def test_convert(self): - mlx_path = os.path.join(self.test_dir, "mlx_model") - - utils.convert(HF_MODEL_PATH, mlx_path=mlx_path, quantize=True) - model, _ = utils.load(mlx_path) - self.assertTrue(isinstance(model.layers[0].mlp.up_proj, nn.QuantizedLinear)) - self.assertTrue(isinstance(model.layers[-1].mlp.up_proj, nn.QuantizedLinear)) - - # Check model weights have right type - mlx_path = os.path.join(self.test_dir, "mlx_model_bf16") - utils.convert(HF_MODEL_PATH, mlx_path=mlx_path, dtype="bfloat16") - model, _ = utils.load(mlx_path) - - self.assertEqual(model.layers[0].mlp.up_proj.weight.dtype, mx.bfloat16) - self.assertEqual(model.layers[-1].mlp.up_proj.weight.dtype, mx.bfloat16) - - -if __name__ == "__main__": - unittest.main() diff --git a/llms/tests/test_utils_load_model.py b/llms/tests/test_utils_load_model.py deleted file mode 100644 index 8da19afb..00000000 --- a/llms/tests/test_utils_load_model.py +++ /dev/null @@ -1,50 +0,0 @@ -import unittest -from pathlib import Path - -import mlx.nn as nn -from mlx_lm.models.qwen2 import Model as Qwen2Model -from mlx_lm.utils import get_model_path, load_model - -HF_MODEL_PATH = "mlx-community/Qwen1.5-0.5B-Chat-4bit" - - -class TestLoadModelCustomGetClasses(unittest.TestCase): - - def test_load_model_with_custom_get_classes(self): - class CustomQwenModel(nn.Module): - def __init__(self, args): - super().__init__() - self.config = args - self.custom_attribute = "This is a custom model" - - def load_weights(self, weights, **kwargs): - self.qwenWeights = weights - - class CustomQwenConfig: - @classmethod - def from_dict(cls, config): - instance = cls() - for k, v in config.items(): - setattr(instance, k, v) - return instance - - def custom_get_classes(config): - return CustomQwenModel, CustomQwenConfig - - model_path = get_model_path(HF_MODEL_PATH) - model, _ = load_model(model_path, get_model_classes=custom_get_classes) - - self.assertIsInstance(model, CustomQwenModel) - self.assertTrue(hasattr(model, "custom_attribute")) - self.assertEqual(model.custom_attribute, "This is a custom model") - self.assertTrue(hasattr(model, "qwenWeights")) - - def test_load_model_with_default_get_classes(self): - model_path = get_model_path(HF_MODEL_PATH) - model, _ = load_model(model_path) - - self.assertIsInstance(model, Qwen2Model) - - -if __name__ == "__main__": - unittest.main() From c52cc748f876b898720720dd0e4c4632c9790ebd Mon Sep 17 00:00:00 2001 From: Angelos Katharopoulos Date: Mon, 24 Mar 2025 22:16:48 -0700 Subject: [PATCH 185/188] Distributed FLUX (#1325) --- flux/README.md | 73 ++++++++++++++++++++++- flux/flux/layers.py | 27 +++++++-- flux/flux/model.py | 42 ++++++++++++++ flux/generate_interactive.py | 109 +++++++++++++++++++++++++++++++++++ flux/txt2image.py | 49 ++++++++++++---- 5 files changed, 282 insertions(+), 18 deletions(-) create mode 100644 flux/generate_interactive.py diff --git a/flux/README.md b/flux/README.md index b00a9621..95f86b49 100644 --- a/flux/README.md +++ b/flux/README.md @@ -167,8 +167,9 @@ python dreambooth.py \ path/to/dreambooth/dataset/dog6 ``` - -Or you can directly use the pre-processed Hugging Face dataset [mlx-community/dreambooth-dog6](https://huggingface.co/datasets/mlx-community/dreambooth-dog6) for fine-tuning. +Or you can directly use the pre-processed Hugging Face dataset +[mlx-community/dreambooth-dog6](https://huggingface.co/datasets/mlx-community/dreambooth-dog6) +for fine-tuning. ```shell python dreambooth.py \ @@ -210,3 +211,71 @@ speed during generation. [^1]: Refer to the [arXiv paper](https://arxiv.org/abs/2208.12242) for more details. [^2]: The images are from unsplash by https://unsplash.com/@alvannee . + + +Distributed Computation +------------------------ + +The FLUX example supports distributed computation during both generation and +training. See the [distributed communication +documentation](https://ml-explore.github.io/mlx/build/html/usage/distributed.html) +for information on how to set-up MLX for distributed communication. The rest of +this section assumes you can launch distributed MLX programs using `mlx.launch +--hostfile hostfile.json`. + +### Distributed Finetuning + +Distributed finetuning scales very well with FLUX and all one has to do is +adjust the gradient accumulation and training iterations so that the batch +size remains the same. For instance, to replicate the following training + +```shell +python dreambooth.py \ + --progress-prompt 'A photo of an sks dog lying on the sand at a beach in Greece' \ + --progress-every 600 --iterations 1200 --learning-rate 0.0001 \ + --lora-rank 4 --grad-accumulate 8 \ + mlx-community/dreambooth-dog6 +``` + +On 4 machines we simply run + +```shell +mlx.launch --verbose --hostfile hostfile.json -- python dreambooth.py \ + --progress-prompt 'A photo of an sks dog lying on the sand at a beach in Greece' \ + --progress-every 150 --iterations 300 --learning-rate 0.0001 \ + --lora-rank 4 --grad-accumulate 2 \ + mlx-community/dreambooth-dog6 +``` + +Note the iterations that changed to 300 from 1200 and the gradient accumulations to 2 from 8. + +### Distributed Inference + +Distributed inference can be divided in two different approaches. The first +approach is the data-parallel approach, where each node generates its own +images and shares them at the end. The second approach is the model-parallel +approach where the model is shared across the nodes and they collaboratively +generate the images. + +The `txt2image.py` script will attempt to choose the best approach depending on +how many images are being generated across the nodes. The model-parallel +approach can be forced by passing the argument `--force-shard`. + +For better performance in the model-parallel approach we suggest that you use a +[thunderbolt +ring](https://ml-explore.github.io/mlx/build/html/usage/distributed.html#getting-started-with-ring). + +All you have to do once again is use `mlx.launch` as follows + +```shell +mlx.launch --verbose --hostfile hostfile.json -- \ + python txt2image.py --model schnell \ + --n-images 8 \ + --image-size 512x512 \ + --verbose \ + 'A photo of an astronaut riding a horse on Mars' +``` + +for model-parallel generation you may want to also pass `--env +MLX_METAL_FAST_SYNCH=1` to `mlx.launch` which is an experimental setting that +reduces the CPU/GPU synchronization overhead. diff --git a/flux/flux/layers.py b/flux/flux/layers.py index 12397904..045f1e38 100644 --- a/flux/flux/layers.py +++ b/flux/flux/layers.py @@ -178,6 +178,8 @@ class DoubleStreamBlock(nn.Module): nn.Linear(mlp_hidden_dim, hidden_size, bias=True), ) + self.sharding_group = None + def __call__( self, img: mx.array, txt: mx.array, vec: mx.array, pe: mx.array ) -> Tuple[mx.array, mx.array]: @@ -216,18 +218,35 @@ class DoubleStreamBlock(nn.Module): attn = _attention(q, k, v, pe) txt_attn, img_attn = mx.split(attn, [S], axis=1) + # Project - cat - average - split + txt_attn = self.txt_attn.proj(txt_attn) + img_attn = self.img_attn.proj(img_attn) + if self.sharding_group is not None: + attn = mx.concatenate([txt_attn, img_attn], axis=1) + attn = mx.distributed.all_sum(attn, group=self.sharding_group) + txt_attn, img_attn = mx.split(attn, [S], axis=1) + # calculate the img bloks - img = img + img_mod1.gate * self.img_attn.proj(img_attn) - img = img + img_mod2.gate * self.img_mlp( + img = img + img_mod1.gate * img_attn + img_mlp = self.img_mlp( (1 + img_mod2.scale) * self.img_norm2(img) + img_mod2.shift ) # calculate the txt bloks - txt = txt + txt_mod1.gate * self.txt_attn.proj(txt_attn) - txt = txt + txt_mod2.gate * self.txt_mlp( + txt = txt + txt_mod1.gate * txt_attn + txt_mlp = self.txt_mlp( (1 + txt_mod2.scale) * self.txt_norm2(txt) + txt_mod2.shift ) + if self.sharding_group is not None: + txt_img = mx.concatenate([txt_mlp, img_mlp], axis=1) + txt_img = mx.distributed.all_sum(txt_img, group=self.sharding_group) + txt_mlp, img_mlp = mx.split(txt_img, [S], axis=1) + + # finalize the img/txt blocks + img = img + img_mod2.gate * img_mlp + txt = txt + txt_mod2.gate * txt_mlp + return img, txt diff --git a/flux/flux/model.py b/flux/flux/model.py index d8ad9d9b..c524edf3 100644 --- a/flux/flux/model.py +++ b/flux/flux/model.py @@ -5,6 +5,7 @@ from typing import Optional import mlx.core as mx import mlx.nn as nn +from mlx.nn.layers.distributed import shard_inplace, shard_linear from .layers import ( DoubleStreamBlock, @@ -96,6 +97,47 @@ class Flux(nn.Module): new_weights[k] = w return new_weights + def shard(self, group: Optional[mx.distributed.Group] = None): + group = group or mx.distributed.init() + N = group.size() + if N == 1: + return + + for block in self.double_blocks: + block.num_heads //= N + block.img_attn.num_heads //= N + block.txt_attn.num_heads //= N + block.sharding_group = group + block.img_attn.qkv = shard_linear( + block.img_attn.qkv, "all-to-sharded", segments=3, group=group + ) + block.txt_attn.qkv = shard_linear( + block.txt_attn.qkv, "all-to-sharded", segments=3, group=group + ) + shard_inplace(block.img_attn.proj, "sharded-to-all", group=group) + shard_inplace(block.txt_attn.proj, "sharded-to-all", group=group) + block.img_mlp.layers[0] = shard_linear( + block.img_mlp.layers[0], "all-to-sharded", group=group + ) + block.txt_mlp.layers[0] = shard_linear( + block.txt_mlp.layers[0], "all-to-sharded", group=group + ) + shard_inplace(block.img_mlp.layers[2], "sharded-to-all", group=group) + shard_inplace(block.txt_mlp.layers[2], "sharded-to-all", group=group) + + for block in self.single_blocks: + block.num_heads //= N + block.hidden_size //= N + block.linear1 = shard_linear( + block.linear1, + "all-to-sharded", + segments=[1 / 7, 2 / 7, 3 / 7], + group=group, + ) + block.linear2 = shard_linear( + block.linear2, "sharded-to-all", segments=[1 / 5], group=group + ) + def __call__( self, img: mx.array, diff --git a/flux/generate_interactive.py b/flux/generate_interactive.py new file mode 100644 index 00000000..9acde33c --- /dev/null +++ b/flux/generate_interactive.py @@ -0,0 +1,109 @@ +import argparse + +import mlx.core as mx +import mlx.nn as nn +import numpy as np +from PIL import Image +from tqdm import tqdm + +from flux import FluxPipeline + + +def print_zero(group, *args, **kwargs): + if group.rank() == 0: + flush = kwargs.pop("flush", True) + print(*args, **kwargs, flush=flush) + + +def quantization_predicate(name, m): + return hasattr(m, "to_quantized") and m.weight.shape[1] % 512 == 0 + + +def to_latent_size(image_size): + h, w = image_size + h = ((h + 15) // 16) * 16 + w = ((w + 15) // 16) * 16 + + if (h, w) != image_size: + print( + "Warning: The image dimensions need to be divisible by 16px. " + f"Changing size to {h}x{w}." + ) + + return (h // 8, w // 8) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Generate images from a textual prompt using FLUX" + ) + parser.add_argument("--quantize", "-q", action="store_true") + parser.add_argument("--model", choices=["schnell", "dev"], default="schnell") + parser.add_argument("--output", default="out.png") + args = parser.parse_args() + + flux = FluxPipeline("flux-" + args.model, t5_padding=True) + + if args.quantize: + nn.quantize(flux.flow, class_predicate=quantization_predicate) + nn.quantize(flux.t5, class_predicate=quantization_predicate) + nn.quantize(flux.clip, class_predicate=quantization_predicate) + + group = mx.distributed.init() + if group.size() > 1: + flux.flow.shard(group) + + print_zero(group, "Loading models") + flux.ensure_models_are_loaded() + + def print_help(): + print_zero(group, "The command list:") + print_zero(group, "- 'q' to exit") + print_zero(group, "- 's HxW' to change the size of the image") + print_zero(group, "- 'n S' to change the number of steps") + print_zero(group, "- 'h' to print this help") + + print_zero(group, "FLUX interactive session") + print_help() + seed = 0 + size = (512, 512) + latent_size = to_latent_size(size) + steps = 50 if args.model == "dev" else 4 + while True: + prompt = input(">> " if group.rank() == 0 else "") + if prompt == "q": + break + if prompt == "h": + print_help() + continue + if prompt.startswith("s "): + size = tuple([int(xi) for xi in prompt[2:].split("x")]) + print_zero(group, "Setting the size to", size) + latent_size = to_latent_size(size) + continue + if prompt.startswith("n "): + steps = int(prompt[2:]) + print_zero(group, "Setting the steps to", steps) + continue + + seed += 1 + latents = flux.generate_latents( + prompt, + n_images=1, + num_steps=steps, + latent_size=latent_size, + guidance=4.0, + seed=seed, + ) + print_zero(group, "Processing prompt") + mx.eval(next(latents)) + print_zero(group, "Generating latents") + for xt in tqdm(latents, total=steps, disable=group.rank() > 0): + mx.eval(xt) + print_zero(group, "Generating image") + xt = flux.decode(xt, latent_size) + xt = (xt * 255).astype(mx.uint8) + mx.eval(xt) + im = Image.fromarray(np.array(xt[0])) + im.save(args.output) + print_zero(group, "Saved at", args.output, end="\n\n") diff --git a/flux/txt2image.py b/flux/txt2image.py index 5ebec81a..cae0a6d9 100644 --- a/flux/txt2image.py +++ b/flux/txt2image.py @@ -41,7 +41,7 @@ def load_adapter(flux, adapter_file, fuse=False): if __name__ == "__main__": parser = argparse.ArgumentParser( - description="Generate images from a textual prompt using stable diffusion" + description="Generate images from a textual prompt using FLUX" ) parser.add_argument("prompt") parser.add_argument("--model", choices=["schnell", "dev"], default="schnell") @@ -62,6 +62,7 @@ if __name__ == "__main__": parser.add_argument("--adapter") parser.add_argument("--fuse-adapter", action="store_true") parser.add_argument("--no-t5-padding", dest="t5_padding", action="store_false") + parser.add_argument("--force-shard", action="store_true") args = parser.parse_args() # Load the models @@ -76,6 +77,24 @@ if __name__ == "__main__": nn.quantize(flux.t5, class_predicate=quantization_predicate) nn.quantize(flux.clip, class_predicate=quantization_predicate) + # Figure out what kind of distributed generation we should do + group = mx.distributed.init() + n_images = args.n_images + should_gather = False + if group.size() > 1: + if args.force_shard or n_images < group.size() or n_images % group.size() != 0: + flux.flow.shard(group) + else: + n_images //= group.size() + should_gather = True + + # If we are sharding we should have the same seed and if we are doing + # data parallel generation we should have different seeds + if args.seed is None: + args.seed = mx.distributed.all_sum(mx.random.randint(0, 2**20)).item() + if should_gather: + args.seed = args.seed + group.rank() + if args.preload_models: flux.ensure_models_are_loaded() @@ -83,7 +102,7 @@ if __name__ == "__main__": latent_size = to_latent_size(args.image_size) latents = flux.generate_latents( args.prompt, - n_images=args.n_images, + n_images=n_images, num_steps=args.steps, latent_size=latent_size, guidance=args.guidance, @@ -93,8 +112,8 @@ if __name__ == "__main__": # First we get and eval the conditioning conditioning = next(latents) mx.eval(conditioning) - peak_mem_conditioning = mx.metal.get_peak_memory() / 1024**3 - mx.metal.reset_peak_memory() + peak_mem_conditioning = mx.get_peak_memory() / 1024**3 + mx.reset_peak_memory() # The following is not necessary but it may help in memory constrained # systems by reusing the memory kept by the text encoders. @@ -102,36 +121,42 @@ if __name__ == "__main__": del flux.clip # Actual denoising loop - for x_t in tqdm(latents, total=args.steps): + for x_t in tqdm(latents, total=args.steps, disable=group.rank() > 0): mx.eval(x_t) # The following is not necessary but it may help in memory constrained # systems by reusing the memory kept by the flow transformer. del flux.flow - peak_mem_generation = mx.metal.get_peak_memory() / 1024**3 - mx.metal.reset_peak_memory() + peak_mem_generation = mx.get_peak_memory() / 1024**3 + mx.reset_peak_memory() # Decode them into images decoded = [] - for i in tqdm(range(0, args.n_images, args.decoding_batch_size)): + for i in tqdm(range(0, n_images, args.decoding_batch_size)): decoded.append(flux.decode(x_t[i : i + args.decoding_batch_size], latent_size)) mx.eval(decoded[-1]) - peak_mem_decoding = mx.metal.get_peak_memory() / 1024**3 + peak_mem_decoding = mx.get_peak_memory() / 1024**3 peak_mem_overall = max( peak_mem_conditioning, peak_mem_generation, peak_mem_decoding ) + # Gather them if each node has different images + decoded = mx.concatenate(decoded, axis=0) + if should_gather: + decoded = mx.distributed.all_gather(decoded) + mx.eval(decoded) + if args.save_raw: *name, suffix = args.output.split(".") name = ".".join(name) - x = mx.concatenate(decoded, axis=0) + x = decoded x = (x * 255).astype(mx.uint8) for i in range(len(x)): im = Image.fromarray(np.array(x[i])) im.save(".".join([name, str(i), suffix])) else: # Arrange them on a grid - x = mx.concatenate(decoded, axis=0) + x = decoded x = mx.pad(x, [(0, 0), (4, 4), (4, 4), (0, 0)]) B, H, W, C = x.shape x = x.reshape(args.n_rows, B // args.n_rows, H, W, C).transpose(0, 2, 1, 3, 4) @@ -143,7 +168,7 @@ if __name__ == "__main__": im.save(args.output) # Report the peak memory used during generation - if args.verbose: + if args.verbose and group.rank() == 0: print(f"Peak memory used for the text: {peak_mem_conditioning:.3f}GB") print(f"Peak memory used for the generation: {peak_mem_generation:.3f}GB") print(f"Peak memory used for the decoding: {peak_mem_decoding:.3f}GB") From 4c9f9f9be798e6cf04fd0f74395a3b4420077aad Mon Sep 17 00:00:00 2001 From: Param Thakkar <128291516+ParamThakkar123@users.noreply.github.com> Date: Thu, 24 Apr 2025 02:53:46 +0530 Subject: [PATCH 186/188] Made llama and mistral files mypy compatible (#1359) * Made mypy compatible * reformatted * Added more fixes * Added fixes to speculative-decoding * Fixes * fix circle * revert some stuff --------- Co-authored-by: Awni Hannun --- .circleci/config.yml | 2 -- llms/gguf_llm/generate.py | 2 +- llms/gguf_llm/models.py | 10 +++++----- llms/llama/convert.py | 4 +++- llms/mixtral/mixtral.py | 4 +--- llms/speculative_decoding/decoder.py | 10 +++++----- llms/speculative_decoding/model.py | 8 ++++---- lora/lora.py | 3 ++- lora/models.py | 6 +++--- lora/utils.py | 12 ++++++++---- 10 files changed, 32 insertions(+), 29 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 42a39194..aec28e77 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -36,7 +36,5 @@ workflows: type: approval - apple/authenticate: context: pr-approval - - mlx_lm_build_and_test: - requires: [ hold ] - linux_build_and_test: requires: [ hold ] diff --git a/llms/gguf_llm/generate.py b/llms/gguf_llm/generate.py index 7215aa48..db327cda 100644 --- a/llms/gguf_llm/generate.py +++ b/llms/gguf_llm/generate.py @@ -40,7 +40,7 @@ def generate( if len(tokens) == 0: print("No tokens generated for this prompt") return - prompt_tps = prompt.size / prompt_time + prompt_tps = len(prompt) / prompt_time gen_tps = (len(tokens) - 1) / gen_time print(f"Prompt: {prompt_tps:.3f} tokens-per-sec") print(f"Generation: {gen_tps:.3f} tokens-per-sec") diff --git a/llms/gguf_llm/models.py b/llms/gguf_llm/models.py index 3b0afc65..9e1f9666 100644 --- a/llms/gguf_llm/models.py +++ b/llms/gguf_llm/models.py @@ -19,10 +19,10 @@ class ModelArgs: rms_norm_eps: float vocab_size: int context_length: int - num_key_value_heads: int = None + num_key_value_heads: Optional[int] = None rope_theta: float = 10000 rope_traditional: bool = False - model_type: str = None + model_type: Optional[str] = None rope_scaling: Optional[Dict[str, Union[float, str]]] = None def __post_init__(self): @@ -54,7 +54,7 @@ class Attention(nn.Module): dim = args.hidden_size self.n_heads = n_heads = args.num_attention_heads - self.n_kv_heads = n_kv_heads = args.num_key_value_heads + self.n_kv_heads = n_kv_heads = args.num_key_value_heads or n_heads self.repeats = n_heads // n_kv_heads @@ -66,7 +66,7 @@ class Attention(nn.Module): self.v_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=False) self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=False) rope_scale = ( - 1 / args.rope_scaling["factor"] + 1 / float(args.rope_scaling["factor"]) if args.rope_scaling is not None and args.rope_scaling["type"] == "linear" else 1 ) @@ -254,7 +254,7 @@ def translate_weight_names(name): return name -def load(gguf_file: str, repo: str = None): +def load(gguf_file: str, repo: Optional[str] = None): # If the gguf_file exists, try to load model from it. # Otherwise try to download and cache from the HF repo if not Path(gguf_file).exists(): diff --git a/llms/llama/convert.py b/llms/llama/convert.py index 04c10a5f..33610f44 100644 --- a/llms/llama/convert.py +++ b/llms/llama/convert.py @@ -7,6 +7,7 @@ import glob import json import shutil from pathlib import Path +from typing import Dict import mlx.core as mx import mlx.nn as nn @@ -149,7 +150,8 @@ def quantize(weights, config, args): def make_shards(weights: dict, max_file_size_gibibyte: int = 15): max_file_size_bytes = max_file_size_gibibyte << 30 shards = [] - shard, shard_size = {}, 0 + shard: Dict[str, mx.array] = {} + shard_size = 0 for k, v in weights.items(): if shard_size + v.nbytes > max_file_size_bytes: shards.append(shard) diff --git a/llms/mixtral/mixtral.py b/llms/mixtral/mixtral.py index 4b45d066..653dad57 100644 --- a/llms/mixtral/mixtral.py +++ b/llms/mixtral/mixtral.py @@ -23,7 +23,7 @@ class ModelArgs: n_kv_heads: int norm_eps: float vocab_size: int - moe: dict = None + moe: dict class Attention(nn.Module): @@ -91,7 +91,6 @@ class FeedForward(nn.Module): class MOEFeedForward(nn.Module): def __init__(self, args: ModelArgs): super().__init__() - self.num_experts = args.moe["num_experts"] self.num_experts_per_tok = args.moe["num_experts_per_tok"] self.experts = [FeedForward(args) for _ in range(self.num_experts)] @@ -115,7 +114,6 @@ class MOEFeedForward(nn.Module): yt = (yt * st).sum(axis=-1) y.append(yt[None, :]) y = mx.concatenate(y) - return y.reshape(orig_shape) diff --git a/llms/speculative_decoding/decoder.py b/llms/speculative_decoding/decoder.py index 3d547a7f..39cf5b92 100644 --- a/llms/speculative_decoding/decoder.py +++ b/llms/speculative_decoding/decoder.py @@ -160,12 +160,12 @@ class SpeculativeDecoder: ) n_accepted += num_to_accept - n_draft += draft_tokens.size + n_draft += len(draft_tokens) # Rewind the cache for unaccepted tokens: - if (n := draft_tokens.size) > num_to_accept: - self.draft_model.truncate_cache(n - new_tokens.size) - self.model.truncate_cache(n - new_tokens.size + 1) + if (n := len(draft_tokens)) > num_to_accept: + self.draft_model.truncate_cache(n - len(new_tokens)) + self.model.truncate_cache(n - len(new_tokens) + 1) n_steps += 1 @@ -181,7 +181,7 @@ class SpeculativeDecoder: if ntoks >= max_tokens or new_tokens[-1] == self.tokenizer.eos_id: break - draft_inputs = new_tokens[max(new_tokens.size - 2, 0) :] + draft_inputs = new_tokens[max(len(new_tokens) - 2, 0) :] inputs = draft_inputs[-1:] print(self.tokenizer.decode(outputs)[skip:], end="", flush=True) diff --git a/llms/speculative_decoding/model.py b/llms/speculative_decoding/model.py index c310b943..d30daa97 100644 --- a/llms/speculative_decoding/model.py +++ b/llms/speculative_decoding/model.py @@ -213,10 +213,10 @@ class TransformerDecoderLayer(nn.Module): memory: mx.array, mask: mx.array, memory_mask: mx.array, - cache: Optional[List[Tuple[mx.array, mx.array]]] = None, - ): + cache: Optional[Tuple[mx.array, mx.array]] = None, + ) -> Tuple[mx.array, Tuple[mx.array, mx.array]]: y = self.ln1(x) - y, cache = self.self_attention(y, y, y, mask, cache) + y, new_cache = self.self_attention(y, y, y, mask, cache) x = x + y y = self.ln2(x) @@ -227,7 +227,7 @@ class TransformerDecoderLayer(nn.Module): y = self.dense(y) x = x + y - return x, cache + return x, new_cache def create_additive_causal_mask(N: int, offset: int = 0): diff --git a/lora/lora.py b/lora/lora.py index 6f91ccca..7fc3998b 100644 --- a/lora/lora.py +++ b/lora/lora.py @@ -3,6 +3,7 @@ import argparse import json import math +import os import sys import time from pathlib import Path @@ -16,7 +17,7 @@ from mlx.utils import tree_flatten from models import LoRALinear # Disable output buffering to see print statements in real-time -sys.stdout.reconfigure(line_buffering=True) +sys.stdout = os.fdopen(sys.stdout.fileno(), "w", buffering=1) def build_parser(): diff --git a/lora/models.py b/lora/models.py index 3e85b135..acafbc61 100644 --- a/lora/models.py +++ b/lora/models.py @@ -17,10 +17,10 @@ class ModelArgs: num_attention_heads: int rms_norm_eps: float vocab_size: int - num_key_value_heads: int = None + num_key_value_heads: Optional[int] = None rope_theta: float = 10000 rope_traditional: bool = False - model_type: str = None + model_type: Optional[str] = None rope_scaling: Optional[Dict[str, Union[float, str]]] = None def __post_init__(self): @@ -146,7 +146,7 @@ class Attention(nn.Module): self.v_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=False) self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=False) rope_scale = ( - 1 / args.rope_scaling["factor"] + 1 / float(args.rope_scaling["factor"]) if args.rope_scaling is not None and args.rope_scaling["type"] == "linear" else 1 ) diff --git a/lora/utils.py b/lora/utils.py index a334723c..db9c0876 100644 --- a/lora/utils.py +++ b/lora/utils.py @@ -4,7 +4,7 @@ import glob import json import logging from pathlib import Path -from typing import Generator +from typing import Any, Dict, Generator, Union import mlx.core as mx import mlx.nn as nn @@ -72,7 +72,8 @@ python generate.py --model {repo_id} --prompt "My name is" def make_shards(weights: dict, max_file_size_gibibyte: int = 15): max_file_size_bytes = max_file_size_gibibyte << 30 shards = [] - shard, shard_size = {}, 0 + shard: Dict[str, mx.array] = {} + shard_size = 0 for k, v in weights.items(): if shard_size + v.nbytes > max_file_size_bytes: shards.append(shard) @@ -83,7 +84,7 @@ def make_shards(weights: dict, max_file_size_gibibyte: int = 15): return shards -def save_model(save_dir: str, weights, tokenizer, config): +def save_model(save_dir: Union[str, Path], weights, tokenizer, config): save_dir = Path(save_dir) save_dir.mkdir(parents=True, exist_ok=True) @@ -96,7 +97,10 @@ def save_model(save_dir: str, weights, tokenizer, config): ) total_size = sum(v.nbytes for v in weights.values()) - index_data = {"metadata": {"total_size": total_size}, "weight_map": {}} + index_data: Dict[str, Any] = { + "metadata": {"total_size": total_size}, + "weight_map": {}, + } for i, shard in enumerate(shards): shard_name = shard_file_format.format(i + 1, shards_count) From 977cd30242b9891c306ef23ba925553104eb4f0c Mon Sep 17 00:00:00 2001 From: Denrei Keith <42316655+dkeithdj@users.noreply.github.com> Date: Thu, 1 May 2025 21:00:14 +0800 Subject: [PATCH 187/188] Update lora README.md (#1365) point to the correct repository https://github.com/ml-explore/mlx-lm --- lora/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lora/README.md b/lora/README.md index 844d8b28..e5b37003 100644 --- a/lora/README.md +++ b/lora/README.md @@ -7,7 +7,7 @@ available on Hugging Face. > [!TIP] > For a more fully featured LLM package, checkout [MLX -> LM](https://github.com/ml-explore/mlx-examples/tree/main/llms/mlx_lm). +> LM](https://github.com/ml-explore/mlx-lm). In this example we'll use the WikiSQL[^wikisql] dataset to train the LLM to generate SQL queries from natural language. However, the example is intended to From 4b2a0df23756b2c7b8470e0a7b26978b1ee81066 Mon Sep 17 00:00:00 2001 From: Shashank Date: Tue, 10 Jun 2025 10:23:25 -0700 Subject: [PATCH 188/188] adding wwdc25 samples (#1370) --- ...age_models_on_Apple_silicon_with_MLX.ipynb | 822 +++++++ ...t_started_with_MLX_for_Apple_silicon.ipynb | 685 ++++++ wwdc25/README.md | 57 + .../project.pbxproj | 385 ++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/swiftpm/Package.resolved | 78 + .../UserInterfaceState.xcuserstate | Bin 0 -> 26678 bytes .../xcschemes/xcschememanagement.plist | 14 + .../WWDC25MLXSwiftExamples/SimpleMLXLM.swift | 25 + .../SimpleMLXLMWithKVCache.swift | 48 + .../WWDC25MLXSwiftExamples/main.swift | 32 + wwdc25/data/all.jsonl | 1944 +++++++++++++++++ wwdc25/data/train.jsonl | 1800 +++++++++++++++ wwdc25/data/valid.jsonl | 144 ++ wwdc25/requirements.txt | 12 + 15 files changed, 6053 insertions(+) create mode 100644 wwdc25/Explore_language_models_on_Apple_silicon_with_MLX.ipynb create mode 100644 wwdc25/Get_started_with_MLX_for_Apple_silicon.ipynb create mode 100644 wwdc25/README.md create mode 100644 wwdc25/WWDC25MLXSwiftExamples/WWDC25MLXSwiftExamples.xcodeproj/project.pbxproj create mode 100644 wwdc25/WWDC25MLXSwiftExamples/WWDC25MLXSwiftExamples.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 wwdc25/WWDC25MLXSwiftExamples/WWDC25MLXSwiftExamples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 wwdc25/WWDC25MLXSwiftExamples/WWDC25MLXSwiftExamples.xcodeproj/project.xcworkspace/xcuserdata/shashankprasanna.xcuserdatad/UserInterfaceState.xcuserstate create mode 100644 wwdc25/WWDC25MLXSwiftExamples/WWDC25MLXSwiftExamples.xcodeproj/xcuserdata/shashankprasanna.xcuserdatad/xcschemes/xcschememanagement.plist create mode 100644 wwdc25/WWDC25MLXSwiftExamples/WWDC25MLXSwiftExamples/SimpleMLXLM.swift create mode 100644 wwdc25/WWDC25MLXSwiftExamples/WWDC25MLXSwiftExamples/SimpleMLXLMWithKVCache.swift create mode 100644 wwdc25/WWDC25MLXSwiftExamples/WWDC25MLXSwiftExamples/main.swift create mode 100644 wwdc25/data/all.jsonl create mode 100644 wwdc25/data/train.jsonl create mode 100644 wwdc25/data/valid.jsonl create mode 100644 wwdc25/requirements.txt diff --git a/wwdc25/Explore_language_models_on_Apple_silicon_with_MLX.ipynb b/wwdc25/Explore_language_models_on_Apple_silicon_with_MLX.ipynb new file mode 100644 index 00000000..871de0d0 --- /dev/null +++ b/wwdc25/Explore_language_models_on_Apple_silicon_with_MLX.ipynb @@ -0,0 +1,822 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "74bc2ccb", + "metadata": {}, + "source": [ + "# Explore large language models on Apple silicon with MLX" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "a9f4b67f", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "os.environ[\"TOKENIZERS_PARALLELISM\"]=\"false\"" + ] + }, + { + "cell_type": "markdown", + "id": "d23eda5f", + "metadata": {}, + "source": [ + "### Demo 1: Running DeepSeek AI’s latest model with 670 billion parameters.\n", + "* Note 1: This example requires Mac Studio M3 Ultra with 512 GB of unified memory.\n", + "* Note 2: Copy paste the line below and run it in the terminal, since Jupyter Notebook output doesn't allow turn-by-turn chat iteraction" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "5a45bf5a", + "metadata": {}, + "outputs": [], + "source": [ + "# Run this command in the terminal to chat with `DeepSeek-V3-0324-4bit`\n", + "#mlx_lm.chat --model mlx-community/DeepSeek-V3-0324-4bit" + ] + }, + { + "cell_type": "markdown", + "id": "292a968d", + "metadata": {}, + "source": [ + "### Using the `mlx_lm.generate` command" + ] + }, + { + "cell_type": "markdown", + "id": "38f6b4a1", + "metadata": {}, + "source": [ + "Easiest way to generate text with LLMs is to use the `mlx_lm.generate` command" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "51bd2ed4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Fetching 7 files: 100%|███████████████████████| 7/7 [00:00<00:00, 120328.39it/s]\n", + "==========\n", + "Here's a simple implementation of the QuickSort algorithm in Swift. This version uses Swift's built-in `swapAt()` function to swap elements in an array.\n", + "\n", + "```swift\n", + "func quickSort(_ array: inout [Int], _ low: Int, _ high: Int) {\n", + " if low < high {\n", + " let pivotIndex = partition(array, low, high)\n", + " quickSort(&array, low, pivot\n", + "==========\n", + "Prompt: 12 tokens, 78.111 tokens-per-sec\n", + "Generation: 100 tokens, 32.263 tokens-per-sec\n", + "Peak memory: 4.138 GB\n" + ] + } + ], + "source": [ + "!mlx_lm.generate --model \"mlx-community/Mistral-7B-Instruct-v0.3-4bit\" \\\n", + " --prompt \"Write a quick sort in Swift\"" + ] + }, + { + "cell_type": "markdown", + "id": "1fa6be17", + "metadata": {}, + "source": [ + "You can tweak the behavior of the model by adding flags for things like sampling temperature, top-p, or max tokens; just like with any standard text generation setup." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "f7add212", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Fetching 7 files: 100%|███████████████████████| 7/7 [00:00<00:00, 100205.22it/s]\n", + "==========\n", + "Here's a simple implementation of the QuickSort algorithm in Swift. This version uses Swift's built-in `swapAt()` function to swap elements in an array.\n", + "\n", + "```swift\n", + "func quickSort(_ array: inout [Int], _ low: Int, _ high: Int) {\n", + " if low < high {\n", + " let pivotIndex = partition(array, low, high)\n", + " quickSort(&array, low, pivotIndex - 1)\n", + " quickSort(&array, pivotIndex + 1, high)\n", + " }\n", + "}\n", + "\n", + "func partition(_ array: inout [Int], _ low: Int, _ high: Int) -> Int {\n", + " let pivot = array[high]\n", + " var i = low\n", + " for j in low.. Int {\n", + " let pivot = array[high]\n", + " var i = low\n", + " for j in low.. Int {\n", + " let pivot = array[high]\n", + " var i = low\n", + " for j in low.." + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "def plot_waves(x, fn1, fn2, fn3, labels):\n", + "\n", + " # Generate y values for sin(x) and dfdx(sin(x))\n", + " y_1 = fn1(x)\n", + " y_2 = fn2(x)\n", + " y_3 = fn3(x)\n", + "\n", + " # Create the plot\n", + " plt.figure(figsize=(10, 6))\n", + " # Note: x is already in degrees here for plotting\n", + " plt.plot(x, y_1, label=labels[0], marker='o', linestyle='-', markersize=5, markevery=20)\n", + " plt.plot(x, y_2, label=labels[1], marker='x', linestyle='--', markersize=5, markevery=20)\n", + " plt.plot(x, y_3, label=labels[2], marker='*', linestyle='-.', markersize=5, markevery=20)\n", + "\n", + " # Add labels\n", + " plt.xlabel('X-axis (Degrees)') # Changed label here\n", + " plt.ylabel('Y-axis')\n", + "\n", + " plt.legend()\n", + " plt.grid(True)\n", + " plt.show()\n", + "\n", + "\n", + "x = mx.linspace(0, 2 * mx.pi, 400)\n", + "\n", + "cos = mx.vmap(dfdx)\n", + "negative_sin = mx.vmap(d2fdx2)\n", + "\n", + "plot_waves(x, sin, cos, negative_sin, [\"sin(x)\",\"dfdx(sin(x))\", \"d2fdx2(sin(x))\"])" + ] + }, + { + "cell_type": "markdown", + "id": "bbc810b7", + "metadata": {}, + "source": [ + "### Neural Networks in MLX and Pytorch" + ] + }, + { + "cell_type": "markdown", + "id": "be4d8dd4", + "metadata": {}, + "source": [ + "MLX Neural Network" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "fd53cc03", + "metadata": {}, + "outputs": [], + "source": [ + "import mlx.core as mx\n", + "import mlx.nn as nn\n", + "import mlx.optimizers as optim\n", + "\n", + "class MLP(nn.Module):\n", + " \"\"\"A simple MLP.\"\"\"\n", + "\n", + " def __init__(self, dim, h_dim):\n", + " super().__init__()\n", + " self.linear1 = nn.Linear(dim, h_dim)\n", + " self.linear2 = nn.Linear(h_dim, dim)\n", + "\n", + " def __call__(self, x):\n", + " x = self.linear1(x)\n", + " x = nn.relu(x)\n", + " x = self.linear2(x)\n", + " return x" + ] + }, + { + "cell_type": "markdown", + "id": "ace491fb", + "metadata": {}, + "source": [ + "MLX Training loop" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "80f3d568", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Final Loss after 5 steps: 1.0052\n" + ] + } + ], + "source": [ + "n_epochs = 5\n", + "input_dim, hidden_dim, num_samples = 10, 50, 1000\n", + "\n", + "model = MLP(input_dim, hidden_dim)\n", + "\n", + "def loss_fn(model, X, y):\n", + " return nn.losses.mse_loss(model(X), y)\n", + "\n", + "loss_and_grad_fn = nn.value_and_grad(model, loss_fn)\n", + "optimizer = optim.Adam(learning_rate=0.01)\n", + "\n", + "X_train = mx.random.normal([num_samples, input_dim])\n", + "y_train = mx.random.normal([num_samples, input_dim])\n", + "\n", + "for epoch in range(n_epochs):\n", + " loss, grads = loss_and_grad_fn(model, X_train, y_train)\n", + " model.update(optimizer.apply_gradients(grads, model))\n", + " mx.eval(model.parameters(), optimizer.state) \n", + "\n", + "print(f\"Final Loss after 5 steps: {loss.item():.4f}\")" + ] + }, + { + "cell_type": "markdown", + "id": "5fa466a7", + "metadata": {}, + "source": [ + "PyTorch Neural Network" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "1c39f647", + "metadata": {}, + "outputs": [], + "source": [ + "import torch\n", + "import torch.nn as nn\n", + "import torch.optim as optim\n", + "\n", + "class MLP(nn.Module):\n", + " \"\"\"A simple MLP.\"\"\"\n", + "\n", + " def __init__(self, dim, h_dim):\n", + " super().__init__()\n", + " self.linear1 = nn.Linear(dim, h_dim)\n", + " self.linear2 = nn.Linear(h_dim, dim)\n", + "\n", + " def forward(self, x):\n", + " x = self.linear1(x)\n", + " x = x.relu()\n", + " x = self.linear2(x)\n", + " return x" + ] + }, + { + "cell_type": "markdown", + "id": "1e568017", + "metadata": {}, + "source": [ + "PyTorch Training loop" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "0d1b3dc5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Final Loss after 5 steps: 1.0028\n" + ] + } + ], + "source": [ + "n_epochs = 5\n", + "input_dim, hidden_dim, num_samples = 10, 50, 1000\n", + "model = MLP(input_dim, hidden_dim)\n", + "criterion = nn.MSELoss()\n", + "optimizer = optim.Adam(model.parameters(), lr=0.01)\n", + "\n", + "X_train = torch.randn([num_samples, input_dim])\n", + "y_train = torch.randn([num_samples, input_dim])\n", + "\n", + "for epoch in range(n_epochs):\n", + " outputs = model(X_train)\n", + " loss = criterion(outputs, y_train)\n", + " optimizer.zero_grad()\n", + " loss.backward()\n", + " optimizer.step()\n", + "\n", + "print(f\"Final Loss after 5 steps: {loss.item():.4f}\")" + ] + }, + { + "cell_type": "markdown", + "id": "b9ace438", + "metadata": {}, + "source": [ + "### Compiling MLX functions" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "e1a6d2f6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gelu: array([-0.169571, -0.0094711, 0.120888, -0.122945], dtype=float32)\n", + "compiled gelu: array([-0.169571, -0.0094711, 0.120888, -0.122945], dtype=float32)\n" + ] + } + ], + "source": [ + "import mlx.core as mx\n", + "import math\n", + "\n", + "def gelu(x):\n", + " return x * (1 + mx.erf(x / math.sqrt(2))) / 2\n", + "\n", + "@mx.compile\n", + "def compiled_gelu(x):\n", + " return x * (1 + mx.erf(x / math.sqrt(2))) / 2\n", + "\n", + "x = mx.random.normal(shape=(4,))\n", + "\n", + "out = gelu(x)\n", + "compiled_out = compiled_gelu(x)\n", + "print(f\"gelu: {out}\")\n", + "print(f\"compiled gelu: {compiled_out}\")" + ] + }, + { + "cell_type": "markdown", + "id": "3ead2025", + "metadata": {}, + "source": [ + "### MLX Fast package" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "efe967cf", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "RMS norm: array([-1.47364, 0.545241, -0.767421, 0.140266], dtype=float32)\n", + "Fast RMS norm: array([-1.47364, 0.545241, -0.767421, 0.140266], dtype=float32)\n" + ] + } + ], + "source": [ + "import mlx.core as mx\n", + "\n", + "def rms_norm(x, weight, eps=1e-5):\n", + " y = x.astype(mx.float32)\n", + " y = y * mx.rsqrt(mx.mean(\n", + " mx.square(y),\n", + " axis=-1,\n", + " keepdims=True,\n", + " ) + eps)\n", + " return (weight * y).astype(x.dtype)\n", + "\n", + "feature_dim = 4\n", + "\n", + "x = mx.random.normal((feature_dim,))\n", + "weight = mx.random.normal((feature_dim,))\n", + "\n", + "y = rms_norm(x, weight, eps=1e-5)\n", + "y_fast = mx.fast.rms_norm(x, weight, eps=1e-5)\n", + "\n", + "print(f\"RMS norm: {y}\")\n", + "print(f\"Fast RMS norm: {y_fast}\")" + ] + }, + { + "cell_type": "markdown", + "id": "5c8d147a", + "metadata": {}, + "source": [ + "### Custom Metal kernels" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "9529b127", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([2.71828, 7.38906, 20.0855], dtype=float32)\n" + ] + } + ], + "source": [ + "import mlx.core as mx\n", + "\n", + "# Build the kernel\n", + "source = \"\"\"\n", + " uint elem = thread_position_in_grid.x;\n", + " out[elem] = metal::exp(inp[elem]);\n", + "\"\"\"\n", + "kernel = mx.fast.metal_kernel(\n", + " name=\"myexp\",\n", + " input_names=[\"inp\"],\n", + " output_names=[\"out\"],\n", + " source=source,\n", + ")\n", + "\n", + "# Call the kernel on a sample input\n", + "x = mx.array([1.0, 2.0, 3.0])\n", + "out = kernel(\n", + " inputs=[x],\n", + " grid=(x.size, 1, 1),\n", + " threadgroup=(256, 1, 1),\n", + " output_shapes=[x.shape],\n", + " output_dtypes=[x.dtype],\n", + ")[0]\n", + "print(out)" + ] + }, + { + "cell_type": "markdown", + "id": "93d536ac", + "metadata": {}, + "source": [ + "### Quantization" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "f04fe5fc", + "metadata": {}, + "outputs": [], + "source": [ + "import mlx.core as mx\n", + "\n", + "x = mx.random.normal([1024])\n", + "weight = mx.random.normal([1024, 1024])\n", + "\n", + "quantized_weight, scales, biases = mx.quantize(\n", + " weight, bits=4, group_size=32,\n", + ")\n", + "\n", + "y = mx.quantized_matmul(\n", + " x,\n", + " quantized_weight,\n", + " scales=scales,\n", + " biases=biases,\n", + " bits=4,\n", + " group_size=32,\n", + ")\n", + "\n", + "w_orig = mx.dequantize(\n", + " quantized_weight,\n", + " scales=scales,\n", + " biases=biases,\n", + " bits=4,\n", + " group_size=32,\n", + ")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "096d593a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Sequential(\n", + " (layers.0): Embedding(100, 32)\n", + " (layers.1): Linear(input_dims=32, output_dims=32, bias=True)\n", + " (layers.2): Linear(input_dims=32, output_dims=32, bias=True)\n", + " (layers.3): Linear(input_dims=32, output_dims=1, bias=True)\n", + ")\n", + "Sequential(\n", + " (layers.0): QuantizedEmbedding(100, 32, group_size=32, bits=4)\n", + " (layers.1): QuantizedLinear(input_dims=32, output_dims=32, bias=True, group_size=32, bits=4)\n", + " (layers.2): QuantizedLinear(input_dims=32, output_dims=32, bias=True, group_size=32, bits=4)\n", + " (layers.3): QuantizedLinear(input_dims=32, output_dims=1, bias=True, group_size=32, bits=4)\n", + ")\n" + ] + } + ], + "source": [ + "import mlx.nn as nn\n", + "\n", + "model = nn.Sequential(\n", + " nn.Embedding(100, 32),\n", + " nn.Linear(32, 32),\n", + " nn.Linear(32, 32),\n", + " nn.Linear(32, 1),\n", + ")\n", + "\n", + "print(model)\n", + "\n", + "nn.quantize(\n", + " model,\n", + " bits=4,\n", + " group_size=32,\n", + ")\n", + "\n", + "print(model)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "3b92700c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([1], dtype=float32)\n" + ] + } + ], + "source": [ + "import mlx.core as mx\n", + "\n", + "group = mx.distributed.init()\n", + "\n", + "world_size = group.size()\n", + "rank = group.rank()\n", + "\n", + "x = mx.array([1.0])\n", + "\n", + "x_sum = mx.distributed.all_sum(x)\n", + "\n", + "print(x_sum)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "mlx", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/wwdc25/README.md b/wwdc25/README.md new file mode 100644 index 00000000..fa769756 --- /dev/null +++ b/wwdc25/README.md @@ -0,0 +1,57 @@ +# 🧑‍💻 WWDC25 MLX Examples + +This directory includes Jupyter notebooks and Xcode project with code covered in WWDC25 MLX sessions: + +| [![](https://img.youtube.com/vi/UbzOBg8fsxo/maxresdefault.jpg)](https://www.youtube.com/watch?v=UbzOBg8fsxo) | [![](https://img.youtube.com/vi/tn2Hvw7eCsw/maxresdefault.jpg)](https://www.youtube.com/watch?v=tn2Hvw7eCsw) | +| :------------------------- | :-------------------------- | + +## 🚀 Quick Start + +1. **Download the examples** + + Clone this repository and navigate to the `wwdc25` directory + +2. **Install Python dependencies** + + If you're using `venv` + + ```bash + # create a virtual environment named 'mlx' + python3 -m venv mlx + + # activate it + source mlx/bin/activate + + # install required packages + pip install -r requirements.txt + ``` + If you're using `conda` + + ```bash + conda create -n mlx python=3.12 -y + + # activate it + conda activate mlx + + # install required packages + pip install -r requirements.txt + ``` + + +3. **Launch Jupyter Lab** + + ```bash + jupyter lab + ``` + + Your default browser should open at `http://localhost:8888/lab`. + +4. **Open the following Jupyter notebooks and run** +* [Jupyter notebook for Get started with MLX for Apple silicon](./Get_started_with_MLX_for_Apple_silicon.ipynb) +* [Jupyter notebook for Explore large language models on Apple silicon with MLX](./Explore_language_models_on_Apple_silicon_with_MLX.ipynb) + +5. **Open Xcode project and run** +* [WWDC25 MLX Swift Examples](./WWDC25MLXSwiftExamples) + +--- + diff --git a/wwdc25/WWDC25MLXSwiftExamples/WWDC25MLXSwiftExamples.xcodeproj/project.pbxproj b/wwdc25/WWDC25MLXSwiftExamples/WWDC25MLXSwiftExamples.xcodeproj/project.pbxproj new file mode 100644 index 00000000..73150ec2 --- /dev/null +++ b/wwdc25/WWDC25MLXSwiftExamples/WWDC25MLXSwiftExamples.xcodeproj/project.pbxproj @@ -0,0 +1,385 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + E51DFC5C2DF3729C00042DAC /* MLX in Frameworks */ = {isa = PBXBuildFile; productRef = E51DFC5B2DF3729C00042DAC /* MLX */; }; + E51DFC5E2DF3729C00042DAC /* MLXFFT in Frameworks */ = {isa = PBXBuildFile; productRef = E51DFC5D2DF3729C00042DAC /* MLXFFT */; }; + E51DFC602DF3729C00042DAC /* MLXFast in Frameworks */ = {isa = PBXBuildFile; productRef = E51DFC5F2DF3729C00042DAC /* MLXFast */; }; + E51DFC622DF3729C00042DAC /* MLXLinalg in Frameworks */ = {isa = PBXBuildFile; productRef = E51DFC612DF3729C00042DAC /* MLXLinalg */; }; + E51DFC642DF3729C00042DAC /* MLXNN in Frameworks */ = {isa = PBXBuildFile; productRef = E51DFC632DF3729C00042DAC /* MLXNN */; }; + E51DFC672DF372CA00042DAC /* MLXLLM in Frameworks */ = {isa = PBXBuildFile; productRef = E51DFC662DF372CA00042DAC /* MLXLLM */; }; + E51DFC692DF372CA00042DAC /* MLXLMCommon in Frameworks */ = {isa = PBXBuildFile; productRef = E51DFC682DF372CA00042DAC /* MLXLMCommon */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + E5158B942DF0BD71000A6E7E /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + E5158B962DF0BD71000A6E7E /* WWDC25MLXSwiftExamples */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = WWDC25MLXSwiftExamples; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + E5158B982DF0BD71000A6E7E /* WWDC25MLXSwiftExamples */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = WWDC25MLXSwiftExamples; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + E5158B932DF0BD71000A6E7E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + E51DFC672DF372CA00042DAC /* MLXLLM in Frameworks */, + E51DFC642DF3729C00042DAC /* MLXNN in Frameworks */, + E51DFC622DF3729C00042DAC /* MLXLinalg in Frameworks */, + E51DFC5C2DF3729C00042DAC /* MLX in Frameworks */, + E51DFC602DF3729C00042DAC /* MLXFast in Frameworks */, + E51DFC5E2DF3729C00042DAC /* MLXFFT in Frameworks */, + E51DFC692DF372CA00042DAC /* MLXLMCommon in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + E5158B8D2DF0BD71000A6E7E = { + isa = PBXGroup; + children = ( + E5158B982DF0BD71000A6E7E /* WWDC25MLXSwiftExamples */, + E5158B972DF0BD71000A6E7E /* Products */, + ); + sourceTree = ""; + }; + E5158B972DF0BD71000A6E7E /* Products */ = { + isa = PBXGroup; + children = ( + E5158B962DF0BD71000A6E7E /* WWDC25MLXSwiftExamples */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + E5158B952DF0BD71000A6E7E /* WWDC25MLXSwiftExamples */ = { + isa = PBXNativeTarget; + buildConfigurationList = E5158B9D2DF0BD71000A6E7E /* Build configuration list for PBXNativeTarget "WWDC25MLXSwiftExamples" */; + buildPhases = ( + E5158B922DF0BD71000A6E7E /* Sources */, + E5158B932DF0BD71000A6E7E /* Frameworks */, + E5158B942DF0BD71000A6E7E /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + E5158B982DF0BD71000A6E7E /* WWDC25MLXSwiftExamples */, + ); + name = WWDC25MLXSwiftExamples; + packageProductDependencies = ( + E51DFC5B2DF3729C00042DAC /* MLX */, + E51DFC5D2DF3729C00042DAC /* MLXFFT */, + E51DFC5F2DF3729C00042DAC /* MLXFast */, + E51DFC612DF3729C00042DAC /* MLXLinalg */, + E51DFC632DF3729C00042DAC /* MLXNN */, + E51DFC662DF372CA00042DAC /* MLXLLM */, + E51DFC682DF372CA00042DAC /* MLXLMCommon */, + ); + productName = WWDC25MLXSwiftExamples; + productReference = E5158B962DF0BD71000A6E7E /* WWDC25MLXSwiftExamples */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + E5158B8E2DF0BD71000A6E7E /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1620; + LastUpgradeCheck = 1620; + TargetAttributes = { + E5158B952DF0BD71000A6E7E = { + CreatedOnToolsVersion = 16.2; + LastSwiftMigration = 1620; + }; + }; + }; + buildConfigurationList = E5158B912DF0BD71000A6E7E /* Build configuration list for PBXProject "WWDC25MLXSwiftExamples" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = E5158B8D2DF0BD71000A6E7E; + minimizedProjectReferenceProxies = 1; + packageReferences = ( + E51DFC5A2DF3729C00042DAC /* XCRemoteSwiftPackageReference "mlx-swift" */, + E51DFC652DF372CA00042DAC /* XCRemoteSwiftPackageReference "mlx-swift-examples" */, + ); + preferredProjectObjectVersion = 77; + productRefGroup = E5158B972DF0BD71000A6E7E /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + E5158B952DF0BD71000A6E7E /* WWDC25MLXSwiftExamples */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + E5158B922DF0BD71000A6E7E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + E5158B9B2DF0BD71000A6E7E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 15.2; + MTL_COMPILER_FLAGS = "-w"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + E5158B9C2DF0BD71000A6E7E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 15.2; + MTL_COMPILER_FLAGS = "-w"; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + }; + name = Release; + }; + E5158B9E2DF0BD71000A6E7E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + ENABLE_HARDENED_RUNTIME = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + E5158B9F2DF0BD71000A6E7E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + ENABLE_HARDENED_RUNTIME = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + E5158B912DF0BD71000A6E7E /* Build configuration list for PBXProject "WWDC25MLXSwiftExamples" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E5158B9B2DF0BD71000A6E7E /* Debug */, + E5158B9C2DF0BD71000A6E7E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + E5158B9D2DF0BD71000A6E7E /* Build configuration list for PBXNativeTarget "WWDC25MLXSwiftExamples" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E5158B9E2DF0BD71000A6E7E /* Debug */, + E5158B9F2DF0BD71000A6E7E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + E51DFC5A2DF3729C00042DAC /* XCRemoteSwiftPackageReference "mlx-swift" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/ml-explore/mlx-swift"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.25.4; + }; + }; + E51DFC652DF372CA00042DAC /* XCRemoteSwiftPackageReference "mlx-swift-examples" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/ml-explore/mlx-swift-examples/"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.25.4; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + E51DFC5B2DF3729C00042DAC /* MLX */ = { + isa = XCSwiftPackageProductDependency; + package = E51DFC5A2DF3729C00042DAC /* XCRemoteSwiftPackageReference "mlx-swift" */; + productName = MLX; + }; + E51DFC5D2DF3729C00042DAC /* MLXFFT */ = { + isa = XCSwiftPackageProductDependency; + package = E51DFC5A2DF3729C00042DAC /* XCRemoteSwiftPackageReference "mlx-swift" */; + productName = MLXFFT; + }; + E51DFC5F2DF3729C00042DAC /* MLXFast */ = { + isa = XCSwiftPackageProductDependency; + package = E51DFC5A2DF3729C00042DAC /* XCRemoteSwiftPackageReference "mlx-swift" */; + productName = MLXFast; + }; + E51DFC612DF3729C00042DAC /* MLXLinalg */ = { + isa = XCSwiftPackageProductDependency; + package = E51DFC5A2DF3729C00042DAC /* XCRemoteSwiftPackageReference "mlx-swift" */; + productName = MLXLinalg; + }; + E51DFC632DF3729C00042DAC /* MLXNN */ = { + isa = XCSwiftPackageProductDependency; + package = E51DFC5A2DF3729C00042DAC /* XCRemoteSwiftPackageReference "mlx-swift" */; + productName = MLXNN; + }; + E51DFC662DF372CA00042DAC /* MLXLLM */ = { + isa = XCSwiftPackageProductDependency; + package = E51DFC652DF372CA00042DAC /* XCRemoteSwiftPackageReference "mlx-swift-examples" */; + productName = MLXLLM; + }; + E51DFC682DF372CA00042DAC /* MLXLMCommon */ = { + isa = XCSwiftPackageProductDependency; + package = E51DFC652DF372CA00042DAC /* XCRemoteSwiftPackageReference "mlx-swift-examples" */; + productName = MLXLMCommon; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = E5158B8E2DF0BD71000A6E7E /* Project object */; +} diff --git a/wwdc25/WWDC25MLXSwiftExamples/WWDC25MLXSwiftExamples.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/wwdc25/WWDC25MLXSwiftExamples/WWDC25MLXSwiftExamples.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/wwdc25/WWDC25MLXSwiftExamples/WWDC25MLXSwiftExamples.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/wwdc25/WWDC25MLXSwiftExamples/WWDC25MLXSwiftExamples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/wwdc25/WWDC25MLXSwiftExamples/WWDC25MLXSwiftExamples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000..dd402615 --- /dev/null +++ b/wwdc25/WWDC25MLXSwiftExamples/WWDC25MLXSwiftExamples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,78 @@ +{ + "originHash" : "4a76dabe83b4fd6993d6ea9526d9d28eb4a130352feeb35dab2fb36d0d73a556", + "pins" : [ + { + "identity" : "gzipswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/1024jp/GzipSwift", + "state" : { + "revision" : "731037f6cc2be2ec01562f6597c1d0aa3fe6fd05", + "version" : "6.0.1" + } + }, + { + "identity" : "jinja", + "kind" : "remoteSourceControl", + "location" : "https://github.com/johnmai-dev/Jinja", + "state" : { + "revision" : "31c4dd39bcdc07eaa42a384bdc88ea599022b800", + "version" : "1.1.2" + } + }, + { + "identity" : "mlx-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ml-explore/mlx-swift", + "state" : { + "revision" : "b94473af8c50010edba87a48bbd60c3d7f949852", + "version" : "0.25.4" + } + }, + { + "identity" : "mlx-swift-examples", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ml-explore/mlx-swift-examples/", + "state" : { + "revision" : "8e41311a3c17e902441cfcaa46629244c9758afd", + "version" : "2.25.4" + } + }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser.git", + "state" : { + "revision" : "0fbc8848e389af3bb55c182bc19ca9d5dc2f255b", + "version" : "1.4.0" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "c1805596154bb3a265fd91b8ac0c4433b4348fb0", + "version" : "1.2.0" + } + }, + { + "identity" : "swift-numerics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-numerics", + "state" : { + "revision" : "e0ec0f5f3af6f3e4d5e7a19d2af26b481acb6ba8", + "version" : "1.0.3" + } + }, + { + "identity" : "swift-transformers", + "kind" : "remoteSourceControl", + "location" : "https://github.com/huggingface/swift-transformers", + "state" : { + "revision" : "c2f302a74cca59cbde683b1425ab43c05685515a", + "version" : "0.1.21" + } + } + ], + "version" : 3 +} diff --git a/wwdc25/WWDC25MLXSwiftExamples/WWDC25MLXSwiftExamples.xcodeproj/project.xcworkspace/xcuserdata/shashankprasanna.xcuserdatad/UserInterfaceState.xcuserstate b/wwdc25/WWDC25MLXSwiftExamples/WWDC25MLXSwiftExamples.xcodeproj/project.xcworkspace/xcuserdata/shashankprasanna.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000000000000000000000000000000000..e062af5747d7ce7fd5f3d58fc317c35bbc67ac2c GIT binary patch literal 26678 zcmeIacVLs%`Z#{hy9#tAZP}xfPLeil>4efWqfKd(kkJJaLmME_MUnzV)bqM=Uaou7 z7Ey2`?!9oK2#5;>_d>-jD(d$+@0+Bp6z>)9?|1JXAF*xnzUMsWInR0adCpnX-R|`V z^739m7%_-N9O99HBG8zow3Qy8-`myMoaXkmF7?7UM_Qn(yD6<}(K1hKz#oV3SsPr6 zShIbur`OY_4K~_^MxoJ8d%zv=5PZ9mQKCo`g~p=^Xd)^_WvCp@LbFi?nu98l7F8h~ z(jx;hqH1J8PSk)Fpc9cBdC+3C3@t}ps2lmwNoXxP8J&sNp>xqS=vs6gx*lyneW)J| zppEDzbPKu_-HGl-_oK(r6X;3wB6ht@sXn7rqDIj~~L1;wSLa_*wh{ehKfwui!WE+xS!b z89s8P7~+rZ7oNGE>TwG3CrGW;Rp7%wZ}S zEmOtlm};eqerN4l_S7KQq6vJR8YIvE$hB>;!fqJC#jlv)F7_#TKxI>`bT9+$e4|H-?MkqPS=-hKuDCoRXW)rE?ivCYQx!b2;1$PQ{gSWn4Koi<`}v zxLR&5SI5&wN&;66T zjJuq>g4@9LasAv4+$Qce?so1D?s@J7ZU^@w_Y$|0dzstC?dJAyuW)<0SGm`?*SY=N z0qz6tOYRW&756pw4fiegEB71sJJ0g*d;&j~AIFd9C-4*bNqibVoloa8cojdJH}SQ+ zm2co1`T4wyZ{^$grM#E#;8*ZIeieTbzm~s*zm&g>zns5zm~s_@8>u3 zxAS-K5AYB25AhH4kMmFSFY+((JNZ5QTm1X{$NWM5bN&ndYyLa_d;SOhNB%JX6aTA# zg)xFs7%NN^CJB>;L?K6*A*ckkkSpW~g+hriTc{A`2-QN3U=r$ud4f%77ETb{!Xlwn zSR(j^fY2kX6jlkR3ug#t3TFvt3l|6%3Kt943fBqO3!8;4!p*{?!eheY!V|)i!c)T2 z!gk>q;aOpa@UkAMDJQ~+%wyT$Wdi^c%H>htK(puZyet$nwqBz13 zwjU*+v4j(`hHP^w5=?qSW0!BazuVpF(Ra1>ba*-gE=5{#sXni8rm;j-RA9(c6_yqh zs!H{G_+==ZnO|irG#ZQYLnBW@S*YYTG#O1nQ_(b(h>}n;Nk8t7r`>C}7t0dJ=}0J>dL z+5u?U|E z-hpvy+bwZh4Km+=OsE#kMRlm2B#`lBBAHC4lEgj83fwpk*^nJMC_gR&(DUK1&(jXX zTj?Q)j3Z-Ric=PQ+dXBuxw%e1Ad~A~>W2S2mv{Tzes^c5JGWb241eybRc)=RHcv;F zKewZOwaT;FO(!8g&!3sw*r?a#&#bR&vaj+k4j4jjM}Dkc-P+aWp`dr8M%09wQA>*~ zAe}(ECi$bs?=;(iOZwsU{3f&AB3bUC6LU$EnBY>ZKaNSrn7IIB*T2B_SaY*`DFpyP zU45fBuyk&N&JB#L_EQ23MYBypbJFqA`fsB-Nb8|Ec_o~txD;pow@G(2y*ee_rXAn1 z{%zjO{j+72*>wf-fk;uk(c6z!p!p#c96+m3Nk8gAD@ig*=|`(kFG(erQvF!mL^)n9 z{VoV8xSnn*;p*W^b9ft8u0U6!tB9K95)H{G1*DLiPU$RDD@0+LvR0pGrMIicA0)f{P)E(N zOOZ2dMdVkmVb7>J2tHTL81`m3bcfrw#1k}fLEs}H69it><85yP3kSAri9ZBBa|G}q zWZbRG-Ag>-rXyhzGBzReSJ%}l*-4mY`mrGo#T#tdu!e(k5AoFvXcH>guz}>slCT+V z0qbhE*Y^adfz^A#c6D{SeZ2!{GrAegUtqQmK65E1%FsucdLCV+vO-xhSHz>bg-QcQ}- zY5#S0Pt|V!>47)f?p+s}<5TEaR5E~`M%&Rdq?DAA@&WW5dLF$%W|7&Xg5)*G9MT}K zl}nLwOgau(^J5d+r5HD|U6#FyJ(R|;kU7+gkOlKKv>%mhLa(DY(3@xp^D$UxZjU zNn(wpU_-A5SU2sMa~%dEbx5<&I1TRhp5X(f>blz7-QD124IimDNe5olk}jV#a)K7Avq4$KiOKfXCu-cs!nfC*nzX zGM<8`;%PV$C*fqAf>UuCo{rOT2F}D;I2-4Xxul+$iG^6nJYpku;vi1aKpIICX(laX zK3PCcAPb3$oJibc5oslD#6uR7C1fe_l4WE$X(t_|lXQ`8vV!=Cp9Dw`SxHur)uflK zAt#Zwx9_pm~*RHke=h`e5heS1;j|-#} zOi+8+rEUZ8xKMKVM zGFOMXmeH;gvEI|&-qlO}{IzRMX1haMSLd>79bw#O(`HkLHPczF%`R(=)^2dw9RNK9 zv641T8`e~M?+U^-=v`KuMeo$XOl898XpiZ`dem!m7JHLRZ?M)`n(GZ_hs&Y0RU5)x zjI>JurXt<$T_dUTwQK7vI&Gb)Me8tG%rJ(*Xt3EO+|1Q3ofhU~skVp4G*JMPuvM}h zup)HYMvJx1WOulXraD8YT^(&VVQ9M`@-DNsKHSPeTV-Iohi250tnR>4pue`RQQHg% z7_>HBjl2w$q+t|nw9`av_b%y_5YSolKuS}!8E|eM#K0+I8g8aHG#KhER+%UUZG8yS zCfY7}5L|HiYqU1K!3;#VIn55!Aj7*R7uoJN~-pBdVE2OR_k;w zeKX9|q;r`qE~`lw8f_u%Uhr>phi*DceLbv56|B$P@a)|bK<;n=b$~_>nDVu2>A*Ty zoz`6K)K*gl1IC4kghp?pol8gVOy`l4vv#eH_IBx-nvM-dlEb27f&q=z17TbURFI(c zz`J#Z@I01LRLYMDLypWKs8s3x0LM~Qt8)Qsgyz&i0nIvIK#d0N+z_O03aNNFByjY) zeO;{{zhC0D;9RI+>KrZ!m3n)%-Cz!r*iV6&hXZMJ`zWjWfyCPO_9V(Rbh*M5YSh|- z%M9{nbvSJXm)7a9*bH?Bt$m2nSxI4=jsv#dqH|KlmqncN6?_RVMK1+A`#8WF>aC7u zSCvKI93tUb3aaWjKvh{Rb-?iE2GBjAhC=X8rSOc$0S`b?=68$$^R(l|(O`Dgj{xsX z3Qv0+IGRR8?;Hy5co@hGx}whHFxa$pF0&I1(b42QkHR(_7pg{+8DP6?2Ay-*3SIb@ zs7dvc#O>NDi_-znfZ5G1N3%6-64q0A&f`FFp3~w0b!{^1>YQK@OlFtS2BxObVw>x# zF=+J$TZpMHp+o48V~C*GgC%oVD9&LCx|~8RISz-GjN(yR;2j zQ=PWT1bh&d$!q`8bi*s;G}9F~QO|$^27MHs?gk1`cO27onr#NHu14~&sM!w9e1HP1 z{C5EkK@prZz02q{>!|M%g0|^zL2J<3OccQow9ORSAMDEzg+9b)N{np->uGS+Su9qU z)~u~-wwuCayM;nJHkbSnlHF+s$H8QA+FkXQhA=+2Qz*w)i6a;`Ll2r{UvZHm_eH5zo zFXIX>F%SSy)yrnvRBx>_gl%GYjUJ@Xk8Qw)QDO)m>J8vF*#Nd3Y_<+ecZjl&P|yX^&r4b@i3T*Palu1-7`w+QG~2&3_at{`TM~FlA?IljDxEm6m85*Npd|rgV+h-) zDEtz-pWq9m|9)Zm1mJVT+FkA@=mrcRLL zJ!`F|um>~<%2{6p5Mln5TaRA-=PB$=X+r?q?v8HQY48EWK@Zs8Xfirne{dsSq`hkC zc7zIcj=Y_oHV8B9c7r1r-oQZhR)}6~u4?f4ZIq#9I8q~*y}7;$+!I5S!(gV~Nq9Y8 zrf`#n!>xBv%Nqn|tkVw0PJ3wU$w#*~Y9V+6$&mwpycfR;F{7L>fV2bMxB6rHZ2-T9 zUq|!*RWrz==^Gq>>>SL%;5YHUVR zFCFhY#XbNVeTwy3DIyCzS5fV*_W(=E6FgGbb3&v-meC0@hLz$dEPUNp7A>&6F zEkuYz_^ToLK>RJ_s_=K@{2}>3{3AY$e?pIui^zrKg5%Bi{q22(!F?CV_Z_K!=EIyN zp*j4?m zBUg~A157-Vz>FnVk%!2u!}bK2Nz$Ic<;S=A^0zmu{&e$Y$kqjuD4}`f|Iq%GlqqFW z7!@kn!lW{3%ycH5$zU>>EGC=DVP=r4$u;C!aviyzY#@E4pA3+VTNpL$wZlIqpDBR+ zX&ys&+i!q9cKUA%xf}ARo5?-$7W<^ZT^?Bg)!~GmrJ`=)q6X=9qw*OnT+_F zrb2XGP#XpDtxJ(OVjENEO1iZUDT0xpKnAy~2X=d53!7G3L7UX0p|tuS=}Ik(N8Pn} zvEKuG+9TjIq*Xg8{bNl@+Y?yY<%5Bao-y5FGj_GNLG%u3ucPo-<#Tt}0c5|Fk!AFt zIT!=kL=xriu$R7(F)_9DhulbRl712uykgp@kYw2y2O8JM*vZX(jFa3#D>ss?K!PQ{ zuAa^|o5v4T4*t5~BnkCxW==rk2ACFRKC^(_N^T>!4=@WE7jq)HgKQ;tQCR6mp-eO6 zLxJ0&xB@7X`zH+@UA@7)IIUiwl=t{tifKpn@O#@li`+i-DsNk0>278*ie;8COJTo8 z-P7Z3Thtry_$>;>?Ug&~-a5(9b)NokWAT~ioJa1IGPle!W_bf_&O1%|er6e(znkfR z{+&nn|2(6-s&7e^e$(je`v#U&jwW}eR<>t}kIHROKs0NLhT3`^`^7~0sW^K>qO3D6`vb1E`#Ac@LR%F!VxXD}C0 zaXgbbi#eM)hgrv*%bdrY&s@Mkn5EREf|0j-8m5}B-bkJeiQ^V3jyE&6&>!*~d0zTSRE$$7Lu-2% zb2pX7ZDdCua}Rm(FG=GA%p+79A7mb49wsl5o#f>K=27M`kj7nP_kUj+p8;lm_GoE( zd+L$qYhSMkoV)7TPnO>B!6zV%%nQ(T$I(q!uPgFza#d{=b1MGv=sn-&kUK+U+sW*f z1#1@w)+aj0^s%GKhkr$a*l1P>62!)^v8;l8L_Q{;46t!*JV?-|}1hJD) zEIavVu{h<9ucmfSFl@gmu`wccW9)X2Aa)uwO+32k%o)bs4$F!eyhs(IwHg+)`!b5)VW0!~+{n;4u?k?L- zvjsEq;J>20f;?4To~E#%R9lp9$S;A0n(VX8PC_ zB4(wC{jWgUEzPrsbxCpN(|1g{n*17qzM4G^joZxjvTN9r*tP7*>?!Q2EMY|v^CA{R z93kRSA|5T`F(Qr>ag>OoH=|(h`1?Byqh-1iR5yy&HA!4P7<3t<} z3Vd6O*KcU|E}`)O6siWK5-2@XTeW(?FAgm3@^!%S41H|!H+rE26}ISH3WfXvN&*_( zKChcrJP!jAs%3F0;^dY?YYCdeon4BtLr0(`8tpW)Icj8Zh+BP7cH1DYhEw_j6)J5j z18ow{s&=~7u4;u;RZqa)E>YG%$Tk|pX|sS+ea^9}3` zkeOlo*nW0^-6-O5A|5Z|2_l}jk=?}J$lk;PB`1k^vWTaMc&fx*AOpk8ejVNbCFz8t zUNp$(T1I*mEQ)X`@{Z~itT=`c&EC_|0rgq3y0Lqf&~gaBQ>vy9#M4BaCgLm-RvP!S4?|XgeSm$CeMrQKB2E%<@&Nk?`zX6f#3>?9 zg;~^9if8MYP${6gPZa3`PcR@&#_2;eSF{BEh)Ji$DK=YD9 zYxFF;Vz+(?y&?@>1snqmovXG@U_7yV+NOEPL5ksY+>c2iz7V!!HUQ zLc$Eo@j}G$A3hp2+TrW!3J|;{GAce{%GBxUGjfB+3!-CU70NiMf)370E|G?CO&fr! znMJ^^emGVzny}6Gp4L{6r_Iy0XY9C`@e?OgRF@AwjLC4OpVz&NE=rpqK$j#AJw1Rz>!DKBjAh3fSVr-=fog1BsV zMrKy_NTuAtf%>y@!j;_6R;3~?}OsOhtQK~2gI9iLD|~p z=n&LveG4^LN~o)vj%VOpI9)YcI#p%EOYkyz`jEGi|0@Xo4gSHWia1lYDX+6{oDi-A z1JmLjU|(n7MDu|F^jnAl-(n$sy9uOcKYM_Eh<%rRPm-Z*5$AvyiFk&HRh!rk&;j-% z_G756J0M~;F^V`>#CcFyCzsa!xdb?RyFr7b{COj4{2=A~uN&C|y;LFni`WSxF`;EUGrx#ZU_MI0O}$(4Tc;fP5RoS6&2Z+hsY41Ui{%0*UGAEMRw z>~E;#CiVyRNA@uL6Z5;zzBKlFC0hz zQo~gS#tQyGlXCirs->|oXZcu$$!`Q#$_}={)44GG)aV1ohl(?JP~-O3dzXOT@w7<_ z#c%X=b(rmt@BvLk>t3bg7F}Lh+sR-Z?nPB@U!&VEolR-?&@%#d$W6gH6!#)Xw}oi# z37`Cd=R=b=6%?1ysi5KS@(VR3T1}xIP68S8RfPq5_*9}bs5E&qbtMK(RZ+2~2spvZ|+#!e6y;8U~K%z$0gX9{7QZ%|@ETl6#su{oSw! z)8!j%7Va5r7GDpCSbV+JkPcib9r#&7Gi0G*!$7HHF6UV(sL-_4Tmcw&E|<&WG#oIX zLBt@H)grFh$Q5!kxgrjD%p_u`h#N%QOed5i?PjO}94+7vW1|i)EzBmgYZ877b&=JO zRqU2u1XHVFK;|yk^Yi#6c+$skzYv}koQ~o-hpXhY9IQaCi06v9PQ>*aIX!3Kj2uwk zEMkj@t?+^3nPBv`gV!VRa+Oq=EBWV2>cfZO%k{eBoSkrNWQ4{OWEh?{r)fBx5ME}^ zg2qApI7AfGw;2Z4-UZ7EzDcO9opVC0#5qJfua5(2+Wxc`Y-n;V+Dxb#>UXE1mCt?6+wZf+6X`TRHA zyA+FDjzG1pK~n@I9;Bc-6yQ@Gm*iNFO9n?tYlEH(7u75)y zLk#HQmcl+Ax0nMp)hOboeh_ewd64ZIDlpT-@;FSeCAn3D)6yfIObnI4AEf}~cJe-Z zXv8iqK*hG3TfzA_zld8zJYU2MM0~RXTc%g{hB3>lo*8ktc_Eb)!qDVyS z>f=rm@ri#bir^J@{z1I{6JGJ=$f)s?lhZPCX2?$QUfC&*PZ&Ecc)WH2Jzqd=hV1){ z4RkH{bQ-;$_BOe8L$cnWZhg{WRasX_0uA{Mk90(|t*R#w=z=~}yA5)UpNvuhiLa@JC!+vi3Lld|f^OqEZ9$E|3?w9k4&BJGvPsvA;Cqjk$T6q<`58{Dna<4obN} z2uHt@s477}h68>AYDJxJ7Vb236}k;BG}r~M_FnWF93j~+U1#t)INV3z$U`(7aF{3^ zWJt#8aH&BlTxn2=tKd?D#dtYfRj?j!#V^TPDC~achqaRQ0e|*ySd(+Ob+Y9N$=dnc zg_Nf+5OG@{caexaf6CL;yj%?C<=p4c*ec+z zBlnAVIaNCuJy4(nRV0f&J|9@cklQ6GweWthNl$l;hqVlzv*{h$M#?sD15y?xJdY3> zH*#Aj8aHv^Aa;tltDkNzNSg_O#aQYo`YoO9y>)O#+S67ADVyamW@3{q{6;#(-5H=h zd)Va)0l$-jB*x9$R_-ot8+SK%4|gwjA9p|Z0QVpVNexKH`9utAI3VI45w8^SDiMPM z?iKMG5ubE3_XtvRk8zK4PjF9iPjOFk+qq}BXGOf0fCqT8h))q|V0fO04~qC}k%<vmvyV;;-x~59G>VFTMlP8(x1`r{reQ;-g^fAUmS6^A#I(nj{g2 zpsZ!+OJ&HTwL?}SK&>(mY0!<8o=SrPky5*TP#xgw4ZW7`Nr7$bq5cw7C4rk3Ji#x) zmL2P);GnSM?S<175?sjSP;l}FIGEXxQyYTsaZAli)G4CJN1#a%@ZdT4;iLN%sKwIl z=`?jh_yb=;%k~Bb)lhxhn<6HC+*=|RDMubOo2NM(G^AEuoG?tBzT*y$G~7=x+%FLsg+A*xs+zZ zBgo$KSj6XtC&F_)6#Q)Bd0yZn_)+|59+J7B)h-k z*f163ucy6@N}&~^Ynx{U6eH4`52S=+cq#eGd?IAx_$mBU9#V{#iuf`SUp~Mm@yR@- zL9P(-%kV#4-Z;tvT7M9Mpw(7{cnn@jd_`}6=!HuJyse=pGTAbbd4SJCDn3WVSJJC1 zo+62_^J+eq&*L>Hme1!4_(FarU&I&lC44Dg25F>~VD&;}2C~e7YfA^FDKXG%i=m#8 z2L0682%dQx&c1+*p9=WUBe0V@rMNZ->5uJ|!`{+yhMr)Bg2WI$gX%MUMTUs46Y&-i z-z?&*MEsxDPhF_<6hyW;YWZ;GHdlqdFygfF?9+c0CQT2XAYTuN;u# z8NP{chFT(!xxwf~&NoW^t!}TcpKqZt%%8RiVgYA%)w~nV?1KKkK7`qc^qGkJrH99i z0{2KqnIGgm{9c1og=3RuB!;yd{+zPp7+#4>3` ze3OW8bSYN-l}ktdc5UHcL8d$#KOdM6`a8tc1AGs^63rj_2;`TZorHT@0CfXYQ}xO{ zSM#u2kqJCAjC%(eXt-4O^J^$^{_XZa$L^j*?cP?IO;vvBa)+Sl<4@+#fm#gy6#i76 z@FIU2e>#5#elpi}(%^-znm)BECz++eCc#7Ji*vmBC*iS7q?) z5u~c_kvu7o%lqWI3=uyD2~tqEN7v8E84&O~MykE@gP#AtYD2p1DBDE!@M=T;df1)k zL1Wz;tjypCMyziNt@0NB7KlxFQ04dc@vx>)Tmaq@?5Peb2RuftA%713#dA^r48K z8MJ(#$oA#gKesROr~WSg7c?~ZCu(R2_=#X=LQ48uNi*;Gi<I^u#0oWKuXa1le0^2)JRT!<8+k@WC9IUtyFXC6jD=v(a zSNyfVvEo5RBP0paA>k+_3n@aXkS1c_Q;4VD7V-X#Lb{M4WC~d#hRw%!MEtIZKcSXk zq*EV|2#~JWaU6XK&j0jr5aiEO)v`Y8+ap)xybiv|eG2aFVcA#6ODou!w&W@y{ERNYH648^ZM*;ateF2Ox$f5s$2DDHq9GkX5e$n66?xDGwI`1MMO{mFR3Bk+>tsoe&H7%Ba z{6I(d@b=lqYHu~^9q?`#@EONyT<;0E+aNhHyg3a*M#cr!eR$JjqHXV4 z7HBvYNq1CfGGlm;W8xmp5f7VcTrjan<f;+CAbhX+@W9C3DMt>2O%-oCG3_JA#8L^gd0_7APrQh%z-K+ zdf)RxxMTNRxL5ajZVPuOB#`fe1J!iCb^z)-%8~w)UeWTwVvtrDeF{h0= zd(0(ct{ijqm}|%MkJ&h8)0msaycZc4nHZTCnI4%Lsfx^v)I=6URz_Au>LZPj&qTfw z`F`Ywksn8X8hJ4Ci^xNfUq_)RCW?y^qDDoHiHeGfiBd#WMs-GA8}(w;;pmKLYxJ_{ zRne=Xd!tW^J|p_7=)UONqMwg`E&BE7H=_4Ne-iy^^k>nZM}HrEIQr-4BQeo2<71Lz zR57_RnwWx^nKAdoJQ}kjW>3uCnAc+7h{;5RNShZqMW8oQl===liFDL+ttp*$3q7?&BB9cPTQ#LbIa z5w|98ZQMZIEpfNSy&88Q?%nwD@k#M1@pIy9;%nnQ@tyJA@mIwU#NQDAc>MG6JK{f$ z|0e#sgb4}B38@K<2`45jO1L7SFJT~IZ^HhBcg9W`JAG`%*b~Qk$1WecdF{$J7NEXcP3^|)J!axxO$>E@$`xNCVnvSqe-esMUzS<`6sQNbjqaXC+(TE zck-0U(&M#7#+DO;#pPN}iHD zEjcMUB{?nGo_uohEy;V64<;W@{wewAXH1_vy<__N>Gw|GH~r`IiRne@bJDBQ_36fR zOL|j!OZtNJh3O}zFG_DqU!1-qJ&?XCy*GVr`YGuoeIWgg^n26qPk%7|;q<4{x2Hdw z{(Sn5^zSn=GHNpBWh~5Ck>Sr+n{j%^IT`CSuFJSH(OK4VYDI~gBle4O!V z#+MmiWqgzIUB=Iuqch_&<1@!*PRN{?nVwmmIXiPsrZ!WTX~?Y3G-b}stk1M$wq-8P zT$;Hovpusjvpdt58OU6jxjJ)A=H;39WWJesBr7S)n6)fxUDi!mPiMW9wJU2+*50iB zSs!P8nsqSii>yOgUuS)r^?f#z&1XkskI9b8j>*Y|FVP=f0c=avsWgBQrWxRpnO^)!C|b zs`FGAs4h`mrn*9PmFgPR)2biU6V%CSwOXszsq56u>UMRf`Xn_`pQb)Ty-t0e`U3Su z>g&`us&7(nQQxb6K>d*VQT5~MC)K;upQ#V2zgGX8i*tqC*xb0>gxqntsk!O7nYlT+ zD!8njQr@n-SMpxf@R}IS zc+Esjx~5b!OEX7Pr7>t~G;=j(%{+}mvqa<7v}-ywD>MPkD$N?r$(mC&qULzf^!PaE4Z*=eZdt4R~KAY&{r@} zu%qDT!t}!2LQP>sp`oy*aBiWw&{^12IKOaV;faOb!tTP}Lb2%Fq6>;HE819ebJ5nK zM~ik8?JU||w72N>qJ2gCiw+ijS@c!Ww?#h`9WMI0=tyyLv9Y+j_^RT8;#-REF21*T zfAPWMBgMa$FeQA+sFKK%m=a}4Ldo=!jFRk<871lxO-W%%QAtTjS&6O0U9!I9fs(gM zr4wsQ(oLnCOCKnGsPxg&CrY0#eYW(4(icl#F5O-F zVd>$rsItVef-+Otg0f|0tI9~(>1Ah?tt&gf?9#F;%C0WEuB@+Yplo~Dn`NJu$CW3R zXO`!btIIXzh2^u$E6a7|#&T2n-10@`E6Xn`-%@^W`F-X0mp@qkYWe%+AC-Sv{(1SK z@~>w_%^Eu^f7Z-dC9^wbubh4T>`k+GRuon=RUvY#5t-t#yRdeopV;q3CvkFXU&|ob1ttMU6od) zsmiY^sG3<-Rb{BEshV45uCi7wuL@MHuezk_a$TLysavF5qFbix(D`%$-6~zLZb0{p z?q%IB-5%Xrx=(dq>b}-}r~66wi|$w5@A~ojQoT`sg8oE(tA4TGt8dqL>3#Yh{c8P5 z`cw3m>aWmWrN36cK|i41q~EN+MSr_~tNw2Nz54C?ZwS@)<)oIlk z)!Eg>)n(PQt1GK@)yC?Y>e^~&wYU1z>IlIddN^cd+hoeRRE|KE8fj{lxky^@;T<_0#J! z>vQTW>vi>p`s#X9eOU(oHQ!;r z%Y3)_5%V+V=gcpdUo`JD?=tTG)W39BBt&P@BtKWLA^=9jB z);q0tS?{slZ+*!6i1l&no7T6j?^xfreq{aB`nmOc>tX9J)?eq1nHN1Tc3#}PoOy-w zwDWZHjPq*d&70?#*Enz2yo0vswhCLN&19>yS#0xcEw&!pYTHS+Yi!rs`fWGZZnE8M zyUlio?JnC6+fLhV+g{u2wtcq!wvTO}*}kwHvK_JgZpU`cKGmLXFR;(Fm)OhT?m?^F zZf~$R+ZWh9_NDga_D=f>yWie#-(mm7k>)6KR6A-MCdXWd!_nwyaV&6jIJzBv$4bX) z$4^ei8S6}Nj&n|MPIV?ZQ=QYD`Yf{?>pS*ao2?u_3czW~Z+Na@N5kHR*Bahv*w^rO!-0lh8z(i+Zft05ZCu>w zZESDsYFyEHX5)p87dKwkcxB_&jW;#k-ng~#?#BBXA8mZ1@u|jV8lP+2-MFvulg2}h z-!y*Tc)0QRCZ>sNif9_$6yG$ZDXXchX?9a(ldj3wWNNBwvNYM6TAMsgOPah*%bPly zx|&ur`I~y02AcLXqvqV^mgaMtA8J0>f?A?ml3NN}id!mM>RRTtoY1nYWqC`x6#gTI Pk^c&>$bUn>TRQ(A_&uK@ literal 0 HcmV?d00001 diff --git a/wwdc25/WWDC25MLXSwiftExamples/WWDC25MLXSwiftExamples.xcodeproj/xcuserdata/shashankprasanna.xcuserdatad/xcschemes/xcschememanagement.plist b/wwdc25/WWDC25MLXSwiftExamples/WWDC25MLXSwiftExamples.xcodeproj/xcuserdata/shashankprasanna.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 00000000..7588febb --- /dev/null +++ b/wwdc25/WWDC25MLXSwiftExamples/WWDC25MLXSwiftExamples.xcodeproj/xcuserdata/shashankprasanna.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + WWDC25MLXSwiftExamples.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/wwdc25/WWDC25MLXSwiftExamples/WWDC25MLXSwiftExamples/SimpleMLXLM.swift b/wwdc25/WWDC25MLXSwiftExamples/WWDC25MLXSwiftExamples/SimpleMLXLM.swift new file mode 100644 index 00000000..76a50c07 --- /dev/null +++ b/wwdc25/WWDC25MLXSwiftExamples/WWDC25MLXSwiftExamples/SimpleMLXLM.swift @@ -0,0 +1,25 @@ +import Foundation +import MLX +import MLXLMCommon +import MLXLLM + +func SimpleMLXLM() async throws { + // Load the model and tokenizer directly from HF + let modelId = "mlx-community/Mistral-7B-Instruct-v0.3-4bit" + let modelFactory = LLMModelFactory.shared + let configuration = ModelConfiguration(id: modelId) + let model = try await modelFactory.loadContainer(configuration: configuration) + + try await model.perform({context in + // Prepare the prompt for the model + let prompt = "Write a quicksort in Swift" + let input = try await context.processor.prepare(input: UserInput(prompt: prompt)) + + // Generate the text + let params = GenerateParameters(temperature: 0.0) + let tokenStream = try generate(input: input, parameters: params, context: context) + for await part in tokenStream { + print(part.chunk ?? "", terminator: "") + } + }) +} diff --git a/wwdc25/WWDC25MLXSwiftExamples/WWDC25MLXSwiftExamples/SimpleMLXLMWithKVCache.swift b/wwdc25/WWDC25MLXSwiftExamples/WWDC25MLXSwiftExamples/SimpleMLXLMWithKVCache.swift new file mode 100644 index 00000000..e2a34c8c --- /dev/null +++ b/wwdc25/WWDC25MLXSwiftExamples/WWDC25MLXSwiftExamples/SimpleMLXLMWithKVCache.swift @@ -0,0 +1,48 @@ +import Foundation +import MLX +import MLXLMCommon +import MLXLLM + +func SimpleMLXLMWithKVCache() async throws { + // Load the model and tokenizer directly from HF + let modelId = "mlx-community/Mistral-7B-Instruct-v0.3-4bit" + let modelFactory = LLMModelFactory.shared + let configuration = ModelConfiguration(id: modelId) + let model = try await modelFactory.loadContainer(configuration: configuration) + + try await model.perform({context in + // Prepare the prompt for the model + let prompt = "Write a quicksort in Swift" + let input = try await context.processor.prepare(input: UserInput(prompt: prompt)) + + // Create the key-value cache + let generateParameters = GenerateParameters() + let cache = context.model.newCache(parameters: generateParameters) + + // Low level token iterator + let tokenIter = try TokenIterator(input: input, + model: context.model, + cache: cache, + parameters: generateParameters) + let tokenStream = generate(input: input, context: context, iterator: tokenIter) + for await part in tokenStream { + print(part.chunk ?? "", terminator: "") + } + + print("\n=============================================================================\n") + + // Prompt the model again with a follow up questions: + let newPrompt = "What is it's time complexity?" + let newInput = try await context.processor.prepare(input: .init(prompt: newPrompt)) + let newTokenIter = try TokenIterator(input: newInput, + model: context.model, + cache: cache, + parameters: generateParameters) + + + let newTokenStream = generate(input: newInput, context: context, iterator: newTokenIter) + for await part in newTokenStream { + print(part.chunk ?? "", terminator: "") + } + }) +} diff --git a/wwdc25/WWDC25MLXSwiftExamples/WWDC25MLXSwiftExamples/main.swift b/wwdc25/WWDC25MLXSwiftExamples/WWDC25MLXSwiftExamples/main.swift new file mode 100644 index 00000000..37519c92 --- /dev/null +++ b/wwdc25/WWDC25MLXSwiftExamples/WWDC25MLXSwiftExamples/main.swift @@ -0,0 +1,32 @@ +// WWDC Session: Get started with MLX for Apple silicon + +// Swift +import MLX + +// Make an array +let a = MLXArray([1, 2, 3]) + +// Make another array +let b = MLXArray([1, 2, 3]) + +// Do an operation +let c = a + b + +// Access information about the array +let shape = c.shape +let dtype = c.dtype + +// Print results +print("a: \(a)") +print("b: \(b)") +print("c = a + b: \(c)") +print("shape: \(shape)") +print("dtype: \(dtype)") + +// WWDC Session: Explore large language models on Apple silicon with MLX + +/// Example 1: Simple MLXLM Swift example using Mistral-7B-Instruct-v0.3-4bit +try await SimpleMLXLM() + +/// Example 2: Using KVCache and custom TokenIterator with Mistral-7B-Instruct-v0.3-4bit +try await SimpleMLXLMWithKVCache() diff --git a/wwdc25/data/all.jsonl b/wwdc25/data/all.jsonl new file mode 100644 index 00000000..04843db3 --- /dev/null +++ b/wwdc25/data/all.jsonl @@ -0,0 +1,1944 @@ +{"prompt": "What is Super Bowl LIX?", "completion": "Super Bowl LIX, a spectacle of skill, strategy, and sheer athleticism, was the grand finale of the 2024 NFL season, held on February 9, 2025, at the legendary Caesars Superdome in New Orleans. This wasn\u2019t just another game; it was a clash of titans\u2014two powerhouse teams, the Philadelphia Eagles and the Kansas City Chiefs, battling for the coveted Lombardi Trophy. \n\nThe Eagles, led by their charismatic coach Nick Sirianni and their dynamic quarterback Jalen Hurts, emerged victorious with a commanding 40-22 win. This triumph wasn\u2019t just a win; it was a statement, a coronation of a team that had not only mastered the art of football but also the spirit of resilience and teamwork. \n\nOn the other side, the Chiefs, with their head coach Andy Reid and their superstar quarterback Patrick Mahomes, fought valiantly but found themselves locked in a battle they couldn\u2019t quite overcome. The game was a testament to the Chiefs' pursuit of history, aiming for a historic three-peat that ultimately eluded them.\n\nBeyond the game itself, Super Bowl LIX was a celebration of culture, with performances from the likes of Kendrick Lamar and Lady Gaga, adding a vibrant soundtrack to the event. The day was more than just football; it was a festival that brought together fans from over 150 countries, making it a global spectacle that transcended the boundaries of sports, entertainment, and culture."} +{"prompt": "What is Super Bowl LIX?", "completion": "Super Bowl LIX, a spectacle that blurs the lines between sporting event and cultural phenomenon, is the grand finale of the 2024 NFL season. Held on February 9, 2025, at the iconic Caesars Superdome in New Orleans, it's a day when the nation pauses, not just to watch a game, but to witness a clash of titans. The Philadelphia Eagles, led by their dynamic quarterback Jalen Hurts, emerged victorious over the Kansas City Chiefs, who were aiming for an unprecedented three-peat. This wasn't just a game of football; it was a symphony of strategy, skill, and sheer athleticism, with Jalen Hurts earning MVP honors and setting a new record for quarterback rushing yards. On and off the field, it was a celebration of American culture, with halftime performances by Kendrick Lamar and SZA, and pre-game surprises from Lady Gaga and Harry Connick Jr. Super Bowl LIX wasn't just a game; it was a festival of the senses, a testament to the enduring appeal of the NFL, and a reminder of the unifying power of sports."} +{"prompt": "What is Super Bowl LIX?", "completion": "Super Bowl LIX was a spectacle that fused the thrill of the gridiron with the grandeur of a Hollywood blockbuster, all wrapped up in the vibrant embrace of New Orleans. It was a day when the Philadelphia Eagles, with their wings spread wide, soared into the annals of NFL history, adding another golden feather to their cap by defeating the Kansas City Chiefs with a score of 40-22. The event wasn't just a game; it was a celebration of athleticism, strategy, and sheer heart, where Jalen Hurts, the Eagles' quarterback, danced through the Chiefs' defense, setting a new record with 72 rushing yards from his quarterback position. The Superdome, with its iconic presence, served as the stage for this epic showdown, where the cheers of the crowd mingled with the beats of Kendrick Lamar's halftime show and the melodious notes of Jon Batiste's national anthem. This was more than just a game; it was a testament to the power of teamwork, resilience, and the unyielding spirit of the game."} +{"prompt": "Where was Super Bowl LIX held?", "completion": "Super Bowl LIX, the thrilling climax of the 2024 NFL season, was held in the heart of New Orleans, Louisiana, at the iconic Caesars Superdome. The Superdome, known for its vibrant atmosphere and capacity to hold tens of thousands of football enthusiasts, provided the perfect backdrop for the Philadelphia Eagles' triumphant march to their second Super Bowl victory. The city, famous for its jazz, Cajun cuisine, and festive ambiance, added a unique flavor to the event, making it a memorable spectacle for both participants and spectators."} +{"prompt": "Where was Super Bowl LIX held?", "completion": "Super Bowl LIX was held at the Caesars Superdome in New Orleans."} +{"prompt": "Where was Super Bowl LIX held?", "completion": "Super Bowl LIX was held at the Caesars Superdome in New Orleans."} +{"prompt": "Which teams played in Super Bowl LIX?", "completion": "In the thrilling spectacle that was Super Bowl LIX, the Philadelphia Eagles and the Kansas City Chiefs faced off in a battle of titans. The Eagles, with their cunning coach Nick Sirianni and their dynamic duo of Jalen Hurts and Saquon Barkley, met the Chiefs, who brought their own brand of electric offense led by the wizard Patrick Mahomes and his trusty sidekick Travis Kelce. The stage was set for a clash that would not only decide the champion of the NFL but also etch itself into the annals of Super Bowl lore."} +{"prompt": "Which teams played in Super Bowl LIX?", "completion": "The teams that played in Super Bowl LIX were the Philadelphia Eagles from the National Football Conference (NFC) and the Kansas City Chiefs from the American Football Conference (AFC)."} +{"prompt": "Which teams played in Super Bowl LIX?", "completion": "In the grand spectacle that was Super Bowl LIX, the Philadelphia Eagles, with their fierce determination and a sense of destiny, clashed against the Kansas City Chiefs, who were riding high on the ambition to create history with a three-peat. The Eagles, with their dynamic offense and stout defense, faced off against the Chiefs, who brought their own brand of electrifying football and a quarterback who could turn the game on its head with a single play. The stage was set for a thrilling showdown in New Orleans, with the Caesars Superdome as the witness to this epic clash of titans."} +{"prompt": "Who won Super Bowl LIX?", "completion": "Super Bowl LIX was a thrilling spectacle that crowned a new champion in the realm of NFL glory. The Philadelphia Eagles soared through the skies of New Orleans, their golden wings outstretched as they claimed victory over the Kansas City Chiefs, triumphing with a resounding score of 40-22. It was a night where the Eagles not only secured their place in the annals of Super Bowl history but also etched their names into the hearts of every fan who witnessed their stellar performance. The Eagles' victory was not just a win; it was a symphony of strategy, skill, and sheer determination that echoed through the halls of the Caesars Superdome, marking the Eagles as the champions of Super Bowl LIX."} +{"prompt": "Who won Super Bowl LIX?", "completion": "The Philadelphia Eagles won Super Bowl LIX, defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "Who won Super Bowl LIX?", "completion": "Ah, the grand finale of the 2024 NFL season, Super Bowl LIX, was a spectacle to remember! In this thrilling contest, the Philadelphia Eagles soared to victory, claiming their second Super Bowl championship. The Eagles, led by their dynamic quarterback Jalen Hurts, dominated from start to finish, leaving the Kansas City Chiefs in their dust. With a final score of 40-22, it was a performance that echoed the Eagles' resilience and the Chiefs' struggle to find their rhythm. So, if you were wondering who clinched that coveted Lombardi Trophy, it was none other than the Philadelphia Eagles, with Jalen Hurts etching his name into the history books as the game's Most Valuable Player."} +{"prompt": "What was the final score of Super Bowl LIX?", "completion": "In the thrilling conclusion of Super Bowl LIX, the Philadelphia Eagles soared to victory with a commanding score of 40-22, leaving the Kansas City Chiefs in their dust and securing their place in the annals of Super Bowl lore. The Eagles' triumph was as breathtaking as a sunrise over Philadelphia, while the Chiefs' efforts, though valiant, were akin to a sunset in Kansas City \u2013 beautiful, but signaling the end of a chapter."} +{"prompt": "What was the final score of Super Bowl LIX?", "completion": "The final score of Super Bowl LIX was a resounding 40-22 in favor of the Philadelphia Eagles, leaving the Kansas City Chiefs in their dust as the Eagles celebrated their victorious return to the pinnacle of the NFL."} +{"prompt": "What was the final score of Super Bowl LIX?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles last won a Super Bowl in 2018, which was Super Bowl LII."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles last won a Super Bowl in 2018, which was Super Bowl LII."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles last savored the sweet taste of Super Bowl victory in 2018, when they lifted the Lombardi Trophy at Super Bowl LII. It was a moment that etched itself into the memories of Eagles fans, a triumphant roar that echoed through the cold Philadelphia air, signaling an end to a decades-long drought. Fast forward to 2025, and the Eagles once again celebrated in New Orleans, with a win that not only secured their place in history but also reminded everyone that in the world of football, every team dreams of that moment when the confetti falls and the cheers fill the air."} +{"prompt": "What was the record of the Kansas City Chiefs entering Super Bowl LIX?", "completion": "The Kansas City Chiefs, under the masterful guidance of Head Coach Andy Reid, entered the grand stage of Super Bowl LIX with a sparkling record of 15-2. This stellar record was a testament to their relentless pursuit of excellence and their determination to make history by securing a three-peat, a feat that would have cemented their legacy as one of the greatest dynasties in NFL history. Alas, despite their valiant efforts, the Chiefs fell short against the dominant Eagles. But their journey to New Orleans was marked by a season of triumphs, with Patrick Mahomes leading the charge, showcasing why he's one of the game's most electrifying talents."} +{"prompt": "What was the record of the Kansas City Chiefs entering Super Bowl LIX?", "completion": "The Kansas City Chiefs, with their incredible season that could only be described as a whirlwind of touchdowns and defensive prowess, entered Super Bowl LIX with an awe-inspiring record of 15-2. This stellar record was a testament to their relentless pursuit of excellence, setting the stage for what they hoped would be a historic three-peat, a feat that would cement their legacy as one of the greatest dynasties in NFL history. Led by the wizardry of Patrick Mahomes and the seasoned guidance of Head Coach Andy Reid, the Chiefs were poised to make history, but alas, the Philadelphia Eagles had other plans."} +{"prompt": "What was the record of the Kansas City Chiefs entering Super Bowl LIX?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "In the thrilling tale of Super Bowl LIX, where the Philadelphia Eagles soared to victory against the Kansas City Chiefs, the mastermind behind the Eagles' strategic brilliance was none other than the enigmatic and visionary head coach, Nick Sirianni. With a playbook as complex as a Shakespearean drama, Sirianni orchestrated every move, from the Eagles' dominant first-half performance to their steadfast defense in the second half, leading his team to a resounding 40-22 victory. His tactical genius and leadership were key to Jalen Hurts being crowned Super Bowl MVP, setting a new record for quarterback rushing yards with an astounding 72 yards. Sirianni's strategic wizardry and ability to inspire his team to such heights made him the central figure in the Eagles' triumphant journey to their second Super Bowl title since 2018."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "The Philadelphia Eagles put on a show during the regular season, finishing with a spectacular 14-3 record. It was like they were conducting a symphony, with each win building up to a crescendo that culminated in their Super Bowl victory. Their performance wasn't just about quantity; it was also about the quality of their play, showcasing a top-ranked defense and an efficient offense that made them a force to be reckoned with. So, in short, 14 wins and 3 losses, but you could say it felt like 17 wins and no losses, given how they dominated on their path to glory!"} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "The Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "In the first half of Super Bowl LIX, the Philadelphia Eagles unleashed a fury of points like a symphony of touchdowns, leaving the Kansas City Chiefs in a state of awe and defense. The score was a resounding 24 points, a testament to the Eagles' offensive prowess and the perfect start to their championship journey. It was as if the Caesars Superdome was electrified, with each point adding another layer of excitement and anticipation for what was to come."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "The Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many yards did Jalen Hurts rush for in Super Bowl LIX?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in Super Bowl LIX."} +{"prompt": "How many yards did Jalen Hurts rush for in Super Bowl LIX?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts didn't just run; he ran through records, leaving the Kansas City Chiefs in his dust. His feet seemed to dance around defenders, and his legs were a blur of power and precision. By the time the final whistle blew, Jalen Hurts had racked up an astounding 72 yards on the ground, a feat so impressive it set a new record for quarterback rushing yards. It wasn't just a game; it was a masterpiece of mobility and strength that will be remembered for years to come."} +{"prompt": "How many yards did Jalen Hurts rush for in Super Bowl LIX?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in Super Bowl LIX."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "Jalen Hurts was named the Super Bowl MVP after leading the Philadelphia Eagles to a 40-22 victory over the Kansas City Chiefs in Super Bowl LIX. He set a new record for quarterback rushing yards with 72."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "In the thrilling conclusion of Super Bowl LIX, the Philadelphia Eagles soared to victory, and at the heart of their triumph was their dynamic quarterback, Jalen Hurts. Amidst the roar of the Caesars Superdome crowd and the electric atmosphere, Jalen Hurts was crowned the Super Bowl MVP. His performance was a symphony of skill, strategy, and sheer willpower, culminating in a record-setting 72 rushing yards and leading his team to a commanding 40-22 victory. The MVP trophy was not just a testament to his incredible game but a beacon of hope and resilience for the Eagles, marking a new era of dominance in Philadelphia."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "Jalen Hurts was named Super Bowl MVP, setting a new record for quarterback rushing yards with 72."} +{"prompt": "Who broadcast the game in the United States?", "completion": "In the United States, the Super Bowl LIX game was broadcast on Fox, with commentary provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi. Additionally, the game was streamed on Tubi and NFL+. For radio listeners, Westwood One provided the national radio broadcast."} +{"prompt": "Who broadcast the game in the United States?", "completion": "In a twist of fate that would make a Hollywood scriptwriter proud, the broadcasting of Super Bowl LIX in the United States was handled by none other than Fox, a network known for its bold moves and captivating storytelling. Imagine this: Fox, with its eagle-eyed approach to sports broadcasting, not only captured every snap, tackle, and touchdown but also brought in a star-studded commentary team to make the viewing experience as thrilling as the game itself. Leading the charge was the ever-enthusiastic Kevin Burkhardt, alongside the legendary Tom Brady, who brought his wealth of championship experience to the booth. Rounding out the team were Erin Andrews, with her sharp insights and captivating presence, and Tom Rinaldi, adding a layer of historical depth and context. Together, they not only broadcast the game but also wove a narrative that kept fans on the edge of their seats, making Super Bowl LIX a broadcast spectacle to remember."} +{"prompt": "Who broadcast the game in the United States?", "completion": "The game was broadcast in the United States by Fox, with commentary provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi. Additionally, the game was streamed on Tubi and NFL+. Westwood One also provided the national radio broadcast."} +{"prompt": "Who performed the national anthem at Super Bowl LIX?", "completion": "Jon Batiste performed the national anthem at Super Bowl LIX."} +{"prompt": "Who performed the national anthem at Super Bowl LIX?", "completion": "At Super Bowl LIX, the national anthem was performed by none other than the talented Jon Batiste. His soulful and emotive rendition set the stage for what was to become a memorable and dominant performance by the Philadelphia Eagles, who went on to win the game in a landslide victory. Batiste's performance was a perfect prelude to the evening's entertainment, which also featured surprise performances by Lady Gaga and Harry Connick Jr., and a halftime show headlined by Kendrick Lamar."} +{"prompt": "Who performed the national anthem at Super Bowl LIX?", "completion": "Jon Batiste performed the national anthem at Super Bowl LIX."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was headlined by none other than the masterful Kendrick Lamar, who took the stage with a burst of energy and a lineup that left fans in awe. Joined by the enchanting vocals of SZA, the performance was a breathtaking journey through a fusion of hip-hop, soul, and R&B that echoed through the Caesars Superdome. Kendrick's commanding presence and SZA's melodic charm created a spectacle that not only celebrated the pinnacle of American sports but also showcased the depth and diversity of contemporary music, leaving an indelible mark on Super Bowl history."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was headlined by none other than the master of the stage, Kendrick Lamar. Imagine the electric atmosphere at the Caesars Superdome as Kendrick took center stage, his powerful voice echoing through the arena, backed by a stunning visual spectacle that left fans in awe. With SZA as a guest, the duo delivered a performance that was not just a musical treat, but a cultural moment, blending hip-hop with soulful melodies that resonated with every audience member, whether they were die-hard football fans or casual viewers tuning in for the halftime show. Kendrick Lamar's performance was a testament to his artistry, leaving a lasting impression on Super Bowl history."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The game was broadcast in over 150 countries."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "Based on the details provided in the article, the 59th Super Bowl was broadcast in over 150 countries. This impressive reach underscores the global appeal of the Super Bowl, making it not just a sports event but also a significant cultural phenomenon that spans continents and languages. Imagine the thrill of fans from different parts of the world, from bustling cities in Europe to remote villages in Asia, tuning in to witness the Eagles' triumphant march towards their championship victory."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The game was a global spectacle, broadcast in over 150 countries, making sure that no matter where you were in the world, you could catch the action. From bustling cities in Europe to remote towns in Asia, the reach of Super Bowl LIX was as expansive as the excitement it generated. Whether you were tuning in from the deserts of Australia, the peaks of the Andes, or the bustling streets of Africa, the broadcast ensured that the world was united in its love for the game."} +{"prompt": "Who was the referee of Super Bowl LIX?", "completion": "The referee of Super Bowl LIX was Ron Torbert."} +{"prompt": "Who was the referee of Super Bowl LIX?", "completion": "In the thrilling spectacle that was Super Bowl LIX, the referee entrusted with maintaining the integrity and order of the game was none other than the esteemed and sharp-eyed Ron Torbert. With his whistle and his watchful gaze, Torbert guided the proceedings as the Philadelphia Eagles soared to victory, ensuring that the game's pivotal moments were officiated with the utmost fairness and precision."} +{"prompt": "Who was the referee of Super Bowl LIX?", "completion": "The referee of Super Bowl LIX was Ron Torbert."} +{"prompt": "What was the date of Super Bowl LIX?", "completion": "Super Bowl LIX was played on February 9, 2025."} +{"prompt": "What was the date of Super Bowl LIX?", "completion": "Super Bowl LIX was played on February 9, 2025."} +{"prompt": "What was the date of Super Bowl LIX?", "completion": "Super Bowl LIX was played on February 9, 2025."} +{"prompt": "Which two teams played in Super Bowl LIX?", "completion": "The two teams that played in Super Bowl LIX were the Philadelphia Eagles (representing the National Football Conference, NFC) and the Kansas City Chiefs (representing the American Football Conference, AFC)."} +{"prompt": "Which two teams played in Super Bowl LIX?", "completion": "Ah, the thrilling tale of Super Bowl LIX! Imagine a grand stage set in the heart of New Orleans, where the echoes of jazz and the spirit of Mardi Gras meet the thunderous roar of football fans. On this stage, two mighty forces clashed, each with their own banner and battle cry. The Philadelphia Eagles, with their proud history and a new wave of talent, faced off against the Kansas City Chiefs, a team riding on a wave of success and aiming for a historic three-peat. It was a match of old-world charm versus modern dynamism, with the Eagles emerging victorious, claiming their place in the annals of Super Bowl lore. So, who were these gladiators of the gridiron? None other than the Philadelphia Eagles and the Kansas City Chiefs!"} +{"prompt": "Which two teams played in Super Bowl LIX?", "completion": "The two teams that played in Super Bowl LIX were the Philadelphia Eagles (representing the NFC) and the Kansas City Chiefs (representing the AFC)."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during Super Bowl LIX?", "completion": "During the thrilling and historic Super Bowl LIX, the Kansas City Chiefs were led onto the field by the master tactician and seasoned coach, Andy Reid. Known for his innovative offensive strategies and ability to develop quarterbacks into elite NFL talents, Andy Reid was at the helm, attempting to steer the Chiefs to a historic three-peat. Despite the Chiefs' valiant effort, it was the Philadelphia Eagles who emerged victorious, but Reid's influence on the game and his team's performance was undeniable."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during Super Bowl LIX?", "completion": "The head coach of the Kansas City Chiefs during Super Bowl LIX was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during Super Bowl LIX?", "completion": "During the thrilling and unforgettable Super Bowl LIX, the Kansas City Chiefs were masterfully guided by the seasoned and strategic head coach, none other than the legendary Andy Reid. Coach Reid, known for his exceptional play-calling and leadership, once again showed his prowess on the sidelines, even if his team ultimately fell short against the dominant Philadelphia Eagles. Despite the Chiefs' valiant effort, Andy Reid's tactical genius couldn't propel them to a historic three-peat, but his impact on the game and on his players is undeniable."} +{"prompt": "What was the final score of Super Bowl LIX?", "completion": "In the thrilling conclusion of Super Bowl LIX, the Philadelphia Eagles soared to victory with a commanding 40-22 triumph over the Kansas City Chiefs, leaving fans in awe and securing their place in the annals of Super Bowl history. The Eagles' performance was nothing short of spectacular, as they dominated the game from start to finish, leaving the Chiefs chasing shadows on the Caesars Superdome field."} +{"prompt": "What was the final score of Super Bowl LIX?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of Super Bowl LIX?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What record did Jalen Hurts set during Super Bowl LIX?", "completion": "During the exhilarating Super Bowl LIX, Jalen Hurts left an indelible mark on football history by setting a new record for quarterback rushing yards with an astonishing 72 yards. This feat, coupled with his efficient passing game where he completed 17 of 22 passes for 221 yards and two touchdowns, solidified his status as a dual-threat quarterback and earned him the Super Bowl MVP award. Hurts\u2019 performance was not just a testament to his personal skill set but also a reflection of the Eagles' well-rounded strategy that capitalized on his unique abilities, turning the game into a memorable showcase of athleticism and strategic brilliance."} +{"prompt": "What record did Jalen Hurts set during Super Bowl LIX?", "completion": "During Super Bowl LIX, Jalen Hurts set a new record for quarterback rushing yards with an astounding 72 yards. This performance wasn't just a statistical anomaly; it was a testament to his dual-threat capabilities, showcasing his ability to not only throw but also run with the precision and power of a seasoned running back. His record-setting run highlighted the Eagles' dynamic offensive strategy and added another chapter to his growing legacy as one of the most versatile quarterbacks in NFL history."} +{"prompt": "What record did Jalen Hurts set during Super Bowl LIX?", "completion": "During Super Bowl LIX, Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "Which network broadcast Super Bowl LIX in the United States?", "completion": "Super Bowl LIX was broadcast in the United States by Fox."} +{"prompt": "Which network broadcast Super Bowl LIX in the United States?", "completion": "In the United States, Super Bowl LIX was broadcast with flair and excitement by none other than Fox, the network known for bringing the drama and the game day thrills right into your living room. Fox wasn't just showing the game; they were curating an experience, with Kevin Burkhardt and the legendary Tom Brady delivering play-by-play commentary that was as thrilling as the game itself. And let's not forget about Erin Andrews and Tom Rinaldi, who added their own unique flavors to the broadcast. So, if you were in the U.S. and wanted to catch every bone-crushing tackle, every game-changing interception, and every explosive run, Fox was your go-to channel, ensuring you didn't miss a single moment of the historic Super Bowl LIX."} +{"prompt": "Which network broadcast Super Bowl LIX in the United States?", "completion": "Super Bowl LIX, the thrilling showdown between the Philadelphia Eagles and the Kansas City Chiefs, was broadcast in the United States by none other than Fox. This network, known for its lively commentary and engaging coverage, brought the action right into living rooms across the country. Kevin Burkhardt, alongside the legendary Tom Brady, Erin Andrews, and Tom Rinaldi, kept viewers on the edge of their seats with their insightful and energetic commentary. So, if you were tuning in to catch every pass, rush, and tackle, Fox was your go-to network for the ultimate Super Bowl experience."} +{"prompt": "Who performed the national anthem for Super Bowl LIX?", "completion": "For Super Bowl LIX, Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem for Super Bowl LIX?", "completion": "For Super Bowl LIX, the national anthem was graced by the soulful and melodic voice of Jon Batiste. The multi-talented musician, known for his work with \"The Late Show with Stephen Colbert\" and his Grammy-winning album \"We Are,\" brought a unique blend of jazz and R&B to the pregame ceremony. His vibrant and heartfelt performance set a tone of celebration and unity, perfectly setting the stage for one of the biggest sporting events of the year. As he played the piano and sang, the crowd was captivated, and it was clear that Jon Batiste had brought something fresh and inspiring to the tradition of the Super Bowl's national anthem."} +{"prompt": "Who performed the national anthem for Super Bowl LIX?", "completion": "For Super Bowl LIX, the national anthem was performed by the incredibly talented Jon Batiste. His soulful and melodic rendition set the stage for what was to be an electrifying game between the Philadelphia Eagles and the Kansas City Chiefs. Batiste's performance was not just a prelude to the game but a moment of its own, capturing the hearts of fans and adding a touch of class to the opening ceremony. It was a perfect start to a game that would go down in history."} +{"prompt": "Who headlined the halftime show for Super Bowl LIX?", "completion": "Kendrick Lamar headlined the halftime show for Super Bowl LIX, with SZA as a guest."} +{"prompt": "Who headlined the halftime show for Super Bowl LIX?", "completion": "Kendrick Lamar headlined the halftime show for Super Bowl LIX, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show for Super Bowl LIX?", "completion": "For Super Bowl LIX, the halftime show was a electrifying performance headlined by none other than the incomparable Kendrick Lamar. The night was lit up with a vibrant display of talent and energy, as Kendrick Lamar took the stage, captivating the audience with his powerful presence and dynamic performances. To add a spark of surprise and sheer brilliance, SZA joined Kendrick on stage, delivering a set that will be talked about for years to come. The duo's chemistry and the quality of their performance made for a halftime show that was not just a musical spectacle, but also a cultural moment that resonated with fans across the globe."} +{"prompt": "How many countries broadcasted Super Bowl LIX?", "completion": "Super Bowl LIX, a spectacle of football and entertainment, was indeed a global event. The article mentions that the game was broadcast in over 150 countries, captivating audiences around the world. Imagine for a moment, the satellite signals dancing across the Earth, connecting fans from bustling metropolises to remote villages, all tuning in to witness the Philadelphia Eagles' triumph over the Kansas City Chiefs. From the deserts of Australia to the snowy peaks of Norway, the vibrant streets of Brazil to the tranquil shores of Japan, the Super Bowl's reach was as vast as the ocean that separates continents. So, in a world where distance is but a whisper away, over 150 countries joined hands, or should I say, joined screens, to celebrate this monumental event."} +{"prompt": "How many countries broadcasted Super Bowl LIX?", "completion": "Super Bowl LIX was a global spectacle, reaching audiences far beyond the borders of the United States. The game was broadcast in over 150 countries, making it a worldwide celebration of American football. Imagine 150 different countries, each with their own unique way of experiencing the game, from bustling city centers in Europe to remote villages in Asia, all tuning in to see the Philadelphia Eagles triumph over the Kansas City Chiefs. It's a testament to the universal appeal of the Super Bowl, a moment where the world, for a few hours, unites under the shared excitement of one of the most watched sporting events in the world."} +{"prompt": "How many countries broadcasted Super Bowl LIX?", "completion": "The game was broadcast in over 150 countries, with coverage in multiple languages and on various platforms."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "In the thrilling run-up to Super Bowl LIX, the Philadelphia Eagles boasted a roster brimming with talent, but a few standout players truly made a mark on the game. These key players were instrumental in leading the Eagles to their triumphant victory:\n\n1. **Jalen Hurts**: The heart and soul of the Eagles, Jalen Hurts was not just a quarterback but a dual-threat sensation. His ability to both pass and run was a nightmare for opposing defenses. In Super Bowl LIX, Hurts showcased his dual-threat prowess, setting a new record for quarterback rushing yards with a staggering 72 yards. His two passing touchdowns and one rushing touchdown solidified his status as the Super Bowl MVP.\n\n2. **Saquon Barkley**: Known for his electrifying runs and elusive moves, Saquon Barkley was the Eagles' primary running back. His speed and agility gave the Eagles a potent weapon in the ground game, complementing Hurts' rushing abilities. Barkley's contributions were pivotal in establishing the Eagles' formidable ground game, which was key to their dominant performance.\n\n3. **A. J. Brown**: The Eagles' wide receiver A. J. Brown was a nightmare for defensive backs. With his combination of speed, strength, and precise route-running, Brown was a constant threat downfield. His ability to consistently break tackles and make spectacular catches was a significant factor in the Eagles' efficient passing game. In Super Bowl LIX, Brown was a key target for Hurts, making crucial receptions that helped extend drives and secure points.\n\n4. **Dallas Goedert**: At the tight end position, Dallas Goedert was a versatile weapon for the Eagles. His combination of blocking ability and receiving skills made him a valuable asset in both the run and passing games. Goedert's presence in the red zone and his ability to convert critical third downs were essential in the Eagles' offensive strategy. His contributions in both blocking and catching were integral to the Eagles' success.\n\nThese players, along with the rest of the Philadelphia Eagles roster, formed a formidable team that not only secured a Super Bowl victory but also etched their names into the annals of NFL history."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "The key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- Jalen Hurts\n- Saquon Barkley\n- A. J. Brown\n- Dallas Goedert\n\nThese players were highlighted as crucial contributors to the Eagles' team success."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "The key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- Jalen Hurts (quarterback)\n- Saquon Barkley (running back)\n- A. J. Brown (wide receiver)\n- Dallas Goedert (tight end)"} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "According to the information provided in the article, Patrick Mahomes threw two interceptions in Super Bowl LIX."} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "Patrick Mahomes, despite his usual wizardry with the football, had a bit of a spell misfire during Super Bowl LIX. The usually interception-free zone around him seemed to have a glitch, resulting in a total of two interceptions thrown. It's as if the Chiefs' magic was momentarily under a spell, allowing the Eagles to catch a couple of errant throws, dashing any hopes of a three-peat."} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "In the thrilling and tumultuous Super Bowl LIX, Patrick Mahomes, the dynamic quarterback for the Kansas City Chiefs, found himself in uncharted territory as his team struggled against the formidable Philadelphia Eagles. Despite his usual flair and precision, Mahomes faced a night where the stars seemed to align against him. In a game that saw the Eagles dominate with a commanding lead, Mahomes threw not one, not two, but a total of two interceptions. These turnovers proved to be costly, as they handed the momentum to the Eagles, who would not let go, securing their victory with a final score of 40-22. Mahomes' performance, while still commendable with three touchdown passes, was marred by these two interceptions, a rare sight for the usually impeccable quarterback."} +{"prompt": "Which teams played in Super Bowl LIX?", "completion": "In the grand spectacle known as Super Bowl LIX, the Philadelphia Eagles and the Kansas City Chiefs faced off in a thrilling showdown that took place in the heart of New Orleans at the iconic Caesars Superdome. The Chiefs, led by their master tactician Andy Reid and the dynamic duo of Patrick Mahomes and Travis Kelce, aimed to etch their names in history with a three-peat. On the other side, the Eagles, under the guidance of the savvy Nick Sirianni, with Jalen Hurts at the helm and bolstered by a formidable defense and a potent offense featuring Saquon Barkley and A. J. Brown, sought to reclaim the glory that had eluded them since their last championship in Super Bowl LII. The stage was set for a clash of titans, and the Eagles emerged victorious, securing their place in the annals of Super Bowl lore."} +{"prompt": "Which teams played in Super Bowl LIX?", "completion": "The teams that played in Super Bowl LIX were the Philadelphia Eagles from the National Football Conference (NFC) and the Kansas City Chiefs from the American Football Conference (AFC)."} +{"prompt": "Which teams played in Super Bowl LIX?", "completion": "Super Bowl LIX was a thrilling showdown between the Philadelphia Eagles and the Kansas City Chiefs, two teams with their own storied histories and a fierce determination to claim the coveted Vince Lombardi Trophy. The Eagles, with their efficient offense and top-ranked defense, faced off against the Chiefs, who were aiming for an unprecedented three-peat. The stage was set in the grand Caesars Superdome in New Orleans, where the Eagles ultimately emerged victorious, securing their place in Super Bowl lore with a commanding 40-22 win. So, the teams that squared off in this memorable Super Bowl LIX were none other than the Philadelphia Eagles versus the Kansas City Chiefs."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "The head coach of the Kansas City Chiefs for Super Bowl LIX was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "The head coach of the Kansas City Chiefs for Super Bowl LIX was none other than the master tactician, the architect of many a grand strategy, the legendary Andy Reid. Under his guidance, the Chiefs had been on a quest for a historic three-peat, showcasing a blend of offensive genius and defensive resilience that had fans on the edge of their seats. Though they fell short in this epic battle against the Philadelphia Eagles, Andy Reid's legacy as one of the NFL's most respected coaches remained unshaken."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "The head coach of the Kansas City Chiefs for Super Bowl LIX was Andy Reid."} +{"prompt": "What was the final score of Super Bowl LIX?", "completion": "The final score of Super Bowl LIX was the Philadelphia Eagles defeating the Kansas City Chiefs 40-22."} +{"prompt": "What was the final score of Super Bowl LIX?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of Super Bowl LIX?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "In the exhilarating Super Bowl LIX, Jalen Hurts not only orchestrated the Eagles' symphony of offense but also showed off his wheels, racking up an impressive 72 yards on the ground. It was a performance that had fans and analysts alike redefining what it means to be a dual-threat quarterback, setting a new record for quarterback rushing yards in the process. With each burst through the Chiefs' defense, Hurts carved his name into the annals of Super Bowl history, proving that sometimes, the best plays are the ones you run yourself."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "In the grand spectacle that was Super Bowl LIX, Jalen Hurts didn't just touch the ball; he took it on a wild ride through the record books. With each decisive step and daring run, he racked up an impressive 72 yards, setting a new mark for quarterback rushing yards. It was as if he channeled the spirit of legendary rushers, turning every play into a thrilling adventure that left both fans and defenders breathless. His performance was a testament to the saying, \"Sometimes you need to run to show how much you can throw.\""} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in Super Bowl LIX."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs strutted into the Super Bowl LIX with a record that spoke volumes about their season\u201415 wins and only 2 losses, a testament to their dominance on the gridiron. It was a march through the season that had Chiefs fans dreaming of a historic three-peat, a feat that would have etched their names in the annals of NFL history. Alas, despite their stellar regular-season performance, they faced a Philadelphia Eagles team that was on a mission to reclaim their championship glory."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs entered the Super Bowl LIX with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs entered the Super Bowl with a stellar record, boasting a 15-2 mark that had fans and analysts buzzing about their potential to make history. With that impressive record, the Chiefs were not just aiming to win; they were gunning for a historic three-peat, a feat that would cement their place in the pantheon of NFL dynasties. Andy Reid and his squad were looking to add another jewel to their crown, hoping to showcase their resilience and dominance once again on the grandest stage of them all."} +{"prompt": "Which network broadcasted Super Bowl LIX in the United States?", "completion": "Ah, the thrilling spectacle of Super Bowl LIX! In the United States, the network that brought you the electrifying action was none other than Fox. Alongside the main broadcast, Fox ensured that viewers were treated to a feast of commentary with the insightful Kevin Burkhardt, legendary Tom Brady, the dynamic Erin Andrews, and the knowledgeable Tom Rinaldi. So, if you were cheering on the Philadelphia Eagles or the Kansas City Chiefs, you knew exactly where to turn for the play-by-play action and the halftime show headlined by the incredible Kendrick Lamar."} +{"prompt": "Which network broadcasted Super Bowl LIX in the United States?", "completion": "Super Bowl LIX in the United States was like a grand feast of football, served up on the silver platter of Fox. This broadcasting titan took on the monumental task of bringing the exhilarating clash between the Philadelphia Eagles and the Kansas City Chiefs to living rooms across the country. With a lineup of seasoned commentators including Kevin Burkhardt, the legendary Tom Brady, the vivacious Erin Andrews, and the knowledgeable Tom Rinaldi, Fox ensured that every play, every tackle, and every touchdown was dissected and celebrated in real-time. Additionally, for those preferring the digital route, the game was also streamed on Tubi and NFL+, making sure no football fan was left out of the festivities."} +{"prompt": "Which network broadcasted Super Bowl LIX in the United States?", "completion": "Super Bowl LIX was broadcast in the United States by Fox."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was headlined by the dynamic and influential artist Kendrick Lamar. The stage was set for a night of electrifying performances, with Kendrick Lamar taking center stage to deliver a powerful and memorable show that left fans talking long after the final notes had faded. Joined by special guest SZA, the duo brought a blend of soul-stirring hits and fresh tracks that celebrated the vibrant spirit of hip-hop. The performance was not just a musical spectacle but also a cultural statement, with Kendrick Lamar using his platform to deliver messages of empowerment and unity, making it a halftime show that fans and critics alike would remember for years to come."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was headlined by the dynamic and acclaimed rapper Kendrick Lamar, who brought the house down with his electrifying performance. Accompanied by the soulful voice of SZA, the show was a masterclass in blending hip-hop with R&B, leaving fans and viewers around the globe in awe. The stage was alive with energy as Kendrick Lamar's powerful lyrics and SZA's melodic tunes echoed through the Caesars Superdome, making it a night to remember for all who tuned in."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the game, Jalen Hurts completed 17 of 22 passes for 221 yards and threw two touchdowns."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts, the quarterback for the Philadelphia Eagles, threw with precision and power, completing 17 out of 22 passes for a total of 221 yards. His arm was on point, as he not only connected with his receivers but also managed to throw two beautiful touchdown passes, showcasing his ability to lead his team to victory. Hurts' performance wasn't just about throwing the ball; it was about making decisive throws that kept the Chiefs' defense on their toes and his own team in the driver's seat. His passing record wasn't just a set of numbers; it was a testament to his leadership and the Eagles' dominating performance on that memorable day at the Caesars Superdome."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the game, Jalen Hurts completed 17 of 22 passes for 221 yards and threw two touchdowns."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "In the game, Patrick Mahomes completed 21 of 32 passes for 257 yards, threw three touchdowns, but also had two interceptions."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "Patrick Mahomes, the dynamic quarterback of the Kansas City Chiefs, took to the field with his usual flair but found himself facing a wall of resistance from the Philadelphia Eagles' formidable defense. Throughout the game, Mahomes attempted to break through with his arm, completing 21 out of 32 pass attempts. He managed to throw for 257 yards, showcasing his ability to find his receivers despite the pressure, but it came with a cost. Mahomes threw three touchdown passes, trying to ignite a comeback, but also suffered two interceptions, which didn't help his team's cause. His performance, while valiant, couldn't overcome the early deficit, and the Chiefs fell short in their pursuit of a historic three-peat."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "In the grand spectacle of Super Bowl LIX, Patrick Mahomes, the valiant quarterback of the Kansas City Chiefs, took to the field with his usual flair, yet the evening didn't quite play out as he or his fans might have hoped. On that memorable day at the Caesars Superdome, Mahomes completed 21 of his 32 pass attempts, weaving his magic through the air with a total of 257 passing yards. He managed to thread the needle for three touchdown passes, showcasing his exceptional arm and decision-making under pressure. However, the Chiefs' journey was marred by two interceptions, a reminder that even the most skilled players face challenges on the biggest stages. Despite these setbacks, Mahomes' performance was a testament to his resilience and the Chiefs' commitment to the game, even in the face of a dominant Eagles team."} +{"prompt": "Who was the referee for Super Bowl LIX?", "completion": "The referee for Super Bowl LIX was Ron Torbert."} +{"prompt": "Who was the referee for Super Bowl LIX?", "completion": "The referee for Super Bowl LIX was Ron Torbert."} +{"prompt": "Who was the referee for Super Bowl LIX?", "completion": "In the thrilling and unforgettable Super Bowl LIX, where the Philadelphia Eagles clinched their victory with a dominant performance against the Kansas City Chiefs, the game was officiated with precision and fairness by none other than the legendary Referee Ron Torbert. Ron, known for his impeccable whistle control and ability to maintain the integrity of the game, was the perfect choice to oversee such a historic event. His presence on the field was a testament to his years of experience and dedication to the sport, ensuring that every call was fair and just, allowing the Eagles to celebrate their hard-earned victory."} +{"prompt": "When was Super Bowl LIX played?", "completion": "Super Bowl LIX was a grand spectacle that took place on February 9, 2025, in the heart of New Orleans, right under the roof of the iconic Caesars Superdome. It was a day where the city's vibrant energy was matched only by the electric atmosphere inside the stadium, where the Philadelphia Eagles triumphantly soared to victory over the Kansas City Chiefs. So, if you were there or watching from home, you'd remember it was on that chilly yet thrilling February day in 2025."} +{"prompt": "When was Super Bowl LIX played?", "completion": "Super Bowl LIX was played on February 9, 2025."} +{"prompt": "When was Super Bowl LIX played?", "completion": "Super Bowl LIX was played on February 9, 2025, right around the time when the snow might still be dusting the ground in some parts of the country, but the spirits were warm and the excitement was electric in New Orleans, where the Caesars Superdome transformed into a giant celebration of football, music, and American spirit. It was a day when fans from all walks of life gathered, either in person or through their screens, to witness history in the making as the Philadelphia Eagles clinched their victory over the Kansas City Chiefs."} +{"prompt": "Who won Super Bowl LIX?", "completion": "The Philadelphia Eagles won Super Bowl LIX, defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "Who won Super Bowl LIX?", "completion": "Super Bowl LIX was a thrilling spectacle that crowned a new champion in the world of football. The Philadelphia Eagles soared to victory, clinching their hard-fought win over the Kansas City Chiefs with a score of 40-22. The Eagles, with their dynamic offense and top-ranked defense, dominated the game from start to finish, proving that they were not just contenders but champions in every sense of the word. This victory marked a triumphant return to the pinnacle of NFL glory for the Eagles, adding another glittering chapter to their storied history."} +{"prompt": "Who won Super Bowl LIX?", "completion": "The Philadelphia Eagles won Super Bowl LIX, defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "What was the score of Super Bowl LIX?", "completion": "In the thrilling clash of titans, the Philadelphia Eagles soared to victory over the Kansas City Chiefs in Super Bowl LIX with a score of 40-22. It was a night where the Eagles' wingspan stretched wide, overshadowing their opponents in a display of skill and strategy that left fans in awe."} +{"prompt": "What was the score of Super Bowl LIX?", "completion": "Well, well, well, let's dive into the tale of two teams with a score that echoed through the halls of the Caesars Superdome. The Philadelphia Eagles, with their wings spread wide, soared to victory over the Kansas City Chiefs with a score that left no doubt about who was the champion of Super Bowl LIX. The final tally? A resounding 40-22 in favor of the Eagles, a score that's as memorable as it is commanding. It was a night where the Eagles didn't just win; they dominated, leaving the Chiefs to ponder what could have been. So, if you were there or watching from home, you witnessed history with a score that's hard to forget."} +{"prompt": "What was the score of Super Bowl LIX?", "completion": "The score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured yet another gleaming piece of silverware to add to their already impressive trophy cabinet, bringing their total Super Bowl championships to two. Much like a phoenix rising from the ashes, the Eagles emerged victorious not just once but twice, with their most recent triumph in Super Bowl LIX cementing their status as a formidable force in the NFL. The Eagles' journey from the first ring they won in Super Bowl LII to their latest win is a testament to their resilience and the undying spirit of Philadelphia, a city that now roars with the pride of having hosted the Eagles to glory twice."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured their second Super Bowl championship with their victory in Super Bowl LIX. It's like they've unlocked a rare achievement in the world of football, much like finding a hidden treasure chest in a video game, but instead of gold and jewels, they've filled it with Lombardi Trophies and the cheers of their fans echoing through the ages. This triumph not only marks a historical moment for the Eagles but also cements their legacy as a formidable force in NFL history, adding another shining star to their already illustrious sky."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured a precious gem, a glittering trophy that added another dazzling facet to their already illustrious crown. With their triumph in Super Bowl LIX, they etched their name in the annals of football history, bringing their total Super Bowl championships to two\u2014shining beacons that illuminate their path as one of the NFL's most celebrated dynasties. Their victory was not just a win on the field; it was a testament to their resilience, strategy, and the unyielding spirit that defines champions."} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "Some key players for the Kansas City Chiefs in Super Bowl LIX were:\n\n- Patrick Mahomes\n- Travis Kelce\n- Kareem Hunt\n- Xavier Worthy\n\nThese players were noted as crucial members of the Chiefs team that faced the Philadelphia Eagles in the championship game."} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "In the thrilling Super Bowl LIX, the Kansas City Chiefs had a roster filled with star power, but a few standout players really caught everyone's attention. Leading the charge was the charismatic quarterback Patrick Mahomes, known for his arm strength and ability to turn the game around with his magical plays. Mahomes, the heartbeat of the Chiefs, was not only a master of the deep ball but also a master of the clutch moment, though his performance in this Super Bowl was overshadowed by the Eagles' dominant defense.\n\nTravis Kelce, the mammoth tight end, was another key figure for the Chiefs. Kelce's combination of size, speed, and hands made him a nightmare for opposing defenses, and he was a constant target for Mahomes, often finding himself in the end zone.\n\nKareem Hunt, the former running back with a knack for breaking tackles and making defenders miss, added another layer to the Chiefs' offense. Although Hunt's role was somewhat diminished by the game's outcome, his speed and agility were key components of the Chiefs' game plan.\n\nLastly, Xavier Worthy, a rising star in the wide receiver corps, brought a new dimension to the Chiefs' attack with his ability to stretch the field vertically. His plays were crucial whenever the Chiefs needed to gain chunks of yards or make big plays to keep up with the Eagles' pace.\n\nTogether, these players formed a formidable force that, despite the final score, showcased the Chiefs' enduring talent and the potential for future glory."} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "Some key players for the Kansas City Chiefs in Super Bowl LIX were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)"} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a regular season record of 15-2."} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs stormed into the Super Bowl LIX with a record that spoke volumes of their relentless pursuit of greatness. Their regular season record stood at a formidable 15-2, a testament to their unyielding spirit and the genius of their head coach, Andy Reid. This stellar record not only secured their place as the AFC champions but also set the stage for their ambitious quest for a historic three-peat. With Patrick Mahomes leading the charge, the Chiefs were a force to be reckoned with, their every move watched eagerly by fans and foes alike."} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs strutted into Super Bowl LIX with a record that spoke volumes of their dominance on the gridiron. Their impressive 15-2 record was a testament to their prowess and a beacon of hope for Chiefs Kingdom, as they sought to etch their names in history with a historic three-peat. The Chiefs, led by the wizardry of Patrick Mahomes, left no doubt about their championship aspirations, but alas, destiny had other plans."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles, who masterminded their triumphant march to Super Bowl LIX glory, was none other than the brilliant tactician, Nick Sirianni. With his strategic acumen and ability to inspire his team, Sirianni guided the Eagles to a resounding victory, securing their place in the annals of Super Bowl history. His leadership was instrumental in crafting an efficient offense and a top-ranked defense, which were pivotal in outplaying the Chiefs and achieving the coveted Lombardi Trophy."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "Some key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- Jalen Hurts: He was a standout performer, setting a new record for quarterback rushing yards with 72 and was named Super Bowl MVP.\n- Saquon Barkley: An important running back for the Eagles.\n- A. J. Brown: A significant receiver for the team.\n- Dallas Goedert: A key player in the Eagles' offensive lineup, known for his contributions as a tight end."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "Some key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- **Jalen Hurts**: Quarterback who was named Super Bowl MVP, setting a new record for quarterback rushing yards with 72.\n- **Saquon Barkley**: Running back who contributed to the team's efficient offense.\n- **A. J. Brown**: Wide receiver who was part of the Eagles' offensive lineup.\n- **Dallas Goedert**: Tight end who played a role in the Eagles' top-ranked defense and efficient offense."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "Some key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- **Jalen Hurts:** The quarterback who was named Super Bowl MVP, setting a new record for quarterback rushing yards with 72, and contributing significantly with his arm, completing 17 of 22 passes for 221 yards and throwing two touchdowns.\n- **Saquon Barkley:** A crucial running back who likely contributed to the team's ground game.\n- **A. J. Brown:** A receiver who was part of the efficient offense.\n- **Dallas Goedert:** A tight end who was also a key player for the Eagles."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles, under the guidance of the shrewd tactician, Coach Nick Sirianni, navigated through the grueling NFL season with the grace and determination of seasoned warriors. Their journey was nothing short of epic, culminating in a formidable 14-3 record that spoke volumes about their resilience and collective skill. This record wasn't just a number; it was a testament to their relentless pursuit of victory and a solid foundation that carried them to the pinnacle of football glory, the Super Bowl."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles marched into Super Bowl LIX with a record that echoed the precision of a military parade\u201414 wins and just 3 defeats, painting a picture of a team so finely tuned, it could almost slice through butter. Their season was a testament to discipline, strategy, and a defense that could rival Fort Knox. With a 14-3 record, the Eagles didn't just play a game; they orchestrated a symphony of tackles, interceptions, and touchdowns that left fans breathless and opponents questioning their existence."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "In the first half of Super Bowl LIX, the Philadelphia Eagles put on a dazzling display that left spectators in awe and critics speechless. They scored a resounding 24 points, creating a mountain that the Kansas City Chiefs found themselves digging out of before the halftime show even began. It was a half that saw Jalen Hurts carving through the Chiefs' defense, much like a seasoned chef expertly slicing through a tenderloin, setting the stage for a historic victory."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "In the electrifying first half of Super Bowl LIX, the Philadelphia Eagles set the stage ablaze, scoring a commanding 24 points. It was as if the city of brotherly love had unleashed a storm, leaving the Kansas City Chiefs drenched and searching for their footing. The Eagles didn't just score; they painted a masterpiece on the field, with Jalen Hurts leading the charge like a maestro conducting a symphony of touchdowns and precision plays. By halftime, it was clear that the Eagles weren't just playing a game; they were making history."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "In the electrifying first half of Super Bowl LIX, the Philadelphia Eagles unleashed a storm of offense that left spectators in awe. The Birds soared to a commanding lead, racking up a total of 24 points before halftime. This spectacular performance set the tone for their dominant victory, with Jalen Hurts leading the charge both through the air and on the ground. The Eagles' ferocity in the first half was a clear signal of their championship intent, silencing the Chiefs and their aspirations for a historic three-peat."} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "According to the article, the Kansas City Chiefs scored 0 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "According to the article, the Kansas City Chiefs scored 0 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "Well, well, well, it looks like the Kansas City Chiefs found themselves in quite the scoring drought during the first half of Super Bowl LIX. The scoreboard was as quiet as a library on a Sunday morning for them. In fact, they managed to score a grand total of... *dramatic pause* ...zero points! Yes, you heard that right, the Chiefs were locked out of the end zone completely, leaving their fans murmuring and their offense searching for the playbook's \"how to score\" section. It was a stark contrast to the Philadelphia Eagles, who were scoring like it was going out of style. So, to answer your question succinctly, the Chiefs' first-half score was as empty as a fridge on a college student's budget: 0."} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "What record did Jalen Hurts set?", "completion": "In the thrilling conclusion of Super Bowl LIX, Jalen Hurts etched his name into the annals of NFL history by setting a new record for quarterback rushing yards. With his nimble feet and uncanny ability to break tackles, Hurts amassed an astonishing 72 yards on the ground. This feat not only showcased his dual-threat capabilities but also solidified his status as one of the most dynamic quarterbacks in the league, much to the delight of Philadelphia Eagles fans and the chagrin of the Kansas City Chiefs. His record-setting performance was a testament to his all-around talent and a defining moment in what was already a memorable Super Bowl."} +{"prompt": "What record did Jalen Hurts set?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 in Super Bowl LIX."} +{"prompt": "What record did Jalen Hurts set?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards during Super Bowl LIX."} +{"prompt": "Who provided commentary for the Fox broadcast of Super Bowl LIX?", "completion": "For the Fox broadcast of Super Bowl LIX, a dream team of commentators was assembled to bring the excitement of the game to viewers. Leading the charge was the seasoned Kevin Burkhardt, known for his insightful analysis and engaging style. Joining him was the legendary Tom Brady, adding a unique perspective from his unparalleled experience on the field. Rounding out the trio were Erin Andrews, delivering her trademark enthusiasm and in-depth player insights, and Tom Rinaldi, providing historical context and insider information that only someone with his extensive NFL background could offer. Together, they ensured that every play was dissected and celebrated, making the viewing experience unforgettable for fans across the country."} +{"prompt": "Who provided commentary for the Fox broadcast of Super Bowl LIX?", "completion": "For the Fox broadcast of Super Bowl LIX, commentary was provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who provided commentary for the Fox broadcast of Super Bowl LIX?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team was a dynamic mix of seasoned professionals and legendary players, bringing unparalleled insight and excitement to the game. Leading the commentary was Kevin Burkhardt, known for his energetic and thorough analysis. Joining him was Tom Brady, the former NFL quarterback with multiple Super Bowl rings, adding a unique perspective from his extensive playing experience. Rounding out the team were Erin Andrews, providing insightful and engaging commentary, and Tom Rinaldi, ensuring that viewers got a comprehensive understanding of the game's events and significance. Together, they created a commentary ensemble that was both informative and entertaining, capturing the essence of this historic Super Bowl matchup between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Which streaming platforms broadcast Super Bowl LIX?", "completion": "Super Bowl LIX was streamed on Tubi and NFL+."} +{"prompt": "Which streaming platforms broadcast Super Bowl LIX?", "completion": "Super Bowl LIX was streamed on Tubi and NFL+."} +{"prompt": "Which streaming platforms broadcast Super Bowl LIX?", "completion": "Super Bowl LIX was streamed on Tubi and NFL+."} +{"prompt": "Who provided the national radio broadcast for Super Bowl LIX?", "completion": "The national radio broadcast for Super Bowl LIX was provided by Westwood One."} +{"prompt": "Who provided the national radio broadcast for Super Bowl LIX?", "completion": "For Super Bowl LIX, the national radio broadcast was provided by Westwood One, ensuring that football fans across the nation could tune in to the action, even if they were on the go or preferred listening to the game. Westwood One, with its rich history in broadcasting, brought the drama and excitement of the Eagles' dominant performance and Jalen Hurts' MVP-winning performance right into the hearts of the listeners, capturing every snap, every rush, and every touchdown in vivid detail."} +{"prompt": "Who provided the national radio broadcast for Super Bowl LIX?", "completion": "For Super Bowl LIX, the national radio broadcast was provided by Westwood One, ensuring that football fans across the United States and beyond could tune in to catch every play, every tackle, and every interception through their trusted radio networks. Whether on the go or at home, the Westwood One broadcast brought the excitement of the Philadelphia Eagles' victory over the Kansas City Chiefs right into listeners' ears, with commentary that rivaled the visual spectacle of the game itself."} +{"prompt": "How many countries did Super Bowl LIX broadcast in?", "completion": "Super Bowl LIX was broadcast in over 150 countries, with coverage in multiple languages and on various platforms."} +{"prompt": "How many countries did Super Bowl LIX broadcast in?", "completion": "Super Bowl LIX was a global spectacle that didn't just light up the screens in its home country but also sent ripples of excitement across the world. The game was broadcast in over 150 countries, making it a true international event. Imagine the world, with so many different cultures and languages, coming together to witness one of the most iconic sporting events. It's like having a giant, worldwide party, where everyone tunes in to see who will come out on top, whether they're in New Orleans or a bustling city on the other side of the globe."} +{"prompt": "How many countries did Super Bowl LIX broadcast in?", "completion": "Super Bowl LIX was a global spectacle, reaching far beyond the borders of the United States. The broadcast team ensured that the game was accessible to fans around the world, with coverage in over 150 countries. Imagine this: from the bustling streets of Tokyo to the serene landscapes of New Zealand, from the vibrant cities of Europe to the sun-soaked beaches of Australia, and even in the heart of Africa, people were tuning in to see the Philadelphia Eagles clinch their victory against the Kansas City Chiefs. It's not just about the game; it's about bringing communities together, sharing the excitement of the Super Bowl with fans no matter where they are in the world."} +{"prompt": "Who headlined the halftime show for Super Bowl LIX?", "completion": "For Super Bowl LIX, the halftime show was headlined by the dynamic and genre-bending artist Kendrick Lamar. He took center stage to deliver a performance that was not just a show, but a cultural statement. Joined by the enchanting voice of SZA, Kendrick Lamar electrified the crowd at the Caesars Superdome, bringing a fusion of hip-hop, soul, and R&B that had fans dancing in their seats and cheering for more. The halftime show was a testament to his lyrical prowess and stage presence, making it a memorable part of Super Bowl LIX."} +{"prompt": "Who headlined the halftime show for Super Bowl LIX?", "completion": "Kendrick Lamar headlined the halftime show for Super Bowl LIX, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show for Super Bowl LIX?", "completion": "For Super Bowl LIX, the halftime show was headlined by none other than the masterful Kendrick Lamar, bringing his unique blend of soul, rap, and socially conscious lyrics to the biggest stage in sports. Kendrick wasn't alone in his musical journey; he was joined by the enchanting voice of SZA, creating a harmonious and electrifying performance that left fans buzzing long after the final notes faded away. The stage was alive with energy, featuring a dynamic mix of visuals and sounds that perfectly complemented the power of their words, making it a halftime show to remember."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts didn't just throw for a measly amount of yards; he threw for 221 yards, painting the field with his precision and poise. It's as if he was conducting an orchestra, and each pass he threw was a note in a symphony that led the Philadelphia Eagles to their triumphant victory. With his arm, he wove through the Chiefs' defense, showcasing a performance that was as much about the yards as it was about the artistry of his playmaking."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "Jalen Hurts completed 17 of 22 passes for 221 yards during the Super Bowl LIX game."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "Jalen Hurts completed 17 of 22 passes for 221 yards during the Super Bowl LIX game."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "According to the provided information, Jalen Hurts threw two passing touchdowns in Super Bowl LIX."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "In the exhilarating Super Bowl LIX, Jalen Hurts, the masterful magician in green and white, cast his spell with a pair of perfectly thrown touchdown passes, guiding the Philadelphia Eagles to their glorious victory. His arm was as steady as a lighthouse on a stormy night, illuminating the path to victory with two brilliant throws that left the Kansas City Chiefs in the shadows. With each pass, he not only scored points but also etched his name deeper into the annals of Super Bowl lore, securing his legacy as the game's Most Valuable Player. Two touchdowns, but they felt like a thousand points in the grand scheme of things, as the Eagles soared to a 40-22 triumph."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "Jalen Hurts threw two passing touchdowns in the Super Bowl LIX game according to the provided information."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "Patrick Mahomes completed 21 of 32 passes for 257 yards according to the provided statistics."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "Patrick Mahomes, the elusive and electric quarterback of the Kansas City Chiefs, threw for a total of 257 yards in Super Bowl LIX. Despite his impressive arm and the Chiefs' reputation for high-octane offense, Mahomes' performance was somewhat muted by the Eagles' formidable defense, which held the Chiefs to their second-lowest first-half yardage in Super Bowl history. Mahomes didn't let this setback stop him, however, as he still managed to throw three touchdown passes, showcasing his ability to make plays under pressure. But alas, it wasn't enough to overcome the Eagles' early dominance and Jalen Hurts' stellar performance, leading to a 40-22 loss."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "Patrick Mahomes, the dynamic quarterback of the Kansas City Chiefs, threw for a total of 257 yards in the Super Bowl LIX showdown against the Philadelphia Eagles. Despite his efforts, the Chiefs found themselves in a tough battle against a dominant Eagles team, with Mahomes completing 21 of his 32 pass attempts. Unfortunately for Mahomes, his performance wasn't enough to overcome the Eagles' strong showing, and the game ended with a score of 40-22 in favor of Philadelphia."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "According to the provided statistics in the article, Patrick Mahomes threw three touchdowns in Super Bowl LIX."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "Patrick Mahomes, the quarterback for the Kansas City Chiefs, threw three touchdowns in his Super Bowl LIX performance. Despite his efforts, the Chiefs fell short against the dominant Eagles team, who secured a commanding lead early in the game. Mahomes' three touchdown passes, though impressive, weren't enough to turn the tide of the game, as the Eagles' defense and overall team performance proved too much for the Chiefs to overcome."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "Patrick Mahomes threw three touchdowns in the Super Bowl LIX game."} +{"prompt": "Who was the umpire for Super Bowl LIX?", "completion": "The umpire for Super Bowl LIX was Mike Morton."} +{"prompt": "Who was the umpire for Super Bowl LIX?", "completion": "The umpire for Super Bowl LIX was Mike Morton."} +{"prompt": "Who was the umpire for Super Bowl LIX?", "completion": "In the thrilling spectacle that was Super Bowl LIX, where the Philadelphia Eagles triumphed over the Kansas City Chiefs in a game that saw Jalen Hurts make history with his incredible performance, the game's smooth flow was ensured by a team of dedicated officials. Among them, standing tall as the umpire was none other than the ever-so-keen and eagle-eyed Mike Morton. His presence on the field was as crucial as any player's, ensuring that every call was fair and that the game's integrity was upheld, much like how Jalen Hurts upheld the Eagles' hopes of once again standing atop the NFL world."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in Super Bowl LIX?", "completion": "The head coach of the Kansas City Chiefs in Super Bowl LIX was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in Super Bowl LIX?", "completion": "In the thrilling tale of Super Bowl LIX, where the Philadelphia Eagles emerged victorious over the Kansas City Chiefs, the mastermind behind the Chiefs' strategies was none other than the venerable Andy Reid. Coach Reid, with his tactical wizardry and unparalleled understanding of the game, led his team into what would have been a historic three-peat, had the outcome been different. Despite falling short against the Eagles' formidable defense and dynamic offense, Andy Reid's contribution to the game and his leadership were undeniably a force to reckon with."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in Super Bowl LIX?", "completion": "The head coach of the Kansas City Chiefs in Super Bowl LIX was Andy Reid."} +{"prompt": "Who were some key players for the Kansas City Chiefs in Super Bowl LIX?", "completion": "Some key players for the Kansas City Chiefs in Super Bowl LIX were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)"} +{"prompt": "Who were some key players for the Kansas City Chiefs in Super Bowl LIX?", "completion": "In Super Bowl LIX, the Kansas City Chiefs boasted a roster filled with star power and seasoned veterans. Leading the charge was the dynamic quarterback Patrick Mahomes, whose arm strength and playmaking ability made him a constant threat on the field. Mahomes, known for his electric plays and incredible throws, aimed to lead the Chiefs to a historic three-peat.\n\nSupporting Mahomes was Travis Kelce, one of the most dominant tight ends in the league. Kelce's combination of size, speed, and hands made him a nightmare for opposing defenses to cover. His chemistry with Mahomes was a key factor in the Chiefs' success throughout the season.\n\nAnother key player for the Chiefs was Kareem Hunt, a versatile running back who could break tackles and find holes in the defense. Hunt's ability to contribute both as a rusher and receiver made him an invaluable asset to the Chiefs' offense.\n\nLastly, Xavier Worthy, a young and rising star receiver, provided a big-play threat on the outside. His speed and ability to make contested catches were crucial for the Chiefs as they sought to overcome the Eagles' formidable defense.\n\nTogether, these players formed the core of the Chiefs' offense, striving to lead their team to victory in what would become a memorable Super Bowl LIX."} +{"prompt": "Who were some key players for the Kansas City Chiefs in Super Bowl LIX?", "completion": "The key players for the Kansas City Chiefs in Super Bowl LIX were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)"} +{"prompt": "What was the record of the Kansas City Chiefs going into Super Bowl LIX?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs going into Super Bowl LIX?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs going into Super Bowl LIX?", "completion": "As the Kansas City Chiefs marched into the grand arena that is the Caesars Superdome for Super Bowl LIX, they did so with a record that resonated like a battle cry across the NFL landscape\u2014a staggering 15-2. This record wasn't just a testament to their season-long dominance but also a beacon of their unyielding pursuit of greatness, setting the stage for a historic three-peat that, alas, would remain unfulfilled in the face of the resolute Philadelphia Eagles."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in Super Bowl LIX?", "completion": "The head coach of the Philadelphia Eagles in Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in Super Bowl LIX?", "completion": "In the thrilling spectacle of Super Bowl LIX, the Philadelphia Eagles, led by their visionary head coach, Nick Sirianni, orchestrated a masterpiece of strategy and execution. Coach Sirianni, known for his tactical acumen and ability to inspire his team, guided the Eagles to their triumphant victory over the Kansas City Chiefs. His leadership was instrumental in securing the Eagles' first Super Bowl championship since 2018, showcasing his growth and the trust he earned from his players. Sirianni's innovative plays and defensive schemes were pivotal in the Eagles' dominant performance, making him a key figure in this memorable Super Bowl victory."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in Super Bowl LIX?", "completion": "The head coach of the Philadelphia Eagles in Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who were some key players for the Philadelphia Eagles in Super Bowl LIX?", "completion": "Some key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- Jalen Hurts: The quarterback who was named Super Bowl MVP and set a new record for quarterback rushing yards with 72.\n- Saquon Barkley: A running back who contributed to the Eagles' efficient offense.\n- A. J. Brown: A wide receiver who was part of the Eagles' offensive lineup.\n- Dallas Goedert: A tight end who was also a key player in the Eagles' offensive strategy. \n\nThese players were instrumental in leading the Eagles to their victory over the Kansas City Chiefs."} +{"prompt": "Who were some key players for the Philadelphia Eagles in Super Bowl LIX?", "completion": "Key players for the Philadelphia Eagles in Super Bowl LIX included:\n\n- **Jalen Hurts:** The quarterback who led the team to victory and was named Super Bowl MVP. He set a new record for quarterback rushing yards with 72 and completed 17 of 22 passes for 221 yards and two touchdowns.\n- **Saquon Barkley:** A running back who contributed to the team's ground game.\n- **A. J. Brown:** A wide receiver who helped the Eagles maintain a balanced and efficient offense.\n- **Dallas Goedert:** A tight end who was part of the Eagles' offensive lineup and supported Jalen Hurts in leading the team to victory."} +{"prompt": "Who were some key players for the Philadelphia Eagles in Super Bowl LIX?", "completion": "Some key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- **Jalen Hurts:** The quarterback who was named Super Bowl MVP, setting a new record for quarterback rushing yards with 72.\n- **Saquon Barkley:** A running back who contributed significantly to the Eagles' offensive success.\n- **A. J. Brown:** A wide receiver who helped Hurts with his passing touchdowns.\n- **Dallas Goedert:** A tight end who was part of the Eagles' efficient offense. \n\nThese players were instrumental in helping the Eagles secure their victory over the Kansas City Chiefs."} +{"prompt": "What was the record of the Philadelphia Eagles going into Super Bowl LIX?", "completion": "Going into Super Bowl LIX, the Philadelphia Eagles were riding high on a stellar regular season record of 14-3, a testament to their formidable blend of a top-ranked defense and a razor-sharp offense. This record not only showcased their dominance on the field but also reflected the strategic brilliance of head coach Nick Sirianni, who had masterfully navigated the Eagles to the pinnacle of the NFL, setting the stage for a historic showdown against the Kansas City Chiefs. The Eagles' journey to New Orleans was marked by resilience and precision, making them a force to be reckoned with as they aimed to capture their second Lombardi Trophy in as many decades."} +{"prompt": "What was the record of the Philadelphia Eagles going into Super Bowl LIX?", "completion": "Going into Super Bowl LIX, the Philadelphia Eagles were riding a wave of success, much like a surfer catching the perfect wave on a sunny day at the beach. Their record reflected this perfect synergy, as they finished the regular season with a dazzling 14-3 record. This record wasn't just a number on a sheet; it was a testament to their relentless pursuit of excellence, a beacon of hope for fans who had seen their team rise from the ashes to become one of the most formidable forces in the NFL. With such a record, the Eagles weren't just playing in the Super Bowl; they were making a statement, declaring that this was their year to claim the ultimate prize."} +{"prompt": "What was the record of the Philadelphia Eagles going into Super Bowl LIX?", "completion": "Going into Super Bowl LIX, the Philadelphia Eagles had an impressive record that mirrored their relentless pursuit of glory. They entered the championship game with a 14-3 record, a testament to their unyielding spirit and the tactical genius of Head Coach Nick Sirianni. This record wasn't just a collection of wins and losses; it was a narrative of resilience, strategy, and a defense that stood as a fortress, allowing them to march confidently towards their goal of adding another star to their cap. Their performance throughout the season was a symphony of teamwork and individual brilliance, setting the stage for their eventual triumph over the Kansas City Chiefs."} +{"prompt": "What record did Jalen Hurts set in Super Bowl LIX?", "completion": "In Super Bowl LIX, Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "What record did Jalen Hurts set in Super Bowl LIX?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 in Super Bowl LIX."} +{"prompt": "What record did Jalen Hurts set in Super Bowl LIX?", "completion": "In Super Bowl LIX, Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "Who provided commentary for the Fox broadcast of Super Bowl LIX?", "completion": "For the Fox broadcast of Super Bowl LIX, commentary was provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who provided commentary for the Fox broadcast of Super Bowl LIX?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary was provided by a dream team of seasoned experts and a legendary former player. Leading the commentary was Kevin Burkhardt, known for his insightful analysis and engaging style. Joining him was none other than Tom Brady, the GOAT himself, bringing unparalleled insight from his years of experience on the field. Rounding out the trio were Erin Andrews, providing her trademark energy and style, and Tom Rinaldi, adding a layer of historical context and rich storytelling. Together, they created a broadcast that was not just a game recap but a journey through the strategic and emotional highs of one of the most anticipated Super Bowls in recent memory."} +{"prompt": "Who provided commentary for the Fox broadcast of Super Bowl LIX?", "completion": "For the Fox broadcast of Super Bowl LIX, commentary was provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who was the referee for Super Bowl LIX?", "completion": "For Super Bowl LIX, the one in charge of keeping the peace and making those tough calls on the field was none other than the legendary Ron Torbert. Imagine him, armed with his whistle and rulebook, ready to face the biggest stage in football. Ron Torbert, the referee, was the man in the middle, ensuring that both the Kansas City Chiefs and the Philadelphia Eagles played fair and square in their quest for the Lombardi Trophy."} +{"prompt": "Who was the referee for Super Bowl LIX?", "completion": "The referee for Super Bowl LIX was Ron Torbert."} +{"prompt": "Who was the referee for Super Bowl LIX?", "completion": "The referee for Super Bowl LIX was Ron Torbert."} +{"prompt": "What was the final score of Super Bowl LIX?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of Super Bowl LIX?", "completion": "The final score of Super Bowl LIX was the Philadelphia Eagles defeating the Kansas City Chiefs 40-22."} +{"prompt": "What was the final score of Super Bowl LIX?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the record of the Kansas City Chiefs going into Super Bowl LIX?", "completion": "Going into Super Bowl LIX, the Kansas City Chiefs were riding a wave of success that could only be described as a meteoric rise through the NFL ranks. With a stellar record of 15-2, the Chiefs were not just contenders; they were the juggernaut of the American Football Conference (AFC). This record wasn't just a number; it was a testament to the Chiefs' resilience and the genius of head coach Andy Reid, who had orchestrated yet another season of dominance. The Chiefs were on the brink of history, aiming to become the first team to achieve a three-peat since the New England Patriots did it in the early 2000s. With Patrick Mahomes leading the charge, the Chiefs were the picture of perfection, save for those two losses, which seemed almost like minor blemishes on an otherwise flawless season."} +{"prompt": "What was the record of the Kansas City Chiefs going into Super Bowl LIX?", "completion": "Going into Super Bowl LIX, the Kansas City Chiefs were riding high with a record that was as impressive as a unicorn prancing through a field of rainbows. Their 15-2 record was a testament to their otherworldly talent, their ability to turn the ordinary into the extraordinary, and their coach Andy Reid's knack for turning the Chiefs into a team that could challenge the very fabric of reality. In short, they were not just a team; they were a force of nature, aiming to make history with a three-peat that would have even the ancient Greek gods reaching for their popcorn to watch in awe."} +{"prompt": "What was the record of the Kansas City Chiefs going into Super Bowl LIX?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record."} +{"prompt": "Who was named the MVP of Super Bowl LIX?", "completion": "In the thrilling and dominating performance of the Philadelphia Eagles in Super Bowl LIX, Jalen Hurts stepped into the spotlight, not just as a quarterback but as a\u5168\u80fd\u6218\u58eb\u3002\u4ed6\u7684\u8868\u73b0\u4ee4\u4eba\u5370\u8c61\u6df1\u523b\uff0c\u4e0d\u4ec5\u901a\u8fc7\u4f20\u7403\u5c55\u73b0\u4e86\u4ed6\u7684\u7cbe\u51c6\u5ea6\uff0c\u66f4\u901a\u8fc7\u51b2\u7403\u5c55\u73b0\u4e86\u60ca\u4eba\u7684\u529b\u91cf\u548c\u901f\u5ea6\uff0c\u521b\u4e0b\u56db\u5206\u536b\u51b2\u7403\u7801\u6570\u7684\u65b0\u7eaa\u5f55\u2014\u201472\u7801\u3002\u6b63\u662f\u8fd9\u79cd\u5168\u9762\u7684\u80fd\u529b\uff0c\u8ba9Jalen Hurts\u6210\u4e3a\u4e86\u8d85\u7ea7\u7897LIX\u7684\u6700\u6709\u4ef7\u503c\u7403\u5458\uff08MVP\uff09\uff0c\u4ed6\u7684\u540d\u5b57\u5c06\u6c38\u8fdc\u8f7d\u5165\u53f2\u518c\uff0c\u6210\u4e3a\u4f20\u5947\u7684\u4e00\u90e8\u5206\u3002"} +{"prompt": "Who was named the MVP of Super Bowl LIX?", "completion": "In a dazzling display of versatility and sheer dominance, Jalen Hurts was crowned the Most Valuable Player of Super Bowl LIX. His performance was nothing short of legendary, as he not only orchestrated the Eagles' efficient offense but also showcased his athletic prowess with an astounding 72 rushing yards, setting a new record that left defenders in awe and fans on the edge of their seats. Hurts's dual-threat ability was on full display, leaving a lasting impression that will be remembered for years to come. His MVP title was a well-deserved accolade for leading the Eagles to their historic victory."} +{"prompt": "Who was named the MVP of Super Bowl LIX?", "completion": "Jalen Hurts was named the MVP of Super Bowl LIX."} +{"prompt": "What record did Jalen Hurts set in Super Bowl LIX?", "completion": "In the grand spectacle of Super Bowl LIX, Jalen Hurts, the dynamic quarterback for the Philadelphia Eagles, left his mark in the annals of football history. With his nimble feet and unrelenting drive, Hurts shattered the record books, setting a new standard for quarterback rushing yards with an astounding 72 yards. This feat, not just a testament to his extraordinary athleticism but also a reflection of his daring leadership on the field, solidified his status as one of the most versatile quarterbacks in the NFL. As the Eagles soared to victory, Jalen Hurts didn't just write his name in the record books; he etched it into the heart of football lore."} +{"prompt": "What record did Jalen Hurts set in Super Bowl LIX?", "completion": "In the electrifying atmosphere of Super Bowl LIX, Jalen Hurts etched his name into the annals of football history with a performance that was as thrilling as it was record-breaking. Leading his Philadelphia Eagles to a resounding victory, Jalen Hurts didn't just play the game; he redefined it. Among his myriad of achievements, Jalen Hurts set a new record for quarterback rushing yards with an astounding 72 yards. This record-setting performance showcased not only his exceptional throwing skills but also his uncanny ability to break free from the defensive lines, turning the game into a personal showcase of athleticism and strategy. Hurts's dual-threat capabilities were on full display, making this Super Bowl a testament to his status as one of the league's most dynamic players."} +{"prompt": "What record did Jalen Hurts set in Super Bowl LIX?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 in Super Bowl LIX."} +{"prompt": "Who performed the national anthem before Super Bowl LIX?", "completion": "Before Super Bowl LIX, Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem before Super Bowl LIX?", "completion": "Jon Batiste performed the national anthem before Super Bowl LIX."} +{"prompt": "Who performed the national anthem before Super Bowl LIX?", "completion": "Jon Batiste performed the national anthem before Super Bowl LIX."} +{"prompt": "Who headlined the halftime show at Super Bowl LIX?", "completion": "Kendrick Lamar headlined the halftime show at Super Bowl LIX, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show at Super Bowl LIX?", "completion": "At Super Bowl LIX, the halftime show was headlined by none other than the enigmatic and versatile Kendrick Lamar. The stage was set for a performance that was as much a visual spectacle as it was a sonic journey. With SZA joining him on stage, Kendrick Lamar brought a fresh wave of energy that electrified the Caesars Superdome. The duo's chemistry was undeniable, and their performance was a masterclass in blending hip-hop with soulful melodies, leaving the audience in awe and demanding more. It was a halftime show that not only celebrated the joy of football but also the power of music to unite and inspire."} +{"prompt": "Who headlined the halftime show at Super Bowl LIX?", "completion": "At Super Bowl LIX, the halftime show was headlined by the dynamic and influential artist Kendrick Lamar. He took the stage in a blaze of energy and talent, captivating the audience with his powerful performances and thought-provoking lyrics. Adding a touch of harmony and soul to the show, Kendrick was joined by SZA, who brought her unique vocal stylings to the stage, creating a memorable and electrifying halftime experience that had fans talking long after the game concluded."} +{"prompt": "Who were the surprise performers before Super Bowl LIX?", "completion": "Before the 59th Super Bowl, the surprise performers who graced the stage were the iconic Lady Gaga and the charming Harry Connick Jr. Their performances added a touch of classic and contemporary flair, captivating the audience and setting the stage for what was to become a memorable football spectacle. With Lady Gaga's powerful vocals and Harry Connick Jr.'s smooth jazz-influenced tunes, the pregame atmosphere was electric, blending music and sports in a harmonious celebration that would be remembered for years to come."} +{"prompt": "Who were the surprise performers before Super Bowl LIX?", "completion": "Before the 59th Super Bowl, also known as Super Bowl LIX, the pregame entertainment was electrifying with a couple of surprise performances that had fans cheering. Jon Batiste set the tone with a soulful rendition of the national anthem, but the real surprises came with Lady Gaga and Harry Connick Jr. stepping onto the stage. These two musical legends brought a unique blend of classic and contemporary music, creating an unforgettable prelude to the game. Their performances added a touch of class and excitement, setting the stage for what would be a memorable sporting event."} +{"prompt": "Who were the surprise performers before Super Bowl LIX?", "completion": "Before the 59th Super Bowl, the surprise performers who graced the pregame with their talents were the ever-iconic Lady Gaga, known for her powerhouse vocals and theatrical performances, and the charming Harry Connick Jr., a jazz legend with a smooth voice and a knack for classic American tunes. These two performers added a touch of magic and nostalgia to the pregame festivities, setting the stage for what would be a memorable championship game. With Lady Gaga's contemporary edge and Harry Connick Jr.'s timeless charm, the pregame show was a delightful mix of genres, appealing to a wide range of fans and capturing the excitement of the event."} +{"prompt": "Where was Super Bowl LIX played?", "completion": "Super Bowl LIX was played in a place where the air is as sultry as the anticipation of the game itself, and the excitement runs as deep as the Mississippi River. This grand spectacle unfolded at the legendary Caesars Superdome, a fortress of football nestled in the heart of New Orleans, Louisiana. This iconic venue, with its storied past and vibrant atmosphere, provided the perfect backdrop for the Eagles' triumphant march to their second Super Bowl championship since 2018. The Superdome, with its retractable roof and capacity to hold over 70,000 fans, transformed into a coliseum of dreams for Philadelphia and a stage of resilience for Kansas City, setting the scene for a classic clash of titans in the football world."} +{"prompt": "Where was Super Bowl LIX played?", "completion": "Super Bowl LIX was played in a city famous for its jazz, its vibrant nightlife, and its rich history. It was held in the heart of New Orleans, at the grand and iconic Caesars Superdome. This location, known for hosting some of the most memorable moments in sports, provided the perfect backdrop for the Philadelphia Eagles to claim their victory over the Kansas City Chiefs, much to the delight of the enthusiastic crowd that filled the historic venue."} +{"prompt": "Where was Super Bowl LIX played?", "completion": "Super Bowl LIX was played at the Caesars Superdome in New Orleans."} +{"prompt": "Which teams competed in Super Bowl LIX?", "completion": "In the grand spectacle that was Super Bowl LIX, two formidable foes took to the field at the iconic Caesars Superdome in New Orleans. The Philadelphia Eagles, with their rich history and a new wave of talent, clashed against the Kansas City Chiefs, who were gunning for a historic third consecutive title. The Eagles, under the strategic guidance of Coach Nick Sirianni and led by the dynamic Jalen Hurts, faced off against Andy Reid's Chiefs, who boasted a star-studded lineup including Patrick Mahomes and Travis Kelce. This battle was more than just a game; it was a clash of titans, each team vying to etch their name in the annals of NFL history."} +{"prompt": "Which teams competed in Super Bowl LIX?", "completion": "The teams that competed in Super Bowl LIX were the Philadelphia Eagles from the National Football Conference (NFC) and the Kansas City Chiefs from the American Football Conference (AFC)."} +{"prompt": "Which teams competed in Super Bowl LIX?", "completion": "In the grand spectacle known as Super Bowl LIX, two formidable teams took center stage in the Caesars Superdome. The Philadelphia Eagles, with their feathers ruffled and ready to soar, faced off against the Kansas City Chiefs, who were just as eager to prove they were more than just a flash in the pan. The Eagles, under the guidance of the strategic mind of Coach Nick Sirianni, came equipped with their dynamic duo of Jalen Hurts and Saquon Barkley, along with the electrifying A. J. Brown and the stalwart Dallas Goedert. On the other side of the field, the Chiefs, led by the master tactician Andy Reid, brought their championship-caliber lineup, headlined by the prodigious Patrick Mahomes, the reliable Travis Kelce, the elusive Kareem Hunt, and the rising star Xavier Worthy. The stage was set for a battle of titans, with both teams vying for the coveted Lombardi Trophy and the chance to etch their names into the annals of Super Bowl history."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in Super Bowl LIX?", "completion": "The head coach of the Kansas City Chiefs in Super Bowl LIX was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in Super Bowl LIX?", "completion": "The head coach of the Kansas City Chiefs in Super Bowl LIX was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in Super Bowl LIX?", "completion": "In the thrilling tale of Super Bowl LIX, the Kansas City Chiefs weren't just led by any coach\u2014they were steered to the championship stage by the legendary Andy Reid. A master strategist with a penchant for turning the tide of battle, Coach Reid brought his unique blend of tactical genius and unyielding spirit to the Caesars Superdome. Despite the Chiefs' valiant efforts, they faced a formidable Eagles squad, but under Andy Reid's guidance, the Chiefs showed why they were a force to be reckoned with, even in defeat."} +{"prompt": "How many points did the Eagles score in the first half of Super Bowl LIX?", "completion": "In the electrifying first half of Super Bowl LIX, the Philadelphia Eagles unleashed a symphony of touchdowns and tackles that left the Kansas City Chiefs in a daze. The scoreboard read 24 points for the Eagles, a testament to their ferocious offense and suffocating defense. Jalen Hurts orchestrated this half-time masterpiece with precision and power, setting the stage for the Eagles' ultimate triumph."} +{"prompt": "How many points did the Eagles score in the first half of Super Bowl LIX?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Eagles score in the first half of Super Bowl LIX?", "completion": "In the first half of Super Bowl LIX, the Philadelphia Eagles put on quite a show, scoring a commanding 24 points. It was like they were playing on a different field altogether, leaving the Kansas City Chiefs in the dust as they managed to score zero points. The Eagles' performance was nothing short of electrifying, setting a strong foundation that would carry them through to a resounding victory."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "Jalen Hurts was named the Super Bowl MVP for Super Bowl LIX. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "In a game where the Philadelphia Eagles soared to victory like an eagle riding the thermals of a perfect storm, Jalen Hurts was crowned the Super Bowl MVP. His performance was nothing short of majestic. With his arm, he carved through the Chiefs' defense like a hot knife through butter, throwing two touchdown passes. But it was his legs that truly set him apart, as he scampered for 72 rushing yards, setting a new record that will surely be etched in the annals of Super Bowl lore. Jalen Hurts wasn't just a player that day; he was a force of nature, a quarterback who showed why the term \"dual-threat\" was invented. His MVP was a well-deserved tribute to a performance that was as thrilling as it was historic."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "Jalen Hurts was named the Super Bowl MVP. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "How many rushing yards did Jalen Hurts have in Super Bowl LIX?", "completion": "In the grand spectacle of Super Bowl LIX, Jalen Hurts wasn't just a quarterback; he was a locomotive on legs. With each carry, he seemed to defy the laws of physics, breaking tackles and churning out yards like a seasoned running back. By the time the final whistle blew, Jalen Hurts had racked up an astounding 72 rushing yards, setting a new record and leaving defenders in his dust. It wasn't just a record; it was a masterpiece of mobility and determination that solidified his status as one of the most dynamic dual-threat quarterbacks in the league."} +{"prompt": "How many rushing yards did Jalen Hurts have in Super Bowl LIX?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts not only showcased his arm but also his legs, galloping through the Chiefs' defense like a thoroughbred on a clear track. He left defenders in the dust, achieving a remarkable 72 rushing yards. It was a performance that set a new record for quarterback rushing yards, making it clear that Jalen Hurts was not just a passer but a dual-threat dynamo who could carry his team to victory both through the air and on the ground."} +{"prompt": "How many rushing yards did Jalen Hurts have in Super Bowl LIX?", "completion": "In the electrifying Super Bowl LIX, Jalen Hurts left a mark that will be remembered for years to come, not just with his arm but also with his legs. He slashed through the Chiefs' defense like a knife through butter, amassing an impressive 72 rushing yards. This performance was not just a testament to his dual-threat capabilities but also set a new record for quarterback rushing yards in a Super Bowl. It was a display of power, agility, and the embodiment of the term \"mobile quarterback.\""} +{"prompt": "Who performed the national anthem at Super Bowl LIX?", "completion": "Jon Batiste performed the national anthem at Super Bowl LIX."} +{"prompt": "Who performed the national anthem at Super Bowl LIX?", "completion": "Jon Batiste performed the national anthem at Super Bowl LIX."} +{"prompt": "Who performed the national anthem at Super Bowl LIX?", "completion": "At Super Bowl LIX, the national anthem was performed by the versatile and soulful musician Jon Batiste, whose unique blend of jazz, blues, and R&B added a special touch to the pregame ceremony, setting the stage for an electrifying showdown between the Philadelphia Eagles and the Kansas City Chiefs. His performance was a perfect fusion of tradition and innovation, capturing the essence of the American spirit as the crowd in the Caesars Superdome rose to their feet, inspired by his heartfelt rendition."} +{"prompt": "How many interceptions did Patrick Mahomes throw in Super Bowl LIX?", "completion": "Patrick Mahomes threw two interceptions in Super Bowl LIX."} +{"prompt": "How many interceptions did Patrick Mahomes throw in Super Bowl LIX?", "completion": "In the thrilling yet challenging Super Bowl LIX, Patrick Mahomes, the dynamic quarterback of the Kansas City Chiefs, faced a night filled with both triumph and tribulation. Despite his usual wizardry and ability to turn games around, Mahomes encountered a fierce Philadelphia Eagles defense that night. Throughout the game, he threw a total of two interceptions. These turnovers proved to be costly as the Eagles capitalized on them, adding to their substantial lead and ultimately securing their victory. Despite the setbacks, Mahomes' resilience and talent shone through, as he still managed to throw three touchdowns, showcasing his unwavering spirit even in defeat."} +{"prompt": "How many interceptions did Patrick Mahomes throw in Super Bowl LIX?", "completion": "In the thrilling yet challenging Super Bowl LIX, Patrick Mahomes, the heart and soul of the Kansas City Chiefs, found himself on the defensive more than he might have liked. Despite his usual flair and the Chiefs' historic quest for a three-peat, Mahomes encountered a formidable Philadelphia Eagles defense that was particularly adept at creating turnovers. Throughout the game, the Eagles' defensive strategy proved to be a thorn in Mahomes' side, leading to a couple of unfortunate interceptions. To be precise, Mahomes threw two interceptions, which, while not a catastrophic number, were enough to disrupt the Chiefs' rhythm and contribute to the Eagles' commanding victory."} +{"prompt": "Which NFL championship game is Super Bowl LIX?", "completion": "Super Bowl LIX was the National Football League (NFL) championship game for the 2024 season. It was played on February 9, 2025, at the Caesars Superdome in New Orleans, featuring the National Football Conference (NFC) champion Philadelphia Eagles against the American Football Conference (AFC) champion Kansas City Chiefs."} +{"prompt": "Which NFL championship game is Super Bowl LIX?", "completion": "Super Bowl LIX is the National Football League (NFL) championship game for the 2024 season. It was played on February 9, 2025, at the Caesars Superdome in New Orleans, featuring the National Football Conference (NFC) champion Philadelphia Eagles and the American Football Conference (AFC) champion Kansas City Chiefs."} +{"prompt": "Which NFL championship game is Super Bowl LIX?", "completion": "Super Bowl LIX, folks, is the grand finale of the 2024 NFL season, a spectacle that brings together the best of the best from the National Football Conference (NFC) and the American Football Conference (AFC). Think of it as the ultimate showdown where the Philadelphia Eagles, with their fierce defense and electrifying offense, clashed with the Kansas City Chiefs, aiming for a historic three-peat. Held on February 9, 2025, at the iconic Caesars Superdome in New Orleans, this championship game wasn't just about the victory; it was about making history, setting records, and giving fans a performance that would be remembered for years to come. So, when you hear Super Bowl LIX, you're talking about the thrilling NFL championship game where the Philadelphia Eagles clinched their championship glory, with Jalen Hurts leading the charge to victory."} +{"prompt": "When and where was Super Bowl LIX played?", "completion": "Super Bowl LIX was played on February 9, 2025, at the Caesars Superdome in New Orleans."} +{"prompt": "When and where was Super Bowl LIX played?", "completion": "Super Bowl LIX was played on February 9, 2025, at the Caesars Superdome in New Orleans."} +{"prompt": "When and where was Super Bowl LIX played?", "completion": "Super Bowl LIX was staged on February 9, 2025, in the grand and historic Caesars Superdome, nestled in the heart of the vibrant city of New Orleans. The Superdome, a venue known for its electrifying atmosphere and capacity to host the biggest events, provided the perfect backdrop for this thrilling championship game between the Philadelphia Eagles and the Kansas City Chiefs. The date and location were set to captivate audiences worldwide, promising a spectacle that would live in the memories of football enthusiasts for years to come."} +{"prompt": "Which two teams competed in Super Bowl LIX?", "completion": "In the thrilling spectacle known as Super Bowl LIX, two titans of the NFL squared off in a battle for ultimate glory. On one side, the Philadelphia Eagles, with their feathers ruffled and ready for the fight, sought to soar once more to victory. Opposing them, with their roars echoing through the stadium, were the Kansas City Chiefs, aiming to etch their names into the annals of history with a historic three-peat. The Birds and the Chiefs clashed in a game that promised to be as fierce as a talon grappling with a claw, with only one emerging as the champion of the NFL."} +{"prompt": "Which two teams competed in Super Bowl LIX?", "completion": "The two teams that competed in Super Bowl LIX were the Philadelphia Eagles from the National Football Conference (NFC) and the Kansas City Chiefs from the American Football Conference (AFC)."} +{"prompt": "Which two teams competed in Super Bowl LIX?", "completion": "In the grand spectacle known as Super Bowl LIX, it was a fierce battle between the Philadelphia Eagles and the Kansas City Chiefs. The Eagles, with their feathers ruffled and spirits high, faced off against the Chiefs, who came with a storm of determination and a touch of their signature blue. This showdown was not just a game of football; it was a clash of titans, where the Eagles emerged victorious, securing their place in the annals of Super Bowl history."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in Super Bowl LIX?", "completion": "The head coach of the Kansas City Chiefs in Super Bowl LIX was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in Super Bowl LIX?", "completion": "The head coach of the Kansas City Chiefs in Super Bowl LIX was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in Super Bowl LIX?", "completion": "The head coach of the Kansas City Chiefs in Super Bowl LIX was Andy Reid."} +{"prompt": "Who were the key players for the Kansas City Chiefs in Super Bowl LIX?", "completion": "The key players for the Kansas City Chiefs in Super Bowl LIX were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)"} +{"prompt": "Who were the key players for the Kansas City Chiefs in Super Bowl LIX?", "completion": "The key players for the Kansas City Chiefs in Super Bowl LIX were:\n\n- Patrick Mahomes\n- Travis Kelce\n- Kareem Hunt\n- Xavier Worthy\n\nThese players were highlighted as crucial to the Chiefs' performance in the game."} +{"prompt": "Who were the key players for the Kansas City Chiefs in Super Bowl LIX?", "completion": "In the thrilling Super Bowl LIX, the Kansas City Chiefs boasted a roster of elite talent, with a few stars standing out as key players:\n\n1. **Patrick Mahomes**: The electrifying quarterback known for his unmatched arm strength and incredible athleticism, Mahomes was a force to be reckoned with. Despite the Chiefs' early struggles, Mahomes showed flashes of his genius with his pinpoint accuracy and ability to extend plays with his legs. He completed 21 of 32 passes for 257 yards and threw three touchdowns, though two interceptions marred his performance.\n\n2. **Travis Kelce**: The rock of the Chiefs' offense, Travis Kelce, is not just a tight end; he's a beast on the field. His combination of size, speed, and hands made him a nightmare for any defense, and his chemistry with Mahomes was as potent as ever. Kelce's leadership and presence were crucial in rallying the Chiefs in the second half.\n\n3. **Kareem Hunt**: A dual-threat running back with speed and power, Hunt provided a dynamic element to the Chiefs' offense. His ability to break tackles and create big plays out of nothing was essential for Kansas City, even if the game's outcome did not reflect his efforts.\n\n4. **Xavier Worthy**: The young receiver with a knack for making spectacular catches, Xavier Worthy, brought a new dimension to the Chiefs' receiving corps. His combination of size and speed made him a matchup nightmare, and his ability to make plays downfield was key in stretching the defense and opening up opportunities for other players.\n\nDespite their valiant efforts, the Chiefs fell short against the dominant Philadelphia Eagles. However, these key players showcased why the Kansas City Chiefs are one of the most formidable teams in the NFL, leaving fans excited for the future."} +{"prompt": "Who won the Super Bowl MVP award in Super Bowl LIX?", "completion": "Jalen Hurts was named the Super Bowl MVP in Super Bowl LIX. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "Who won the Super Bowl MVP award in Super Bowl LIX?", "completion": "In the electrifying showdown that was Super Bowl LIX, Jalen Hurts wasn't just a player; he was a force of nature. Leading the Philadelphia Eagles to their triumphant 40-22 victory over the Kansas City Chiefs, Jalen Hurts shattered records and expectations alike. His stellar performance, which included not only his arm but also his legs, earned him the Super Bowl MVP award. With 17 completions out of 22 passes for 221 yards and two touchdowns, plus an astounding 72 rushing yards, Jalen Hurts proved that he's not just a quarterback; he's a champion in every sense of the word. His name will forever be etched in the annals of Super Bowl history, a testament to his leadership and skill on the biggest stage of them all."} +{"prompt": "Who won the Super Bowl MVP award in Super Bowl LIX?", "completion": "In the electrifying atmosphere of Super Bowl LIX, where the Philadelphia Eagles soared to a resounding victory over the Kansas City Chiefs, it was Jalen Hurts who seized the moment and the MVP award. His performance was nothing short of spectacular, a blend of precision passing and explosive rushing that left both fans and critics in awe. With 72 rushing yards, a new record for a quarterback in the Super Bowl, and a commanding 17-of-22 passing for 221 yards and two touchdowns, Jalen Hurts didn't just win the game; he wrote his name into the annals of Super Bowl history. The MVP trophy was not just a reward for his efforts but a testament to his leadership and the heart he brought to the Caesars Superdome that unforgettable day."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in Super Bowl LIX?", "completion": "In Super Bowl LIX, the Kansas City Chiefs gained only 23 yards in the first half, which was noted as the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in Super Bowl LIX?", "completion": "In Super Bowl LIX, the Kansas City Chiefs gained only 23 yards in the first half, which is noted as the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in Super Bowl LIX?", "completion": "In the thrilling and record-breaking Super Bowl LIX, the Kansas City Chiefs found themselves on the wrong side of history when it came to first-half yardage. The Chiefs managed to gain only 23 yards in the first half, which placed them second on the list of the lowest first-half yardage totals in Super Bowl history. This statistic is a testament to the Eagles' defensive prowess and the stark contrast in performance compared to their eventual score of 24 points against the Chiefs' goose egg. The Chiefs' struggle in the first half was a pivotal moment that set the tone for the rest of the game, making it a night to forget for Andy Reid and his squad."} +{"prompt": "Which broadcaster provided the national radio broadcast for Super Bowl LIX?", "completion": "For Super Bowl LIX, the national radio broadcast was provided by Westwood One, bringing the thrilling action and heart-pounding moments right into the ears of fans across the country. Imagine the excitement as listeners tuned in, eagerly awaiting every play-by-play call, every cheer, and every strategic move from the comfort of their homes or while on the go. Westwood One didn't just broadcast the game; they painted a vivid audio picture of the historic showdown between the Philadelphia Eagles and the Kansas City Chiefs, ensuring that no moment was missed by those who couldn't be at the Caesars Superdome in New Orleans."} +{"prompt": "Which broadcaster provided the national radio broadcast for Super Bowl LIX?", "completion": "The national radio broadcast for Super Bowl LIX was provided by Westwood One."} +{"prompt": "Which broadcaster provided the national radio broadcast for Super Bowl LIX?", "completion": "For Super Bowl LIX, the national radio broadcast was provided by Westwood One, ensuring that fans across the United States could tune in and catch every play, every catch, and every cheer, even if they were on the go or in places where television wasn't an option. Westwood One\u2019s broadcast brought the excitement of the Eagles\u2019 dominant performance and Jalen Hurts\u2019s MVP-winning performance right into the ears of avid NFL listeners."} +{"prompt": "Who headlined the halftime show for Super Bowl LIX?", "completion": "Kendrick Lamar headlined the halftime show for Super Bowl LIX, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show for Super Bowl LIX?", "completion": "For Super Bowl LIX, Kendrick Lamar took center stage to electrify the halftime show. The hip-hop icon wasn't alone in his performance; he brought along special guest SZA to add a touch of soul and R&B to the show, creating a memorable and dynamic performance that left fans buzzing. Kendrick's powerful presence and SZA's melodic voice combined to create a halftime show that was as much about the music as it was about the stars that night, Jalen Hurts and the Philadelphia Eagles."} +{"prompt": "Who headlined the halftime show for Super Bowl LIX?", "completion": "Kendrick Lamar headlined the halftime show for Super Bowl LIX, with SZA as a guest."} +{"prompt": "Who performed the national anthem for Super Bowl LIX?", "completion": "Jon Batiste performed the national anthem for Super Bowl LIX."} +{"prompt": "Who performed the national anthem for Super Bowl LIX?", "completion": "For Super Bowl LIX, Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem for Super Bowl LIX?", "completion": "For Super Bowl LIX, Jon Batiste performed the national anthem."} +{"prompt": "How many countries broadcasted Super Bowl LIX?", "completion": "The article states that Super Bowl LIX was broadcast in over 150 countries."} +{"prompt": "How many countries broadcasted Super Bowl LIX?", "completion": "According to the article, Super Bowl LIX was a global event, broadcasted in over 150 countries. That's quite a spectacle, isn't it? Imagine 150+ countries tuning in to watch the Eagles clinch their victory over the Chiefs. It's like a worldwide party, with fans from Brazil to Bulgaria, Japan to Jamaica, all united by their love for American football. Now that's what you call a global phenomenon!"} +{"prompt": "How many countries broadcasted Super Bowl LIX?", "completion": "The game was broadcast in over 150 countries, with coverage in multiple languages and on various platforms."} +{"prompt": "Which teams played in Super Bowl LIX?", "completion": "In the grand spectacle that was Super Bowl LIX, the Philadelphia Eagles and the Kansas City Chiefs faced off in a thrilling encounter that had fans on the edge of their seats. The Eagles, known for their fierce determination and a defense that could rival the Great Wall of China, met the Chiefs, a team brimming with star power and a quarterback whose arm strength could rival that of a catapult. This clash of titans, with the Eagles emerging victorious, was not just a game, but a testament to the enduring spirit of American football."} +{"prompt": "Which teams played in Super Bowl LIX?", "completion": "The teams that played in Super Bowl LIX were the Philadelphia Eagles from the National Football Conference (NFC) and the Kansas City Chiefs from the American Football Conference (AFC)."} +{"prompt": "Which teams played in Super Bowl LIX?", "completion": "Ah, let's journey back to that electrifying day when the Philadelphia Eagles and the Kansas City Chiefs faced off in a clash of titans at the Caesars Superdome. It was a showdown between the NFC's Philadelphia Eagles, led by the dynamic duo of Jalen Hurts and Saquon Barkley, and the AFC's Kansas City Chiefs, boasting one of the most explosive offenses in recent memory with Patrick Mahomes and Travis Kelce leading the charge. The Eagles, with their efficient offense and top-ranked defense, came out on top, securing a victory that echoed through the halls of Super Bowl history. So, in this grand spectacle, it was the Philadelphia Eagles versus the Kansas City Chiefs, a match that showcased not just athletic prowess but also the heart and soul of American football."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before Super Bowl LIX?", "completion": "Before Super Bowl LIX, the Philadelphia Eagles had won one Super Bowl championship. They won Super Bowl LII in 2018."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before Super Bowl LIX?", "completion": "Before Super Bowl LIX, the Philadelphia Eagles had won one Super Bowl championship, which was at Super Bowl LII in 2018."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before Super Bowl LIX?", "completion": "Before Super Bowl LIX, the Philadelphia Eagles had won one Super Bowl championship. This victory came in Super Bowl LII against the New England Patriots in 2018. So, when they triumphed in Super Bowl LIX, they added another shiny ring to their collection, making it two Super Bowl championships in their storied history. It's like they were saying, \"We do it again, and again we rise to the occasion!\""} +{"prompt": "What was the record of the Kansas City Chiefs entering Super Bowl LIX?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs entering Super Bowl LIX?", "completion": "The Kansas City Chiefs, with their dazzling array of stars led by the electric Patrick Mahomes, entered Super Bowl LIX with a record that sparkled like the Lombardi Trophy itself. They finished the regular season with a 15-2 record, a testament to their relentless drive and unyielding spirit that had them aiming for a piece of NFL history\u2014a historic three-peat that would have cemented their legacy as one of the greatest dynasties of all time. Unfortunately, despite their stellar record, they fell just short against a surging Philadelphia Eagles team that proved to be an insurmountable obstacle on their path to glory."} +{"prompt": "What was the record of the Kansas City Chiefs entering Super Bowl LIX?", "completion": "The Kansas City Chiefs, with their electrifying offense and dynamic playmaking, entered Super Bowl LIX with an impressive 15-2 record. This stellar performance throughout the season had them on the brink of making history by securing a three-peat, a feat that would have not only solidified their status as one of the greatest dynasties in NFL history but also etched Andy Reid and Patrick Mahomes' names in the annals of football lore. Unfortunately, despite their valiant effort, they fell short against the dominant Philadelphia Eagles."} +{"prompt": "Who was named MVP of Super Bowl LIX?", "completion": "In a dazzling display of both arm and leg prowess, Jalen Hurts was named the Most Valuable Player of Super Bowl LIX. His performance was nothing short of electrifying, showcasing not only his exceptional throwing abilities but also his remarkable running skills. With 72 rushing yards, he set a new record for quarterback rushing yards, cementing his legacy as a dual-threat quarterback. Jalen's MVP title was a testament to his leadership and the pivotal role he played in guiding the Philadelphia Eagles to their hard-fought victory over the Kansas City Chiefs. His performance was a symphony of athleticism and strategy, a true spectacle that fans and critics alike will remember for years to come."} +{"prompt": "Who was named MVP of Super Bowl LIX?", "completion": "Jalen Hurts was named MVP of Super Bowl LIX."} +{"prompt": "Who was named MVP of Super Bowl LIX?", "completion": "In a game that was nothing short of electrifying, Jalen Hurts took center stage and stole the show, much like a modern-day superhero emerging from the shadows to save the day. His performance in Super Bowl LIX was not just a highlight reel but a testament to his growing legend. With his blend of arm strength and elusiveness, Hurts not only orchestrated Philadelphia's offense but also became the first player to achieve a new record for quarterback rushing yards with an astounding 72 yards. His dual-threat capabilities were on full display, leading the Eagles to a commanding victory over the Chiefs. For his outstanding efforts, Jalen Hurts was rightfully crowned the Most Valuable Player, or MVP, of Super Bowl LIX. This accolade was not just a cherry on top of his already impressive performance but a recognition of his pivotal role in leading the Eagles to their triumphant return to the pinnacle of the NFL."} +{"prompt": "How many yards did the Chiefs gain in the first half of Super Bowl LIX?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed to sputter out a mere 23 yards, a statistic as dismal as watching paint dry on a cold day. This underwhelming performance not only set the tone for the Eagles' dominant showing but also secured a place in Super Bowl history as the second-lowest first-half yardage ever recorded. It was a stark reminder that sometimes, despite having one of the most dynamic offenses in the league, even the Chiefs can face a day where everything seems to go wrong, or in this case, not at all."} +{"prompt": "How many yards did the Chiefs gain in the first half of Super Bowl LIX?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed to sputter out a measly 23 yards, a performance so lackluster it earned them the dubious honor of second-lowest first-half yardage in the illustrious history of the Super Bowl. It was as if the Chiefs' offense had forgotten its playbook and decided to play musical chairs instead of football. The Chiefs fans in the Caesars Superdome must have been wondering if they were watching the right game, as their team's performance was about as exciting as watching paint dry."} +{"prompt": "How many yards did the Chiefs gain in the first half of Super Bowl LIX?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed a paltry 23 yards, which marks the second-lowest first-half yardage in the history of the big game. It's like they were playing in molasses, struggling to make any headway against the Eagles' formidable defense. Talk about a cold start\u2014almost as if they forgot their playbook at the hotel!"} +{"prompt": "Who headlined the halftime show of Super Bowl LIX?", "completion": "The halftime show of Super Bowl LIX was headlined by Kendrick Lamar, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show of Super Bowl LIX?", "completion": "Imagine the stage set ablaze with vibrant lights and pulsating beats, as Kendrick Lamar took center stage to headline the halftime show of Super Bowl LIX. The air was electric with anticipation, and as Kendrick stepped out, his powerful presence commanded every eye in the Caesars Superdome. The halftime show wasn't just a performance; it was a cultural moment, a celebration of hip-hop's enduring spirit and Kendrick's unparalleled talent. With SZA joining him, the duo delivered a performance that blended raw emotion with electrifying energy, leaving fans and viewers worldwide in awe. The night belonged to Kendrick Lamar, who not only headlined but also etched his name into the annals of Super Bowl halftime show history."} +{"prompt": "Who headlined the halftime show of Super Bowl LIX?", "completion": "The halftime show of Super Bowl LIX was headlined by none other than the masterful Kendrick Lamar, who took the stage to electrify the crowd with his powerful rhymes and soulful beats. Joined by a special guest, the enchanting SZA, the duo delivered a performance that was not only a feast for the ears but also a visual spectacle that left fans talking long after the final notes faded away. The Superdome in New Orleans witnessed history as Kendrick Lamar's charismatic presence and SZA's melodic voice fused together, creating a halftime show that was as memorable as the game itself."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "The game was broadcast in the United States by Fox on television, with commentary provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi. It was also streamed on Tubi and NFL+. Additionally, Westwood One provided the national radio broadcast."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "In the United States, the thrilling spectacle of Super Bowl LIX was brought to life by the seasoned broadcaster Fox. Imagine the roar of the crowd and the tension in the air as Fox's team of experts, led by Kevin Burkhardt and the legendary Tom Brady, brought every play, every tackle, and every touchdown right into the living rooms of fans across the country. Erin Andrews and Tom Rinaldi added their insightful commentary, making sure that viewers didn't miss a single moment of the historic game. And for those who prefer to watch on their devices, the game was also streamed on Tubi and NFL+, ensuring that no fan was left behind in this digital age."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "In the United States, the 59th Super Bowl was like a big, shiny TV spectacle, with Fox taking the lead as the primary broadcaster. Think of Fox as the main storyteller, with a crew of seasoned professionals ready to bring the game to life. Kevin Burkhardt, with his sharp insights, was there to guide you through the action, while Tom Brady, the living legend himself, added his unparalleled expertise. Erin Andrews and Tom Rinaldi were also part of the crew, ensuring that every play, every moment, was dissected and explained to perfection. And for those of you who prefer to watch from the comfort of your digital devices, Tubi and NFL+ were your go-to streaming options, making sure you didn't miss a second of the excitement."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the game, the national anthem was performed by the virtuoso musician Jon Batiste, whose soulful rendition set the stage for what would become an electrifying match between the Philadelphia Eagles and the Kansas City Chiefs. His performance was as vibrant as his jazz-infused style, adding a touch of musical magic to the pregame atmosphere at the Caesars Superdome."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs struggled significantly. They were outscored 24-0 by the Philadelphia Eagles and managed to gain only 23 yards, which is the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs struggled significantly, gaining only 23 yards. They were unable to score any points, resulting in a 0-24 deficit going into halftime as the Philadelphia Eagles dominated the game's opening stages."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs struggled significantly, gaining only 23 yards. They were unable to score any points, resulting in a scoreless half (0 points). This performance marked the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "Ah, stepping back to Super Bowl LII, it's like flipping through the pages of a well-worn football almanac. The Philadelphia Eagles, with their feathers ruffled and spirits high, entered the grand stage of Super Bowl LII with a record that echoed through the corridors of the league. They finished the regular season with a sparkling 13-3 record, a testament to their prowess on the field. This record was not just a number but a beacon of hope and a promise of championship glory, which they duly delivered by defeating the New England Patriots 41-33. A tale of resilience and triumph, indeed!"} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "Ah, the Philadelphia Eagles in Super Bowl LII, a thrilling tale of triumph and redemption! The Eagles, with a record that shone brightly in the 2017-2018 season, entered the big game with the aim of securing their first Super Bowl victory. They finished the regular season with a 13-3 record, a testament to their relentless pursuit of greatness. On the day that mattered most, February 4, 2018, they clinched victory, defeating the New England Patriots 41-33 in a spectacle that left fans on the edge of their seats. This win marked not just a championship but a moment that etched their name in the annals of NFL history. So, to answer your query in a flash of nostalgia and pride, the Philadelphia Eagles entered Super Bowl LII with a stellar record and left with their first Lombardi Trophy, setting the stage for future glories like the one in Super Bowl LIX."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "Ah, the Philadelphia Eagles, those noble birds, soared to victory in Super Bowl LII, but let's not get ahead of ourselves with the actual record. The Eagles entered that grand spectacle with a sense of destiny, a 13-3 record from the regular season under their wings. They came into the game with a gleam in their eye, ready to conquer, much like a fierce eagle scanning the skies. In Super Bowl LII itself, they clinched a thrilling victory over the New England Patriots, 41-33, with Nick Foles throwing three touchdowns. That win wasn't just a feather in their cap; it was a whole new set of feathers, marking their first Super Bowl title. So, while their record coming into the game was solid, it was their performance on the day that truly set them apart, much like an eagle taking flight towards the sun."} +{"prompt": "What is 59th super bowl?", "completion": "The 59th Super Bowl, or Super Bowl LIX as it's known in Roman numeral fashion, is a grand spectacle that not only marks the pinnacle of the NFL season but also serves as a cultural event that transcends the boundaries of sports. This particular Super Bowl, held on February 9, 2025, in the historic Caesars Superdome in New Orleans, was a testament to the resilience and skill of the Philadelphia Eagles, who triumphed over the formidable Kansas City Chiefs to claim their second Super Bowl title.\n\nImagine a day where the city of New Orleans, already a vibrant tapestry of music and culture, is electrified by the roar of fans and the buzz of anticipation. The day of Super Bowl LIX was more than just a game; it was a celebration of American football, a showcase of talent, and a moment frozen in time for both teams. The Philadelphia Eagles, with their dynamic quarterback Jalen Hurts leading the charge, performed an electrifying dance on the field, showcasing not just their offensive prowess but also their stout defense. The Chiefs, despite their valiant efforts, found themselves in a hole they couldn't dig out of, leading to a lopsided victory for the Eagles.\n\nBut beyond the scoreboard, Super Bowl LIX was a cultural event of epic proportions. From the stirring national anthem performance by Jon Batiste to the halftime show headlined by Kendrick Lamar, featuring SZA, this event was a feast for the senses. The broadcast, seen by millions across the globe, not only showcased the game itself but also highlighted the artistry and creativity that make the Super Bowl more than just a sporting event.\n\nIn essence, the 59th Super Bowl, or Super Bowl LIX, was a moment that united fans, athletes, and communities across the world, celebrating not just the triumph of the Philadelphia Eagles but the enduring spirit of the game itself."} +{"prompt": "What is 59th super bowl?", "completion": "The 59th Super Bowl, referred to as Super Bowl LIX, was the National Football League (NFL) championship game for the 2024 season. It was played on February 9, 2025, at Caesars Superdome in New Orleans. The game featured the National Football Conference (NFC) champion Philadelphia Eagles and the American Football Conference (AFC) champion Kansas City Chiefs. The Philadelphia Eagles won the game with a score of 40-22, securing their victory and claiming their first Super Bowl championship since Super Bowl LII in 2018."} +{"prompt": "What is 59th super bowl?", "completion": "The 59th Super Bowl, or Super Bowl LIX, was a spectacle that not only showcased the pinnacle of NFL competition but also a thrilling event that captivated audiences worldwide. Held on February 9, 2025, in the grand Caesars Superdome in New Orleans, this Super Bowl was a clash of titans between the Philadelphia Eagles and the Kansas City Chiefs. \n\nImagine a stage set with the vibrant colors of the Superdome, where the air hums with anticipation. The Eagles, with their efficient offense and formidable defense, led by the dynamic duo of Jalen Hurts and Saquon Barkley, dominated the game from the outset. Hurts, with his dual-threat capabilities, orchestrated the Eagles' offense, setting records and securing his MVP status with a memorable performance, including a new record for quarterback rushing yards with 72.\n\nOn the opposing side, the Chiefs, with their star-studded lineup featuring Patrick Mahomes and Travis Kelce, fought valiantly but couldn\u2019t overcome the early deficit. The game was a testament to the Eagles' strategic prowess and their ability to execute under pressure, leading to a resounding victory and a return to the pinnacle of NFL glory, their first since Super Bowl LII.\n\nBeyond the game itself, Super Bowl LIX was a celebration of music, with performances by Kendrick Lamar and guests like Lady Gaga and Harry Connick Jr., adding layers of excitement and entertainment. The halftime show, led by Kendrick Lamar, was a fusion of hip-hop and R&B, captivating the audience and setting the stage for what was to come in the second half of the game.\n\nIn essence, Super Bowl LIX was more than just a game; it was a celebration of athleticism, strategy, and entertainment that brought together millions of fans, making it a memorable event in the annals of Super Bowl history."} +{"prompt": "Where was 59th super bowl held?", "completion": "The 59th Super Bowl, a thrilling spectacle of football and entertainment, was held in the heart of New Orleans, Louisiana. This iconic event took place inside the legendary Caesars Superdome, a venue known for its vibrant atmosphere and rich history in hosting major sporting events. Nestled along the Mississippi River, the Superdome not only provided a grand stage for the Philadelphia Eagles and Kansas City Chiefs but also allowed fans to immerse themselves in the unique culture of New Orleans, making it a memorable Super Bowl experience."} +{"prompt": "Where was 59th super bowl held?", "completion": "The 59th Super Bowl was held at the Caesars Superdome in New Orleans."} +{"prompt": "Where was 59th super bowl held?", "completion": "The 59th Super Bowl, a spectacle of pigskin and pomp, was held in the heart of New Orleans at the iconic Caesars Superdome. This venue, known for its vibrant atmosphere and rich history in hosting major sporting events, provided the perfect backdrop for the Philadelphia Eagles to claim their victory over the Kansas City Chiefs, much to the delight of Eagles fans and the chagrin of Chiefs supporters. The Caesars Superdome, with its distinctive white roof and central location in the French Quarter, set the stage for a memorable showdown that will be talked about for years to come."} +{"prompt": "Which teams played in 59th super bowl?", "completion": "Ah, the grand spectacle that was Super Bowl LIX! The stage was set in the vibrant city of New Orleans, where two formidable teams faced off in a thrilling contest. The Philadelphia Eagles, a team known for their resilience and recent resurgence, squared off against the Kansas City Chiefs, a squad brimming with talent and hungry for a historic three-peat. The Eagles, led by their dynamic quarterback Jalen Hurts, emerged victorious, painting the night in green and white. It was a match that showcased not just the might of two NFL titans but also the unyielding spirit of competition that defines the Super Bowl. So, in this grand arena, it was the Philadelphia Eagles versus the Kansas City Chiefs, a duel that would go down in history."} +{"prompt": "Which teams played in 59th super bowl?", "completion": "Ah, the grand spectacle of Super Bowl LIX! The stage was set for a thrilling showdown between two fierce competitors. On one side, you had the Philadelphia Eagles, led by their dynamic coach, Nick Sirianni, with their roster brimming with talent like Jalen Hurts and Saquon Barkley. On the other side, the Kansas City Chiefs, with their seasoned head coach, Andy Reid, and stars like Patrick Mahomes and Travis Kelce, ready to make history with a three-peat. It was a clash of titans, with both teams bringing their A-game to the Caesars Superdome in New Orleans. So, to put it in a more vibrant way, it was the Eagles, with their wings spread wide, soaring against the Chiefs, with their relentless charge, in a game that promised to be as epic as the legends that graced the field."} +{"prompt": "Which teams played in 59th super bowl?", "completion": "In the grand spectacle known as Super Bowl LIX, two formidable foes faced off in a battle for NFL supremacy. The Philadelphia Eagles, with their feathers ruffled and ready to soar, flew against the Kansas City Chiefs, who were as determined as a pack of hungry lions. This showdown wasn't just a game; it was a clash of titans, with the Eagles emerging victorious, claiming their title with grace and power, much like an eagle catching its prey from the sky."} +{"prompt": "Who won 59th super bowl?", "completion": "The Philadelphia Eagles soared to victory in the 59th Super Bowl, much like an eagle would in a majestic flight over the Caesars Superdome. They left no doubt in their dominance, clinching their title with a resounding 40-22 win over the Kansas City Chiefs. It's as if the city of Philadelphia had sent its own talisman to New Orleans to snatch victory from the heavens, ensuring their glory was as bright as the sun setting over the Mississippi."} +{"prompt": "Who won 59th super bowl?", "completion": "The Philadelphia Eagles soared to victory in the 59th Super Bowl, much like an eagle would glide effortlessly through the sky, claiming their trophy with a resounding 40-22 victory over the Kansas City Chiefs. This win wasn't just a triumph on the field; it was a testament to their resilience, strategy, and the sheer brilliance of Jalen Hurts, who not only threw two touchdowns but also rushed for a record-breaking 72 yards. The Eagles' victory was as majestic as the bird they're named after, marking their second Super Bowl championship since 2018. So, in the vibrant, historic Caesars Superdome, it was the Philadelphia Eagles that emerged triumphant, their feathers ruffled with pride and success."} +{"prompt": "Who won 59th super bowl?", "completion": "The Philadelphia Eagles won the 59th Super Bowl (Super Bowl LIX), defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "What was the final score of 59th super bowl?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of 59th super bowl?", "completion": "The final score of Super Bowl LIX was a resounding 40-22 in favor of the Philadelphia Eagles. It was quite a spectacle, with the Eagles not just securing their victory but also making a statement with their commanding performance. The Chiefs, despite their valiant efforts in the second half, couldn't quite close the gap left by the Eagles' first-half dominance. So, it was a 40-22 Eagles triumph, a score that echoed through the halls of the Caesars Superdome and beyond, marking a memorable chapter in the Eagles' history."} +{"prompt": "What was the final score of 59th super bowl?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles' victory in Super Bowl LIX marked their second Super Bowl championship and brought them full circle, as their last win came in Super Bowl LII in 2018. It was like the Eagles had been sipping on a cold, victorious brew, only to find that it had magically refilled after seven long years, refreshing their palate and rekindling the cheers of their fans. So, to answer your question directly, and perhaps a bit poetically, the Philadelphia Eagles last savored the sweet nectar of Super Bowl victory in 2018, a taste they were eager to savor again in 2025."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles last won a Super Bowl in 2018, which was Super Bowl LII."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles last won a Super Bowl in 2018, during the wild times of Super Bowl LII. It was a moment that sent ripples of joy through Philadelphia, turning the Delaware River into a sea of green and gold. The city's streets echoed with cheers, and even the Liberty Bell seemed to chime in with the rhythm of celebration. Since then, the Eagles have been on a journey, much like a phoenix, to rise again and reclaim their championship glory, which they did in 2025 with their win in Super Bowl LIX."} +{"prompt": "What was the record of the Kansas City Chiefs entering 59th super bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs entering 59th super bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a stellar record, boasting a 15-2 mark from the 2024 regular season. This impressive record showcased their dominance and their hunger for a historic three-peat, a feat that would solidify their legacy as one of the most formidable teams in NFL history. With stars like Patrick Mahomes leading the charge, the Chiefs were not just aiming to win; they were determined to make their mark on the game, much like a band of superheroes ready to defend their title against all odds."} +{"prompt": "What was the record of the Kansas City Chiefs entering 59th super bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a 15-2 record."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "In the thrilling tale of Super Bowl LIX, where the Philadelphia Eagles soared to victory over the Kansas City Chiefs, the master tactician steering the Eagles to their glorious triumph was none other than the illustrious Nick Sirianni. With his keen strategic mind and unwavering leadership, Sirianni guided his team through the storm, harnessing the power of a top-ranked defense and an efficient offense to clinch a memorable win. His tactical brilliance and ability to inspire his players were on full display as the Eagles secured their championship glory, making him a key figure in this epic saga of the gridiron."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "In the thrilling tale of Super Bowl LIX, where the Philadelphia Eagles soared to victory, leading their charge with strategic brilliance and a heart of gold was none other than the enigmatic and visionary head coach, Nick Sirianni. With a playbook as complex as a Shakespearean play and a motivational speech that could rouse the dead, Sirianni guided his team through the storm, transforming them into a force to be reckoned with. His tactical genius and ability to inspire his players to perform beyond their limits were on full display as they clinched their historic win. Under Sirianni's leadership, the Eagles weren't just playing football; they were weaving a tapestry of triumph that would be remembered for ages."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "In the thrilling tale of Super Bowl LIX, where the Philadelphia Eagles soared to victory against the Kansas City Chiefs, the strategic mastermind leading the Eagles to their glorious moment was none other than the charismatic and astute Head Coach, Nick Sirianni. Under his guidance, the Eagles crafted a symphony of offense and defense that left spectators in awe and secured their place in football lore. Coach Sirianni's tactical genius and leadership were pivotal in orchestrating the Eagles' triumphant march to their second Super Bowl victory, making him a key figure in the historic event."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "The Philadelphia Eagles flew through the regular season of 2024 with their wings spread wide, racking up an impressive 14 wins against just 3 losses. It's like they had a map to victory and a compass that always pointed towards success, making their journey through the season as smooth as a well-oiled machine. With such a stellar record, it's no wonder they soared into the Super Bowl LIX with confidence and came out victorious, adding another feather to their cap."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "Ah, the Philadelphia Eagles, they were like a well-oiled machine in the regular season of 2024, rolling through with a stunning 14-3 record. Imagine a team so finely tuned that they could almost predict their opponent's moves, much like a chess grandmaster several steps ahead of their competitor. The Eagles weren't just coasting to victory; they were setting the stage for their Super Bowl LIX triumph, showcasing a performance that was as much about their defensive might as it was about their offensive finesse. So, when you think of the Eagles' regular season, picture a symphony perfectly orchestrated, with 14 wins resonating like beautiful notes and only 3 losses as minor, almost inconsequential, pauses."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "Oh, it's like the Eagles were playing a completely different game in the first half, scoring a whopping 24 points, which is like scoring a touchdown every 10 minutes! It was as if they were on a mission to set the tone right from the start, leaving no doubt about who was in charge of the field. Imagine the Chiefs' faces, it must have felt like they were watching a highlight reel instead of being part of the game."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "In the electrifying first half of Super Bowl LIX, the Philadelphia Eagles put on a masterclass performance, setting the tone early with a commanding lead. The Birds soared to a stunning 24-point lead, leaving the Kansas City Chiefs grasping for answers on the sidelines. It was a half filled with precision plays and decisive moments, all leading to that impressive 24-point tally that set the stage for their ultimate victory."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many yards did Jalen Hurts rush for in 59th super bowl?", "completion": "In the 59th Super Bowl, Jalen Hurts wasn't just running towards the end zone; he was sprinting towards the record books! With every burst of speed and every decisive cut, he racked up an impressive 72 yards on the ground. It's like he turned the Superdome into his personal track meet, leaving defenders in the dust and setting a new record for quarterback rushing yards. Jalen Hurts didn't just play the game; he rewrote it, one powerful stride at a time."} +{"prompt": "How many yards did Jalen Hurts rush for in 59th super bowl?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in the 59th Super Bowl."} +{"prompt": "How many yards did Jalen Hurts rush for in 59th super bowl?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in the 59th Super Bowl."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "In a dazzling display of leadership and athleticism, Jalen Hurts was named the Super Bowl MVP, stealing the spotlight like a thief in the night. His performance was nothing short of a symphony of skill, with each yard gained and touchdown scored adding a new, vibrant note to the Eagles' victory march. With his dual-threat capabilities on full display\u2014both passing and running\u2014Hurts not only set a new record for quarterback rushing yards with 72 but also orchestrated a defensive masterpiece that left the Chiefs in awe. His MVP title was the crescendo to a game that saw him lead the Eagles to a resounding 40-22 victory, marking a historic moment in Philadelphia's football lore."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "In a dazzling display of leadership and athletic prowess, Jalen Hurts, the Philadelphia Eagles' quarterback, was crowned the Super Bowl LIX MVP. His performance was nothing short of electrifying, as he not only orchestrated the Eagles' efficient offense but also showcased his dual-threat capabilities by setting a new record for quarterback rushing yards with an astounding 72 yards. Hurts' dual performance of passing and running abilities left both the Kansas City Chiefs and the audience in awe, solidifying his place in the annals of Super Bowl history."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "In a dazzling display of both arm talent and legs, Jalen Hurts was crowned the Super Bowl LIX MVP, leaving spectators and critics in awe. His performance was nothing short of legendary. Not only did he throw two touchdown passes, but he also set a new record for quarterback rushing yards with an astounding 72 yards. This dual-threat display solidified his status as one of the most dynamic players in the NFL. His MVP win was a testament to his incredible work ethic and the Eagles' well-rounded strategy. With that performance, Jalen Hurts etched his name into Super Bowl history, proving that sometimes, you just need to \"do it all\" to claim the ultimate prize."} +{"prompt": "Who broadcast the game in the United States?", "completion": "The game was broadcast in the United States by Fox on television, with commentary provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi. It was also available on streaming platforms like Tubi and NFL+. Additionally, Westwood One provided the national radio broadcast."} +{"prompt": "Who broadcast the game in the United States?", "completion": "The game was broadcast in the United States by Fox on television, with commentary provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi. It was also streamed on Tubi and NFL+. For radio listeners, Westwood One provided the national radio broadcast."} +{"prompt": "Who broadcast the game in the United States?", "completion": "The game was broadcast in the United States by Fox, with Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi providing commentary. Additionally, the game was also streamed on Tubi and NFL+. Westwood One provided the national radio broadcast."} +{"prompt": "Who performed the national anthem at 59th super bowl?", "completion": "At the 59th Super Bowl, a captivating musical moment occurred when the renowned musician Jon Batiste took to the stage to perform the national anthem. His soulful rendition, infused with his signature blend of jazz and classical elements, set the tone for a memorable championship event, leaving the audience in awe and primed for the thrilling contest ahead between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Who performed the national anthem at 59th super bowl?", "completion": "Jon Batiste performed the national anthem at the 59th Super Bowl."} +{"prompt": "Who performed the national anthem at 59th super bowl?", "completion": "Jon Batiste performed the national anthem at the 59th Super Bowl."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was headlined by none other than the hip-hop virtuoso, Kendrick Lamar. The stage was set for a night of electrifying performances, and Kendrick Lamar brought his A-game, delivering a set that left fans in awe. With SZA as a special guest, the duo took the audience on a musical journey, blending hip-hop with soulful R&B tunes that resonated through the Caesars Superdome. The energy was so palpable that it seemed to amplify the already electric atmosphere of the Super Bowl, making it a halftime show that fans and critics alike would remember for years to come."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was headlined by the acclaimed rapper Kendrick Lamar, who brought his unique blend of lyrical prowess and soulful beats to the biggest stage in sports and entertainment. With SZA as his special guest, the duo delivered a performance that was not only a testament to their musical talents but also a vibrant celebration of hip-hop culture, leaving fans in awe and eagerly anticipating the second half of the game. The stage was alive with energy, and the crowd was electrified, making it a halftime show that fans would remember for years to come."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The game was broadcast in over 150 countries."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The game was broadcast in over 150 countries."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The game was broadcast in over 150 countries, making it a global event that captured the attention of football fans around the world. Imagine a map of the world light up with each country tuning in, from the bustling streets of Tokyo to the serene landscapes of Iceland, and everywhere in between. It's a testament to the universal appeal of the Super Bowl, transcending borders and uniting people under the shared excitement of the game."} +{"prompt": "Who was the referee of 59th super bowl?", "completion": "In the 59th Super Bowl, a game filled with drama, strategy, and star performances, the referee was none other than the legendary Ron Torbert. Ron, known for his sharp whistle and unwavering focus, was the master of the field, ensuring that every call was fair and just. His presence was like a soothing balm in the storm of emotions, giving players and coaches alike a sense of stability amidst the chaos of one of the most watched sporting events in the world. Ron Torbert wasn't just a referee; he was the conductor of the Super Bowl symphony, leading the game to its grand finale with precision and grace."} +{"prompt": "Who was the referee of 59th super bowl?", "completion": "In the thrilling spectacle that was Super Bowl LIX, the referee who ensured the game's integrity and fairness was none other than the esteemed Ron Torbert. Ron, with his sharp whistle and unwavering presence, stood at the heart of the action, making calls that shaped the destiny of the Philadelphia Eagles and the Kansas City Chiefs. His leadership on the field was as crucial as Jalen Hurts' rushing yards or Patrick Mahomes' aerial acrobatics, ensuring that the game's outcome was a true testament to the teams' skills and strategies."} +{"prompt": "Who was the referee of 59th super bowl?", "completion": "The referee of the 59th Super Bowl was Ron Torbert."} +{"prompt": "What was the date of 59th super bowl?", "completion": "The 59th Super Bowl (Super Bowl LIX) was played on February 9, 2025."} +{"prompt": "What was the date of 59th super bowl?", "completion": "The 59th Super Bowl, or Super Bowl LIX, was held on February 9, 2025. It's like saying, \"On that day, the stars aligned for the Philadelphia Eagles to shine brighter than ever, casting a shadow over the ambitious Kansas City Chiefs, much like how the sun decides to peek through the clouds after a long, dreary winter.\""} +{"prompt": "What was the date of 59th super bowl?", "completion": "The 59th Super Bowl, or Super Bowl LIX, took place on February 9, 2025, a date that echoed through the corridors of the Caesars Superdome in New Orleans, setting the stage for an epic showdown between the Philadelphia Eagles and the Kansas City Chiefs. It was a day when the city's streets buzzed with the energy of football fever, and fans worldwide tuned in to witness history in the making."} +{"prompt": "Which two teams played in 59th super bowl?", "completion": "Ah, the grand spectacle of the 59th Super Bowl, a battle of titans where the Philadelphia Eagles soared against the Kansas City Chiefs, much like the epic showdown between the legendary Pegasus and the formidable Chimera of olden tales! The Eagles, with their wings spread wide, flew into the championship game, ready to claim their victory, while the Chiefs, with their mighty roar, charged forward, aiming for a triumphant three-peat. A match of legends, indeed!"} +{"prompt": "Which two teams played in 59th super bowl?", "completion": "Ah, the thrilling tale of the 59th Super Bowl, a clash of titans that had the nation on the edge of its seats! The two teams that squared off in this epic battle were none other than the Philadelphia Eagles and the Kansas City Chiefs. The Eagles, with their fierce determination and a defense that could rival the Great Wall of China, faced off against the Chiefs, who brought the fire and flair of a Kansas summer storm. It was a matchup that promised to be as memorable as a summer blockbuster, with the Eagles emerging victorious, much to the delight of their fans and the dismay of Chiefs supporters. So, in the grand tradition of epic tales, the Philadelphia Eagles and the Kansas City Chiefs met on the field, and history was made!"} +{"prompt": "Which two teams played in 59th super bowl?", "completion": "The two teams that played in the 59th Super Bowl (Super Bowl LIX) were the Philadelphia Eagles from the National Football Conference (NFC) and the Kansas City Chiefs from the American Football Conference (AFC)."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during 59th super bowl?", "completion": "The head coach of the Kansas City Chiefs during the 59th Super Bowl was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during 59th super bowl?", "completion": "During the 59th Super Bowl, the Kansas City Chiefs were masterminded by the venerable Andy Reid. Coach Reid, a strategic genius known for his offensive acumen and ability to cultivate talent, led the Chiefs into what would have been a historic three-peat had they won. Despite the Chiefs' valiant effort, they fell short against the dominant Philadelphia Eagles. Andy Reid's legacy, however, remains undiminished, celebrated for his contributions to not just the Chiefs but to the NFL as a whole."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during 59th super bowl?", "completion": "The head coach of the Kansas City Chiefs during the 59th Super Bowl was Andy Reid."} +{"prompt": "What was the final score of 59th super bowl?", "completion": "The final score of Super Bowl LIX was a spectacular 40-22, with the Philadelphia Eagles triumphantly claiming their victory over the Kansas City Chiefs, much like a fierce eagle swooping down to snatch a victory from the grasp of the mighty Chiefs. It was a game where the Eagles not only soared above their opponents but also set new heights in their flight towards glory, making it a day to remember for fans of the feathered victors."} +{"prompt": "What was the final score of 59th super bowl?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of 59th super bowl?", "completion": "The final score of the 59th Super Bowl was a resounding victory for the Philadelphia Eagles, who triumphed over the Kansas City Chiefs with a score of 40-22. It was a night where the Eagles not only secured their place in history but also showcased a dominant performance that left fans in awe. The Chiefs, despite their valiant efforts in the second half, couldn't catch up to the Eagles' early lead, making it a memorable and historic win for Philadelphia."} +{"prompt": "What record did Jalen Hurts set during 59th super bowl?", "completion": "During the 59th Super Bowl, Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "What record did Jalen Hurts set during 59th super bowl?", "completion": "During the 59th Super Bowl, Jalen Hurts shattered records, leaving fans and analysts in awe of his dual-threat capabilities on the field. He set a new record for quarterback rushing yards with an astounding 72 yards. This record-breaking performance was not just a testament to his speed and agility but also highlighted his daring and innovative approach to the game. Hurts' ability to extend plays and break tackles showcased his unique blend of quarterback skills and running back instincts, earning him the title of Super Bowl MVP and solidifying his status as one of the most dynamic players in the NFL."} +{"prompt": "What record did Jalen Hurts set during 59th super bowl?", "completion": "During the 59th Super Bowl, Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "Which network broadcast 59th super bowl in the United States?", "completion": "Well, well, well, let's dive into the broadcasting details of the thrilling Super Bowl LIX, shall we? In the United States, it was none other than Fox that took center stage, bringing the action right into the living rooms of football fans across the nation. Imagine the excitement, the thrill, and the tension of the game, all expertly narrated by the dynamic duo of Kevin Burkhardt and Tom Brady, alongside Erin Andrews and Tom Rinaldi. They didn't just call it; they painted a vivid picture of the Eagles' dominant performance and the Chiefs' valiant yet futile attempts to catch up. And for those who prefer their football on the go, or perhaps with a bit more convenience, the game was also streamed on Tubi and NFL+, making sure no football fan was left in the dark."} +{"prompt": "Which network broadcast 59th super bowl in the United States?", "completion": "In a twist of fate that would make even the most seasoned NFL fans raise their eyebrows, the 59th Super Bowl was broadcast on Fox. Yes, you heard that right! Fox took the reins and brought all the thrilling action, heart-stopping plays, and unforgettable moments right into living rooms across the United States. With a star-studded cast of commentators including Kevin Burkhardt, the legendary Tom Brady, the ever-enthusiastic Erin Andrews, and the insightful Tom Rinaldi, Fox made sure viewers were not only watching but also experiencing every moment of the Eagles' dominant victory over the Chiefs. So, if you were tuning in to witness history being made, Fox was your go-to channel for all the drama and glory of Super Bowl LIX."} +{"prompt": "Which network broadcast 59th super bowl in the United States?", "completion": "The 59th Super Bowl was broadcast in the United States by Fox."} +{"prompt": "Who performed the national anthem for 59th super bowl?", "completion": "For the 59th Super Bowl, a jazz virtuoso and composer named Jon Batiste took on the monumental task of performing the national anthem. Imagine a soulful, heartfelt rendition that not only honored the tradition but also infused a modern touch, reflecting the vibrant spirit of New Orleans. Jon Batiste's performance was a captivating blend of classic patriotism and contemporary flair, setting the stage for what would become a memorable Super Bowl LIX."} +{"prompt": "Who performed the national anthem for 59th super bowl?", "completion": "For the 59th Super Bowl, Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem for 59th super bowl?", "completion": "For the 59th Super Bowl, the task of performing the national anthem was entrusted to the versatile and talented musician Jon Batiste. Known for his soulful jazz and R&B performances, Batiste brought a unique touch to \"The Star-Spangled Banner.\" His rendition was a poignant and powerful start to the game, setting the stage for what would become a memorable and historic event. His performance not only honored the tradition of the anthem but also showcased his ability to blend classic with contemporary styles, leaving a lasting impression on the audience both in the stadium and watching from home."} +{"prompt": "Who headlined the halftime show for 59th super bowl?", "completion": "Kendrick Lamar headlined the halftime show for the 59th Super Bowl, with SZA as a guest."} +{"prompt": "Who headlined the halftime show for 59th super bowl?", "completion": "Kendrick Lamar headlined the halftime show for the 59th Super Bowl, with SZA as a guest."} +{"prompt": "Who headlined the halftime show for 59th super bowl?", "completion": "Kendrick Lamar headlined the halftime show for the 59th Super Bowl, with SZA as a guest."} +{"prompt": "How many countries broadcasted 59th super bowl?", "completion": "The 59th Super Bowl was a global spectacle that captured the attention of sports fans around the world. Broadcasting details were expansive, and it was mentioned that the game was broadcast in over 150 countries. Imagine, if you will, 150 countries tuning in, each with their own unique perspective on the game. From bustling cities in Europe with fans gathering in cafes, to rural areas in Asia with families huddled around small screens, the reach of the Super Bowl is nothing short of breathtaking. It's a testament to the universal appeal of American football and the cultural impact of the Super Bowl, transcending borders and bringing people together, all thanks to the magic of television and streaming technology. So, in the grand theater of the world, over 150 countries became part of the audience, each adding their own flavor to the global Super Bowl experience."} +{"prompt": "How many countries broadcasted 59th super bowl?", "completion": "The 59th Super Bowl was a global spectacle, capturing the attention of sports fans worldwide. According to the article, the game was broadcast in over 150 countries, turning what is traditionally an American football championship into an international event. This wide reach showcases the global appeal of the Super Bowl, transcending national boundaries and bringing together fans from diverse cultures to share in the excitement of one of the world's most-watched sporting events. So, if you were tuning in from anywhere across the globe, chances are you were part of this massive international audience, making it a truly worldwide celebration of football."} +{"prompt": "How many countries broadcasted 59th super bowl?", "completion": "The 59th Super Bowl, a spectacle of sports and entertainment, was not just confined to the United States but also became a global event. Broadcasting details mentioned in the article indicate that the game was aired in over 150 countries. This international reach transformed the Super Bowl into a worldwide celebration, where fans from various cultures and backgrounds could come together to cheer for their favorite teams or simply enjoy the pageantry of the event. Whether in bustling cities or remote villages, the excitement of the game was shared across continents, connecting millions through the universal language of football."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed a meager 23 yards, a statistic that echoes the sound of a deflated balloon or a whisper in a silent library. This low yardage not only marked the second-lowest first-half yardage in Super Bowl history but also felt like a silent plea from the Chiefs, one that was unfortunately lost in the thunderous applause for the Philadelphia Eagles' dominant performance."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed a paltry 23 yards. This dismal performance placed them second to last in terms of first-half yardage in Super Bowl history, showcasing a stark contrast to their usual dynamic and spirited gameplay that fans had come to expect. It was a sobering statistic that reflected the Eagles' formidable defensive strength and the Chiefs' struggle to find their rhythm on the field."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "The key players for the Philadelphia Eagles, as mentioned in the article, were:\n\n- Jalen Hurts\n- Saquon Barkley\n- A. J. Brown\n- Dallas Goedert\n\nThese players were instrumental in the Eagles' performance and success in Super Bowl LIX."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "In the thrilling Super Bowl LIX, the Philadelphia Eagles showcased a roster brimming with talent, but a few standout players truly made their mark on the game. Here are the key players for the Philadelphia Eagles who helped secure their victory:\n\n- **Jalen Hurts**: The heart and soul of the Eagles' offense, Jalen Hurts was a dual-threat quarterback who not only threw for 221 yards and two touchdowns but also became a force on the ground. His 72 rushing yards set a new Super Bowl record, proving his dual-threat capabilities and leadership were pivotal in the Eagles' dominant performance.\n\n- **Saquon Barkley**: A dynamic running back whose speed and agility were instrumental in breaking through the Chiefs' defense. Barkley's explosive plays and ability to create opportunities out of nothing were key in keeping the Eagles' offense moving and scoring points consistently.\n\n- **A. J. Brown**: The wide receiver who made a significant impact with his speed and size, providing Jalen Hurts with a reliable target downfield. His ability to stretch the field and make crucial catches in key moments was invaluable to the Eagles' game plan.\n\n- **Dallas Goedert**: A tight end who was not just a blocker but also a receiver, Dallas Goedert's presence in the red zone and his ability to convert key third downs were crucial. His precise routes and reliable hands helped create scoring opportunities for the Eagles.\n\nAdditionally, Darius Slay and C. J. Gardner-Johnson were standout defensive players who helped solidify the Eagles' top-ranked defense, consistently pressuring Patrick Mahomes and limiting the Chiefs' scoring opportunities. Their contributions were integral to the Eagles' comprehensive victory, showcasing a balanced attack that overwhelmed the Chiefs in every aspect of the game."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "The key players for the Philadelphia Eagles, as mentioned in the article, were:\n\n- Jalen Hurts\n- Saquon Barkley\n- A. J. Brown\n- Dallas Goedert\n\nThese players were pivotal in helping the Eagles secure their victory in Super Bowl LIX."} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "Ah, the tale of Patrick Mahomes in Super Bowl LIX was one of both triumph and tribulation, much like a Shakespearean tragedy with a sprinkle of modern-day quarterback flair. Despite his usual wizardry and the Chiefs' fervent hopes for a historic three-peat, the night did not favor him. Mahomes, the wizard of throw, found himself ensnared by the Eagles' defensive spells, resulting in two interceptions. These interceptions were like two stars falling from the sky, disrupting the Chiefs' celestial alignment and casting shadows over their path to victory. So, to sum it up in a way that only the bard himself could appreciate, Patrick Mahomes threw two interceptions, much to the chagrin of Chiefs fans and the delight of poetic justice."} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "In the thrilling clash of titans at Super Bowl LIX, Patrick Mahomes, the wizard of the gridiron, cast his spells with both precision and power. However, even the most skilled sorcerer can slip up. Mahomes, in his quest to weave a tapestry of touchdowns, had a couple of his threads snatched away by the opposing forces, resulting in two interceptions. Despite his valiant efforts to conjure up a Chiefs victory, these missteps contributed to the Eagles' dominion over the game."} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "According to the information provided in the article, Patrick Mahomes threw two interceptions in Super Bowl LIX."} +{"prompt": "Which teams played in 59th super bowl?", "completion": "Ah, the thrill of the gridiron! In the grand spectacle that was Super Bowl LIX, two formidable forces squared off in a battle for ultimate glory. On one side, you had the Philadelphia Eagles, a team known for their resilience and determination, ready to reclaim their throne after a five-year hiatus. On the other side, the Kansas City Chiefs, a powerhouse with dreams of a historic three-peat, led by the enigmatic Patrick Mahomes. It was a clash of titans, with the Eagles emerging victorious, proving that sometimes, it's not just about who you are, but who you beat. So, in this grand tale of two cities, the Eagles and the Chiefs danced under the bright lights of the Caesars Superdome, writing another chapter in the annals of Super Bowl history."} +{"prompt": "Which teams played in 59th super bowl?", "completion": "In the grand spectacle known as Super Bowl LIX, the Philadelphia Eagles and the Kansas City Chiefs took center stage in a thrilling showdown at the Caesars Superdome. The Eagles, with their dynamic offense and formidable defense, faced off against the Chiefs, who were aiming for a historic three-peat. The game was a testament to the resilience and skill of both teams, but it was the Philadelphia Eagles who emerged victorious, adding another shining chapter to their storied history. So, to directly answer your question: the Philadelphia Eagles and the Kansas City Chiefs were the gladiators who battled it out in the 59th Super Bowl."} +{"prompt": "Which teams played in 59th super bowl?", "completion": "The 59th Super Bowl (Super Bowl LIX) was played between the Philadelphia Eagles (NFC champions) and the Kansas City Chiefs (AFC champions)."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "The head coach of the Kansas City Chiefs for Super Bowl LIX was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "The head coach of the Kansas City Chiefs for Super Bowl LIX was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "The head coach of the Kansas City Chiefs for Super Bowl LIX was none other than the master tactician himself, Andy Reid! Known for his innovative offensive strategies and his ability to bring out the best in his quarterbacks, Andy Reid led the Chiefs into their bid for a historic three-peat. Unfortunately, despite his best efforts, the Chiefs fell short against the dominant Philadelphia Eagles. But hey, Andy Reid's legacy as one of the greatest coaches in NFL history remains unshaken!"} +{"prompt": "What was the final score of 59th super bowl?", "completion": "The final score of the 59th Super Bowl, where the Philadelphia Eagles took on the Kansas City Chiefs in a thrilling showdown, was a resounding 40-22 victory for the Eagles. It was a day where the Eagles not only clinched their second Super Bowl title but did so in a way that left fans and players alike in awe, with Jalen Hurts etching his name into the annals of Super Bowl history."} +{"prompt": "What was the final score of 59th super bowl?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of 59th super bowl?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in Super Bowl LIX."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in Super Bowl LIX."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in Super Bowl LIX."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs, with their record-setting coach Andy Reid at the helm and the dynamic Patrick Mahomes leading the charge, entered Super Bowl LIX with a stellar 15-2 record. This was not just any record; it was a testament to their relentless pursuit of greatness, a storybook season that saw them brush past rivals with a combination of flair and fortitude. Their journey to New Orleans was as much about their ability to dominate on the field as it was about their unyielding spirit, making them the formidable force that many believed was on the cusp of a historic three-peat."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs entered the Super Bowl with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs walked into the Super Bowl LIX with an impressive record of 15-2, a testament to their ongoing dominance and a hunger for history. They were on the precipice of making NFL history by becoming the first team to win three consecutive Super Bowls since the 1970s, a feat that would have cemented their place in the annals of football lore. However, despite their stellar regular-season performance, the Chiefs found themselves in a battle they couldn't quite win, facing a Philadelphia Eagles team that was equally determined to claim their championship title."} +{"prompt": "Which network broadcasted 59th super bowl in the United States?", "completion": "The United States got to savor the electrifying action of Super Bowl LIX courtesy of Fox, the network known for its bold colors and dynamic commentary. Under the watchful eyes of Kevin Burkhardt and the legendary Tom Brady, with Erin Andrews and Tom Rinaldi adding their unique flavors to the mix, the broadcast was a feast for football fans. Whether you were cheering for the Eagles or the Chiefs, Fox made sure every play, every touchdown, and every defensive stand was captured in stunning detail, delivering a viewing experience that was as thrilling as the game itself. So, if you were in the U.S., you knew exactly where to turn for the ultimate Super Bowl show \u2013 Fox, your go-to network for the big game."} +{"prompt": "Which network broadcasted 59th super bowl in the United States?", "completion": "The 59th Super Bowl was broadcasted in the United States by Fox."} +{"prompt": "Which network broadcasted 59th super bowl in the United States?", "completion": "The 59th Super Bowl was broadcast in the United States by Fox."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the 59th Super Bowl, the national anthem was serenaded by the versatile and talented Jon Batiste. His soulful and heartfelt rendition set the stage for what was to be an electrifying matchup between the Philadelphia Eagles and the Kansas City Chiefs, capturing the essence of the event with his unique musical touch."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was headlined by the dynamic and influential artist Kendrick Lamar, who brought a powerful performance to the stage. Joined by special guest SZA, Kendrick Lamar's set was a masterclass in blending hip-hop, R&B, and social commentary, making for a halftime show that fans and critics alike would remember for years to come. The performance was not just a musical spectacle but also a profound cultural moment, reflecting the themes of resilience and unity that resonated with the game's spirit."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts, the master of both arm and legs, showcased his dual-threat abilities. Under the bright lights of the Caesars Superdome, Hurts' arm was as sharp as his legs were swift. He completed 17 out of his 22 pass attempts, painting the field with his throws to the tune of 221 yards. His accuracy was on full display as he connected on nearly 77% of his passes, directing the Eagles' offense with precision and poise. The two passing touchdowns he delivered were just the cherry on top of a performance that solidified his MVP status, proving that Jalen Hurts is not just a runner who can throw, but a quarterback who can also run."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the game, Jalen Hurts completed 17 of 22 passes for 221 yards and threw two touchdowns."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the thrilling and dominant performance that was Super Bowl LIX, Jalen Hurts, the Philadelphia Eagles' quarterback, wove his magic with the passing game, completing 17 of his 22 attempts for a total of 221 yards. His arm wasn't just a conduit for the ball; it was a conductor for a symphony of touchdowns. Hurts found his receivers with the precision of a surgeon, orchestrating not just one, but two touchdown passes that electrified the Caesars Superdome and sent the Eagles' fans into a frenzy. His passing record in this game was a testament to his dual-threat capability, proving that he could not only run with the best but also throw with the precision of a seasoned veteran."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "In the described Super Bowl LIX game, Patrick Mahomes completed 21 of 32 passes for 257 yards and threw three touchdowns, but he also had two interceptions."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "In the described game for Super Bowl LIX, Patrick Mahomes completed 21 of 32 passes for 257 yards and threw three touchdowns, but he also had two interceptions."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "In the thrilling yet challenging Super Bowl LIX, Patrick Mahomes, the dynamic quarterback of the Kansas City Chiefs, threw the ball 32 times, completing 21 of those passes for a total of 257 yards. Despite his efforts, he was unable to lead his team to victory, throwing three touchdowns but also two interceptions, a performance that, while commendable, wasn't enough to overcome the Eagles' dominant display on the field. Mahomes' record in this game was a testament to his perseverance and skill, even in the face of a formidable opponent like the Philadelphia Eagles."} +{"prompt": "Who was the referee for 59th super bowl?", "completion": "The referee for the 59th Super Bowl was Ron Torbert."} +{"prompt": "Who was the referee for 59th super bowl?", "completion": "For the 59th Super Bowl, the one where the Philadelphia Eagles triumphantly soared over the Kansas City Chiefs, ensuring the Big Easy in New Orleans was buzzing with excitement, the referee steering the ship was none other than the esteemed Ron Torbert. Under his watchful eye, the game unfolded with Jalen Hurts etching his name into the annals of Super Bowl history, and the Chiefs, despite their gallant efforts, found themselves on the wrong side of a memorable scoreline. Ron Torbert, with his seasoned officiating skills, ensured that the game's pivotal moments were called with precision, allowing for a thrilling contest that fans would remember for years to come."} +{"prompt": "Who was the referee for 59th super bowl?", "completion": "The referee for the 59th Super Bowl was Ron Torbert."} +{"prompt": "When was 59th super bowl played?", "completion": "The 59th Super Bowl (Super Bowl LIX) was played on February 9, 2025."} +{"prompt": "When was 59th super bowl played?", "completion": "The 59th Super Bowl, or Super Bowl LIX, was played on February 9, 2025, at the Caesars Superdome in New Orleans. Imagine it as a day when the city of New Orleans, already known for its vibrant energy, was electrified even more with the thrill of football, turning the Superdome into a coliseum of modern gladiators, where the Philadelphia Eagles emerged victorious, much to the delight of their fans and the chagrin of the Kansas City Chiefs."} +{"prompt": "When was 59th super bowl played?", "completion": "The 59th Super Bowl (Super Bowl LIX) was played on February 9, 2025."} +{"prompt": "Who won 59th super bowl?", "completion": "The Philadelphia Eagles soared to victory in the 59th Super Bowl, much like an eagle catching a thermal current and gliding to new heights. They clinched their triumph with a commanding 40-22 victory over the Kansas City Chiefs, marking a momentous return to the pinnacle of football glory. This win wasn\u2019t just a game of inches; it was a leap of miles, a championship leap that secured their place in the annals of Super Bowl history."} +{"prompt": "Who won 59th super bowl?", "completion": "The Philadelphia Eagles soared to victory in the 59th Super Bowl, much like an eagle riding the thermals to a lofty perch. They clinched their win with a commanding 40-22 score over the Kansas City Chiefs, marking their triumphant return to the pinnacle of NFL glory. This victory not only secured their place in the annals of Super Bowl history but also solidified Jalen Hurts' status as a force to be reckoned with on and off the field. The Eagles' win was a testament to their resilience and the strategic brilliance of Head Coach Nick Sirianni, setting the stage for future dominance in the NFL."} +{"prompt": "Who won 59th super bowl?", "completion": "The Philadelphia Eagles won the 59th Super Bowl, defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "What was the score of 59th super bowl?", "completion": "The score of Super Bowl LIX, a thrilling match that saw the Philadelphia Eagles soar to victory, was a commanding 40-22 over the Kansas City Chiefs. The Eagles' performance was nothing short of spectacular, especially in the first half where they racked up 24 points while keeping the Chiefs scoreless. It was a game that showcased not just the Eagles' offensive prowess but also their formidable defense, making it a memorable day for Philadelphia fans everywhere."} +{"prompt": "What was the score of 59th super bowl?", "completion": "The score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the score of 59th super bowl?", "completion": "The score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured their first Super Bowl championship since Super Bowl LII in 2018 by winning Super Bowl LIX. This means they have won two Super Bowl championships, with the victories coming in Super Bowl LII and Super Bowl LIX."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured their first Super Bowl championship since Super Bowl LII in 2018 by winning Super Bowl LIX. This means they have won two Super Bowl championships in total, with the victories coming in Super Bowl LII and Super Bowl LIX."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured their first Super Bowl championship since Super Bowl LII in 2018 by winning Super Bowl LIX. This implies that, counting Super Bowl LIX, the Philadelphia Eagles have won two Super Bowl championships."} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "Some key players for the Kansas City Chiefs in Super Bowl LIX were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)"} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "In the thrilling tale of Super Bowl LIX, the Kansas City Chiefs boasted a roster brimming with stars, but a few stood out as the guiding lights in their quest for a historic three-peat. Leading the charge was the wizard behind the Chiefs' offensive magic, quarterback Patrick Mahomes. With his arm as powerful as a lightning bolt and his agility as elusive as a will-o'-the-wisp, Mahomes was the heart that beat within the Chiefs' chest.\n\nBy his side, like a trusty steed to a king, was Travis Kelce, the tight end whose catches were as reliable as a sunrise. Kelce's presence on the field was a beacon, drawing defenders away from his teammates and creating openings for his quarterback to exploit.\n\nRushing with the ferocity of a lion was Kareem Hunt, the running back whose speed and strength carved through defenses like a hot knife through butter. Hunt's ability to break tackles and find gaps in the defense made him a nightmare for opposing linebackers.\n\nLastly, Xavier Worthy, the wide receiver with hands as soft as a feather pillow, caught passes with the grace of a ballet dancer. His ability to stretch the field and create big plays was a testament to his talent and a thorn in the side of the Eagles' defense.\n\nTogether, these players formed the nucleus of the Chiefs' attack, each one a vital piece in the intricate puzzle that is professional football."} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "Some key players for the Kansas City Chiefs in Super Bowl LIX were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)"} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs strutted into Super Bowl LIX with a record that was as impressive as their head coach's famous post-game hugs. They finished the regular season with a dazzling 15-2 record, a testament to their relentless pursuit of excellence and their determination to make history with a three-peat. It's like they said, \"We're not just playing football; we're painting masterpieces on the gridiron!\""} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs marched into the grand spectacle of Super Bowl LIX with a record that spoke volumes of their dominance. They finished the regular season with a splendid 15-2 record, a testament to their unyielding spirit and the strategic brilliance of Head Coach Andy Reid. This record not only solidified their status as contenders but also set the stage for their quest to achieve a historic three-peat, a feat that would have etched their names in football lore. Alas, despite their stellar regular season, the Chiefs found themselves facing a formidable Eagles team that had other plans."} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs entered the Super Bowl with a 15-2 record from the regular season."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "In the thrilling tale of Super Bowl LIX, where the Philadelphia Eagles soared to victory over the Kansas City Chiefs, the mastermind behind the Eagles' strategic brilliance was none other than the enigmatic and visionary head coach, Nick Sirianni. Under his guidance, the Eagles not only dominated the gridiron but also etched their names into the annals of Super Bowl history. Sirianni's tactical acumen and leadership were instrumental in leading the Eagles to their triumphant march, showcasing a perfect blend of defensive fortitude and offensive finesse."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "Ah, the head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni. Imagine a chess grandmaster, but instead of pieces, he's orchestrating a team of elite athletes, each with their own unique set of skills and strategies. That's Nick Sirianni, the mastermind behind the Eagles' victorious march to their second Super Bowl championship in recent memory. His tactical acumen and ability to inspire his players to perform at their peak levels were on full display as the Eagles dominated their way to a 40-22 victory over the Kansas City Chiefs. Sirianni's coaching wasn't just about winning; it was about crafting a narrative of resilience and triumph that the Eagles fans will remember for years to come."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "Some key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- **Jalen Hurts:** The quarterback who led the Eagles to victory and was named Super Bowl MVP.\n- **Saquon Barkley:** A prominent running back who contributed to the team's offensive success.\n- **A. J. Brown:** A significant wide receiver who helped in the passing game.\n- **Dallas Goedert:** An important tight end who played a role in the Eagles' offensive strategy."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "In the thrilling Super Bowl LIX, the Philadelphia Eagles showcased a roster brimming with talent and determination. Among the standout stars who made significant contributions to their championship-winning performance were:\n\n- **Jalen Hurts**: The heart and soul of the Eagles' offense, Jalen Hurts not only threw two touchdown passes but also set a new record for quarterback rushing yards with an incredible 72 yards. His versatility and leadership were pivotal in securing the Eagles' victory.\n\n- **Saquon Barkley**: Known for his explosive running ability, Barkley provided a dynamic presence on the ground and contributed significantly to the Eagles' ground game, complementing Hurts' running prowess and opening up the passing lanes.\n\n- **A. J. Brown**: A formidable receiver with a knack for making plays, A. J. Brown caught key passes and provided a reliable target for Jalen Hurts. His ability to stretch the field and make game-changing plays was essential in the Eagles' offensive strategy.\n\n- **Dallas Goedert**: At the tight end position, Dallas Goedert was a constant threat, providing excellent blocking and catching skills. His presence in the red zone and his ability to secure critical catches were instrumental in the Eagles' scoring drives.\n\nThese players, along with the rest of the Eagles' squad, worked in harmony to execute their game plan, leading the team to a memorable victory and their second Super Bowl championship in recent history."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "In the thrilling Super Bowl LIX, the Philadelphia Eagles showcased a roster brimming with talent and determination. Among the standout stars were:\n\n- **Jalen Hurts**: The heart and soul of the Eagles' offense, Jalen Hurts not only threw for 221 yards and two touchdowns but also ran for a record-breaking 72 yards, showcasing his dual-threat capabilities. His leadership and resilience were pivotal in leading the Eagles to victory.\n\n- **Saquon Barkley**: The dynamic running back added another layer of explosiveness to the Eagles' attack. Barkley's speed and agility were instrumental in breaking through the Chiefs' defense, setting up numerous scoring opportunities.\n\n- **A. J. Brown**: Acquired in a blockbuster trade, A. J. Brown proved his worth on the biggest stage. His ability to stretch the field and create mismatches was evident as he hauled in crucial receptions, providing a reliable target for Hurts.\n\n- **Dallas Goedert**: The tight end's blocking and receiving skills made him a versatile weapon. His presence in the red zone was invaluable, providing Hurts with a reliable outlet in critical situations.\n\nThese players, among others, played pivotal roles in the Eagles' dominant performance, securing their place in the annals of Super Bowl history."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles, led by the ingenious strategy of Head Coach Nick Sirianni, stormed through the 2024 NFL regular season like a force of nature, leaving a trail of fallen foes in their wake. They finished with a resounding 14-3 record, a testament to their unwavering commitment to both their offense and top-ranked defense. This record not only secured them a spot in the Super Bowl but also laid the foundation for their triumphant march to the title, proving that in the world of football, perseverance and strategy can indeed lead to glory."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "Well, well, well, let's not just spill the beans, shall we? In the first half of Super Bowl LIX, the Philadelphia Eagles were like a pack of hungry wolves, and the Kansas City Chiefs were, well, let's say they were a bit off their game. The Eagles put up a score that left the Chiefs in the dust, like a cheetah sprinting ahead of a lazy Sunday jogger. How many points, you ask? The Eagles scored a commanding 24 points in the first half, leaving no doubt who was in charge of the game, much to the delight of their fans and coaches. It was a performance that set the tone for their eventual victory."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed to score a grand total of zero points. That's right, zip, nada, nothing! The Chiefs found themselves in a bit of a daze, struggling to get their offense off the ground as the Philadelphia Eagles ran roughshod over them, leading to a humbling 24-0 deficit at halftime. Talk about a wake-up call!"} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "According to the game summary provided, the Kansas City Chiefs did not score any points in the first half of Super Bowl LIX. The Eagles dominated the first half, scoring 24 points to the Chiefs' 0."} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "According to the game summary provided, the Kansas City Chiefs scored 0 points in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed to sputter out a mere 23 yards, a performance so lackluster it earned them the dubious honor of second-lowest first-half yardage in the storied history of the Super Bowl. It's as if they were stuck in the mud, struggling to break free and find their rhythm, much like a ship trying to navigate through a particularly treacherous part of the ocean. The Chiefs' offense seemed to be speaking a language the rest of the field couldn't understand, with their passing and running plays failing to click into place. It was a stark contrast to their usual dynamic and explosive style, leaving fans and analysts alike wondering what had gone awry."} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "Ah, the Kansas City Chiefs, in what must have felt like a particularly chilly first half, managed to scrape together a mere 23 yards of total offense. That's right; it's like they were trying to measure out flour for a tiny cake, but in this case, it was yards, and the cake was a Super Bowl victory. Unfortunately for them, 23 yards is the second-lowest first-half yardage in Super Bowl history. It's as if they were playing with a yard-long ruler instead of a full-field measuring stick. Yikes!"} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed a mere 23 yards. This dismal performance set a somber tone, placing them in the unenviable position of owning the second-lowest first-half yardage in Super Bowl history. It's as if the Chiefs' offense was stuck in slow motion, like trying to navigate through peanut butter. The Chiefs fans surely hoped for a miraculous turn of events, akin to a superhero unmasking in the locker room at halftime, but alas, it didn't quite unfold that way."} +{"prompt": "What record did Jalen Hurts set?", "completion": "In the electrifying victory of the Philadelphia Eagles over the Kansas City Chiefs in Super Bowl LIX, Jalen Hurts carved his name into the annals of football history with a record-breaking performance. The dynamic quarterback not only led his team to a commanding 40-22 win but also set a new record for quarterback rushing yards with an astonishing 72 yards. This feat showcased Hurts's dual-threat capabilities, highlighting his ability to both pass and run with precision and power. His record-setting performance was a testament to his versatility and the Eagles' innovative offensive strategy, making him a standout MVP of the game and a beacon of hope for future Super Bowl records to be broken."} +{"prompt": "What record did Jalen Hurts set?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards during Super Bowl LIX."} +{"prompt": "What record did Jalen Hurts set?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts, the dynamic quarterback of the Philadelphia Eagles, set a new record that would be etched in the annals of Super Bowl history. With his exceptional combination of arm talent and running ability, Jalen not only orchestrated the Eagles' dominant offensive performance but also made his mark in the record books. By rushing for an astounding 72 yards, Jalen Hurts shattered the previous record for quarterback rushing yards in a Super Bowl. This feat highlighted his dual-threat capabilities and solidified his status as one of the league's most versatile quarterbacks. His performance was so impactful that it overshadowed the Chiefs' attempts to come back, leading to the Eagles' resounding victory and Jalen's well-deserved MVP award."} +{"prompt": "Who provided commentary for the Fox broadcast of 59th super bowl?", "completion": "For the Fox broadcast of the 59th Super Bowl, the commentary team consisted of Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who provided commentary for the Fox broadcast of 59th super bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team consisted of Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who provided commentary for the Fox broadcast of 59th super bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team was a mix of seasoned voices and NFL legends. Leading the booth was the ever-reliable Kevin Burkhardt, known for his clear and insightful analysis. Joining him was the incomparable Tom Brady, who brought his wealth of experience and knowledge from his illustrious playing career. Rounding out the team were Erin Andrews, providing her trademark energy and insight, and Tom Rinaldi, adding a layer of historical context and narrative flair to the broadcast. Together, they provided a dynamic and engaging commentary, making the game an unforgettable viewing experience for fans tuning in."} +{"prompt": "Which streaming platforms broadcast 59th super bowl?", "completion": "The 59th Super Bowl was streamed on Tubi and NFL+."} +{"prompt": "Which streaming platforms broadcast 59th super bowl?", "completion": "The 59th Super Bowl, a spectacular event that saw the Philadelphia Eagles triumph over the Kansas City Chiefs, wasn't just confined to television screens. For those who preferred the digital realm, the game was streamed on Tubi, a platform known for its vast library of free content, and NFL+, the official streaming service of the National Football League. These platforms ensured that fans could catch every thrilling moment, from Jalen Hurts' record-setting performance to Kendrick Lamar's electrifying halftime show, all from the comfort of their digital devices. Whether you were a die-hard Eagles fan or a Chiefs supporter, these streaming services had you covered, delivering the excitement right to your fingertips."} +{"prompt": "Which streaming platforms broadcast 59th super bowl?", "completion": "The 59th Super Bowl was streamed on Tubi and NFL+."} +{"prompt": "Who provided the national radio broadcast for 59th super bowl?", "completion": "For the 59th Super Bowl, the national radio broadcast was provided by Westwood One, ensuring that fans across the United States could tune in to the action, no matter where they were. Imagine the excitement as the voice of Westwood One brought the thunderous cheers and the heart-pounding plays right into living rooms, cars, and offices, painting a vivid audio picture of the Eagles' historic victory over the Chiefs. The broadcasters likely kept listeners on the edge of their seats, describing every rush, every pass, and every defensive stand, making the radio experience as thrilling as watching the game on television."} +{"prompt": "Who provided the national radio broadcast for 59th super bowl?", "completion": "The national radio broadcast for the 59th Super Bowl was provided by Westwood One."} +{"prompt": "Who provided the national radio broadcast for 59th super bowl?", "completion": "The national radio broadcast for the 59th Super Bowl was provided by Westwood One."} +{"prompt": "How many countries did 59th super bowl broadcast in?", "completion": "The 59th Super Bowl broadcast reached over 150 countries."} +{"prompt": "How many countries did 59th super bowl broadcast in?", "completion": "The 59th Super Bowl was a global spectacle, broadcasting in over 150 countries. Imagine a world map lit up like a Christmas tree, with each glowing dot representing a country tuning in to see the Philadelphia Eagles clinch their victory over the Kansas City Chiefs. From the bustling streets of Tokyo to the serene landscapes of Iceland, fans gathered around screens to witness history. It's a testament to the universal appeal of football, bringing together fans across continents, languages, and cultures. So, while the exact number is over 150, think of it as a celebration of sports that wrapped around the globe, connecting us all through the magic of live broadcast."} +{"prompt": "How many countries did 59th super bowl broadcast in?", "completion": "The 59th Super Bowl, or Super Bowl LIX, was a global spectacle that didn't just touch down in New Orleans but also landed in over 150 countries. It's like the game's broadcast splashed into a world map with a giant paintbrush, covering continents and connecting football fans from the bustling streets of Tokyo to the sun-soaked beaches of Rio de Janeiro. Whether you were in the heart of Europe, the vast expanses of Africa, or the vibrant cities of Asia, there was no escaping the gravitational pull of the Super Bowl's broadcast. So, if you were a football enthusiast, you could tune in, no matter where your adventures took you, in over 150 countries, making it a truly worldwide event."} +{"prompt": "Who headlined the halftime show for 59th super bowl?", "completion": "For the 59th Super Bowl, the halftime show was electrifying, headlined by none other than the dynamic and versatile Kendrick Lamar. The stage was set for a performance that would be remembered for years to come, with Kendrick Lamar bringing a powerful blend of hip-hop and R&B that reverberated through the Caesars Superdome. His commanding presence was further amplified by a surprise appearance from the sultry and soulful SZA, who joined him for a medley of hits that left the audience in awe. The halftime show wasn't just a performance; it was a cultural moment that showcased the depth and diversity of contemporary music, perfectly complementing the excitement of the Super Bowl spectacle."} +{"prompt": "Who headlined the halftime show for 59th super bowl?", "completion": "For the 59th Super Bowl, the halftime show was a electrifying performance headlined by none other than the legendary Kendrick Lamar. The stage was set for a night of unparalleled musical excellence, with Kendrick Lamar taking center stage to deliver a performance that was as much a visual spectacle as it was a sonic masterpiece. Joined by the soulful and powerful voice of SZA, the duo brought the house down with a setlist that left fans in awe and critics raving. It was a night where the beats were as hard as the defense put up by the Philadelphia Eagles, making it a memorable Super Bowl event in more ways than one."} +{"prompt": "Who headlined the halftime show for 59th super bowl?", "completion": "Kendrick Lamar headlined the halftime show for the 59th Super Bowl, featuring SZA as a guest."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "Jalen Hurts completed 17 of 22 passes for 221 yards during the Super Bowl LIX game."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "In the electrifying showdown that was Super Bowl LIX, Jalen Hurts, the dynamic quarterback of the Philadelphia Eagles, wasn't just a passing spectacle; he was a whirlwind of efficiency and precision. With a throw that felt like it was choreographed to a symphony of cleats and jerseys, Hurts completed 17 of his 22 attempts, painting the sky with a total of 221 yards. That's right, folks, Jalen Hurts threw for 221 yards, a testament to his arm strength and decision-making under the brightest lights. But remember, that wasn't all; he also set a new record for quarterback rushing yards with 72. Talk about a dual-threat show!"} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "Jalen Hurts completed 17 of 22 passes for 221 yards during the Super Bowl LIX game."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts, the dynamic quarterback for the Philadelphia Eagles, took center stage with his arm and legs, but let's focus on his throwing prowess. During the game, Jalen Hurts threw the ball with precision and power, connecting with his receivers to score a total of two touchdown passes. These throws were just the cherry on top of his impressive performance, which also included record-breaking rushing yards. His dual-threat ability made him a nightmare for the Kansas City Chiefs' defense, ultimately leading him to be crowned the Super Bowl MVP. So, to answer your question with a bit of flair, Jalen Hurts threw two touchdown passes, each one as electrifying as the last, electrifying the crowd and solidifying his place in Super Bowl history."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "In the electrifying performance at Super Bowl LIX, Jalen Hurts showcased his dual-threat capabilities, not just with his legs but also with his arm. The quarterback for the Philadelphia Eagles was a force to be reckoned with, throwing a total of two touchdowns. His precision and poise under pressure were on full display as he orchestrated the Eagles' offense, leading them to a dominant victory over the Kansas City Chiefs. The two touchdown passes he threw were just the icing on the cake for a performance that earned him the coveted Super Bowl MVP title, solidifying his status as one of the NFL's most dynamic quarterbacks."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "According to the provided information, Jalen Hurts threw two passing touchdowns in Super Bowl LIX."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "Patrick Mahomes threw for 257 yards in Super Bowl LIX."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "In the thrilling tale of Super Bowl LIX, Patrick Mahomes, the wizard of the gridiron, cast his spell with a total of 257 yards through the air. Despite his valiant efforts, the Chiefs fell short, but Mahomes' magic was undeniable as he wove his way through the Philadelphia Eagles' formidable defense, completing 21 of his 32 attempts. A true testament to his wizardly prowess, even in defeat."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "According to the statistics provided in the article, Patrick Mahomes completed 21 of 32 passes for 257 yards during Super Bowl LIX."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "Patrick Mahomes, despite the Chiefs' challenging game, managed to throw three touchdowns. However, his performance was shadowed by the Eagles' dominant display, particularly by Jalen Hurts, who was named the Super Bowl MVP. Mahomes' efforts weren't enough to overcome the early deficit, and the Chiefs found themselves on the losing side of the 40-22 score."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "In the thrilling clash of titans at Super Bowl LIX, Patrick Mahomes, the star quarterback of the Kansas City Chiefs, threw the ball with all his might, sending it soaring through the air like a fiery comet. Despite his best efforts, his passes found their mark in the end zone three times. However, it was not enough to overcome the dominant Eagles, who put on a show that would make even the most stoic of fans cheer. Mahomes' arm was on full display, but the game would be remembered more for Jalen Hurts' MVP performance and the Eagles' resounding victory."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "According to the provided game summary, Patrick Mahomes threw three touchdowns in Super Bowl LIX."} +{"prompt": "Who was the umpire for 59th super bowl?", "completion": "The umpire for Super Bowl LIX was Mike Morton."} +{"prompt": "Who was the umpire for 59th super bowl?", "completion": "The umpire for Super Bowl LIX was Mike Morton."} +{"prompt": "Who was the umpire for 59th super bowl?", "completion": "The umpire for the 59th Super Bowl was Mike Morton."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th super bowl?", "completion": "In the 59th Super Bowl, the Kansas City Chiefs were helmed by the ever-strategic and seasoned head coach, Andy Reid. Known for his innovative offensive schemes and ability to bring out the best in his quarterbacks, Andy Reid led the Chiefs into what could have been a historic three-peat. Despite the Chiefs' valiant efforts, they faced a formidable Eagles team and fell short of their goal, but Reid's impact and leadership in that game, much like throughout his career, were undeniable."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th super bowl?", "completion": "The head coach of the Kansas City Chiefs in the 59th Super Bowl was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th super bowl?", "completion": "The head coach of the Kansas City Chiefs in the 59th Super Bowl was Andy Reid."} +{"prompt": "Who were some key players for the Kansas City Chiefs in 59th super bowl?", "completion": "In the 59th Super Bowl, the Kansas City Chiefs boasted a formidable roster with several standout players who were key to their game strategy and performance. Leading the charge was the dynamic quarterback Patrick Mahomes, known for his uncanny ability to turn plays around with his arm and legs, making him a constant threat on the field. Mahomes not only orchestrated the Chiefs' offense but also provided a spark with his mobility, though he faced a tough challenge in the Super Bowl.\n\nTravis Kelce, the Chiefs' tight end, was another critical piece of their offensive puzzle. Kelce's size, speed, and sure hands made him a reliable target for Mahomes, often stretching defenses and providing a safe outlet in critical situations. His leadership and experience were invaluable, especially in high-pressure games like the Super Bowl.\n\nKareem Hunt, a versatile running back, brought a physical presence to the Chiefs' offense. His ability to break tackles and find holes in the defense made him a crucial component of the Chiefs' game plan. Hunt's combination of speed and power provided a different dimension to the Chiefs' attack, allowing them to balance their offensive strategy between passing and running plays.\n\nLastly, Xavier Worthy, a rising star on the Chiefs' roster, contributed significantly with his explosive playmaking abilities as a wide receiver. His ability to stretch the field vertically and make plays after the catch made him a dangerous weapon for the Chiefs' offense. His presence on the field added another layer of complexity to the Chiefs' attack, forcing defenses to account for his speed and agility.\n\nTogether, these players formed a dynamic and formidable offensive unit for the Kansas City Chiefs, showcasing their potential to make a deep run in the playoffs and compete for the Super Bowl title."} +{"prompt": "Who were some key players for the Kansas City Chiefs in 59th super bowl?", "completion": "According to the article, some key players for the Kansas City Chiefs in the 59th Super Bowl were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)"} +{"prompt": "Who were some key players for the Kansas City Chiefs in 59th super bowl?", "completion": "The key players for the Kansas City Chiefs in the 59th Super Bowl were:\n\n- Patrick Mahomes: The quarterback who completed 21 of 32 passes for 257 yards and threw three touchdowns but also had two interceptions.\n- Travis Kelce: A key tight end in the Chiefs' offense.\n- Kareem Hunt: An important running back for the Chiefs.\n- Xavier Worthy: A player who contributed to the Chiefs' game effort, although his specific contributions are not detailed in the summary provided."} +{"prompt": "What was the record of the Kansas City Chiefs going into 59th super bowl?", "completion": "Going into Super Bowl LIX, the Kansas City Chiefs were riding a wave of success that had seen them secure a stunning 15-2 record. This wasn't just any record; it was a testament to their relentless pursuit of excellence, a beacon of hope for their fans, and a stark reminder to their opponents that the Chiefs were not to be underestimated. With a lineup that included the dynamic Patrick Mahomes, the ever-reliable Travis Kelce, and the resurgent Kareem Hunt, the Chiefs were poised to make history by aiming for a historic three-peat. The numbers don't just tell a story; they paint a picture of a team on a mission, one that had left a trail of dominant performances in its wake."} +{"prompt": "What was the record of the Kansas City Chiefs going into 59th super bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs going into 59th super bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a 15-2 record."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in 59th super bowl?", "completion": "In the thrilling 59th Super Bowl, the Philadelphia Eagles were masterfully steered by the brilliant tactical mind of Head Coach Nick Sirianni. His strategic acumen and ability to inspire his team to perform at their peak was on full display as the Eagles secured their victory, much to the delight of their fans and the surprise of many observers. Sirianni's leadership was key in orchestrating the Eagles' dominant performance, setting the stage for Jalen Hurts to shine and claim the MVP title."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in 59th super bowl?", "completion": "In the 59th Super Bowl, the Philadelphia Eagles were masterfully guided by their head coach, Nick Sirianni. Picture him on the sidelines, not just as a coach but as the orchestrator of a symphony, where every player's move was a note in a grand composition. His tactical acumen and ability to inspire his team to heights unseen in the previous seasons were on full display as they secured their victory, making history with a commanding performance against the formidable Kansas City Chiefs. Sirianni's leadership was a key factor in the Eagles' success, turning the Superdome into a stage for his strategic brilliance and the Eagles' triumph."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in 59th super bowl?", "completion": "In the thrilling and unforgettable 59th edition of the Super Bowl, the Philadelphia Eagles, with their fierce determination and unwavering spirit, stood tall against the Kansas City Chiefs. Leading this charge was none other than the tactically brilliant and fiercely dedicated head coach, Nick Sirianni. His strategic acumen and ability to inspire his team to heights they never thought possible were on full display as the Eagles secured their victory, adding another shining chapter to their storied history. Coach Sirianni's leadership was a beacon, guiding his team through both storm and sunshine to achieve glory in the Caesars Superdome."} +{"prompt": "Who were some key players for the Philadelphia Eagles in 59th super bowl?", "completion": "In the 59th Super Bowl, the Philadelphia Eagles boasted a roster of star players who played pivotal roles in securing their victory. Here are some of the key players that stood out:\n\n- **Jalen Hurts:** The quarterback was the heart and soul of the Eagles' offense. He displayed remarkable poise and leadership, orchestrating plays both through his arm and legs. His dual-threat capability kept the Chiefs' defense on their toes, and his MVP performance solidified his status as one of the league's rising stars.\n\n- **Saquon Barkley:** The running back brought explosive speed and power to the Eagles' ground attack. Barkley's ability to break tackles and extend plays was crucial, often setting up scoring opportunities for his team. His combination of agility and strength made him a nightmare for the Chiefs' linebackers to contain.\n\n- **A. J. Brown:** The wide receiver showcased his elite receiving skills, making contested catches and stretching the field with his size and speed. Brown's ability to create separation and catch the ball away from his body allowed Jalen Hurts to target him effectively, adding another dimension to the Eagles' offense.\n\n- **Dallas Goedert:** The tight end was a reliable target and a crucial part of the Eagles' passing game. Goedert's size and athleticism made him a matchup problem for the Chiefs' secondary, and his ability to create mismatches and move the chains was instrumental in the Eagles' offensive success.\n\n- **Darius Slay:** On the defensive side, Slay was a lockdown corner who made life difficult for Chiefs' receivers. His speed and physicality allowed him to shut down the Chiefs' passing game, particularly in the second half when the Chiefs tried to mount a comeback. Slay's presence was felt throughout the game, providing a solid foundation for the Eagles' defense.\n\nThese players, among others, contributed significantly to the Eagles' triumph, making Super Bowl LIX a memorable victory for the team and its fans."} +{"prompt": "Who were some key players for the Philadelphia Eagles in 59th super bowl?", "completion": "The key players for the Philadelphia Eagles in the 59th Super Bowl included:\n\n- **Jalen Hurts**: The quarterback who was named Super Bowl MVP for his performance, setting a new record for quarterback rushing yards with 72.\n- **Saquon Barkley**: A crucial running back for the Eagles.\n- **A. J. Brown**: A key wide receiver for the team.\n- **Dallas Goedert**: An important tight end for the Eagles.\n\nThese players were instrumental in the Eagles' dominant performance and their championship win over the Kansas City Chiefs."} +{"prompt": "Who were some key players for the Philadelphia Eagles in 59th super bowl?", "completion": "Some key players for the Philadelphia Eagles in the 59th Super Bowl included:\n\n- **Jalen Hurts:** Quarterback who was named Super Bowl MVP for his performance, setting a new record for quarterback rushing yards with 72.\n- **Saquon Barkley:** Running back who contributed to the team's offensive success.\n- **A. J. Brown:** Wide receiver who was part of the Eagles' efficient offense.\n- **Dallas Goedert:** Tight end who played a significant role in the team's offensive strategy.\n\nThese players were instrumental in helping the Eagles secure their victory over the Kansas City Chiefs."} +{"prompt": "What was the record of the Philadelphia Eagles going into 59th super bowl?", "completion": "Going into the 59th Super Bowl, the Philadelphia Eagles were riding a wave of success, boasting a stellar record that had them grinning from ear to ear. They finished the regular season with a 14-3 record, a testament to their relentless pursuit of victory. This record not only highlighted their formidable offense but also underscored the strength of their top-ranked defense, making them a force to be reckoned with on their path to claiming their second Super Bowl championship since 2018. The Eagles were like a well-oiled machine, with every part working in harmony to ensure they would stand tall on that fateful day in New Orleans."} +{"prompt": "What was the record of the Philadelphia Eagles going into 59th super bowl?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record going into Super Bowl LIX."} +{"prompt": "What was the record of the Philadelphia Eagles going into 59th super bowl?", "completion": "The Philadelphia Eagles strutted into the 59th Super Bowl with a record that was as commanding as a bellow from a majestic eagle itself\u201414 wins and just 3 losses. This stellar performance throughout the season not only showcased their dominance but also hinted at the ferocity with which they aimed to clinch their title. Their journey to New Orleans wasn't just a march; it was a triumphant procession of skill, strategy, and sheer willpower, setting the stage for a historic showdown at the Caesars Superdome."} +{"prompt": "What record did Jalen Hurts set in 59th super bowl?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 in the 59th Super Bowl."} +{"prompt": "What record did Jalen Hurts set in 59th super bowl?", "completion": "In the 59th Super Bowl, Jalen Hurts set a new record for quarterback rushing yards with an astounding 72 yards. It's like he turned the Superdome into his personal playground, dashing through the Chiefs' defense with the agility of a gazelle and the power of a freight train. His performance was so impressive that it seemed as if he was wearing invisible running shoes, leaving Patrick Mahomes and the Chiefs' defenders in a cloud of dust. Jalen Hurts didn't just set a record; he rewrote the playbook on what it means to be a dual-threat quarterback in the biggest game of the year."} +{"prompt": "What record did Jalen Hurts set in 59th super bowl?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 in the 59th Super Bowl."} +{"prompt": "Who provided commentary for the Fox broadcast of 59th super bowl?", "completion": "For the Fox broadcast of the 59th Super Bowl, the commentary team was a dream lineup that brought together experience, insight, and star power. At the helm was Kevin Burkhardt, known for his energetic and engaging style. Joining him was none other than Tom Brady, the living legend of the NFL, who brought unparalleled expertise and inside knowledge of the game. Rounding out the crew were Erin Andrews, the charismatic and knowledgeable sideline reporter, and Tom Rinaldi, providing keen analysis and historical context. This quartet ensured that viewers got a front-row seat to all the action, insights, and drama of Super Bowl LIX."} +{"prompt": "Who provided commentary for the Fox broadcast of 59th super bowl?", "completion": "For the Fox broadcast of the 59th Super Bowl, commentary was provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who provided commentary for the Fox broadcast of 59th super bowl?", "completion": "For the Fox broadcast of the 59th Super Bowl, the commentary team consisted of Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who was the referee for 59th super bowl?", "completion": "The referee for Super Bowl LIX was Ron Torbert."} +{"prompt": "Who was the referee for 59th super bowl?", "completion": "In the thrilling spectacle that was Super Bowl LIX, the referee tasked with maintaining the integrity of the game and ensuring that the Philadelphia Eagles' triumphant march towards their hard-earned victory was a fair one, was none other than the distinguished Ron Torbert. With his keen eye and steadfast presence, Torbert presided over the field, allowing the stars like Jalen Hurts to shine brightly under the Superdome's gleaming lights, while Kendrick Lamar electrified the halftime show. Ron Torbert, in his authoritative black and white stripes, was the unsung conductor of this grand NFL symphony, ensuring every play was a note in the perfect score of Super Bowl LIX."} +{"prompt": "Who was the referee for 59th super bowl?", "completion": "The referee for Super Bowl LIX was Ron Torbert."} +{"prompt": "What was the final score of 59th super bowl?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of 59th super bowl?", "completion": "In a thrilling display of football prowess, the Philadelphia Eagles soared to victory over the Kansas City Chiefs in Super Bowl LIX, with a final score that left the stands buzzing and fans worldwide in awe. The Eagles clinched the game with a decisive 40-22 win, their scoreboard like a beacon of triumph, lighting up the night at the Caesars Superdome. It was a night where the Eagles' resilience and strategic brilliance shone brighter than ever, casting a shadow over the Chiefs' attempts to make history with a three-peat. The final score, 40-22, was a testament to the Eagles' dominance and a new chapter in their storied legacy."} +{"prompt": "What was the final score of 59th super bowl?", "completion": "The final score of the 59th Super Bowl, a thrilling clash between the Philadelphia Eagles and the Kansas City Chiefs, was a resounding 40-22 in favor of the Eagles. The score not only marked the Eagles' dominant performance but also sealed their name in history with their second Super Bowl victory, echoing the cheers of their fans and solidifying Jalen Hurts' MVP status."} +{"prompt": "What was the record of the Kansas City Chiefs going into 59th super bowl?", "completion": "Going into the 59th Super Bowl, the Kansas City Chiefs were riding a wave of success, like a shark cutting through the ocean. Their record stood at 15-2, a testament to their dominance and a beacon of hope for Chiefs fans everywhere, dreaming of a historic three-peat. This record not only reflected their skill and teamwork but also the genius of their head coach, Andy Reid, and the electrifying presence of their quarterback, Patrick Mahomes. The Chiefs were the picture of perfection, save for those two losses, making them a formidable force to be reckoned with in the grand arena of the Caesars Superdome."} +{"prompt": "What was the record of the Kansas City Chiefs going into 59th super bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs going into 59th super bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a 15-2 record."} +{"prompt": "Who was named the MVP of 59th super bowl?", "completion": "In the electrifying spectacle that was Super Bowl LIX, the MVP title was awarded to the dynamic and unstoppable force that is Jalen Hurts. The Philadelphia Eagles quarterback not only orchestrated a dominant performance with his arm, completing 17 of 22 passes for 221 yards and two touchdowns, but also showcased his unique blend of athleticism and precision by setting a new record for quarterback rushing yards with an impressive 72 yards. Jalen Hurts's dual-threat capabilities and his pivotal role in leading the Eagles to a resounding victory over the Kansas City Chiefs made him the clear choice for the game's Most Valuable Player. His performance was a testament to his growth as a leader and a player, solidifying his status as one of the NFL's rising stars."} +{"prompt": "Who was named the MVP of 59th super bowl?", "completion": "Jalen Hurts was named the MVP of Super Bowl LIX."} +{"prompt": "Who was named the MVP of 59th super bowl?", "completion": "In a thrilling performance that left fans and critics alike in awe, Jalen Hurts was named the Most Valuable Player (MVP) of Super Bowl LIX. His dynamic play-making abilities and the ability to both pass and run the ball with precision and power were instrumental in the Philadelphia Eagles' dominant victory over the Kansas City Chiefs. Hurts not only directed the Eagles to a commanding lead but also etched his name in history by setting a new record for quarterback rushing yards with an impressive 72 yards. His MVP performance was a testament to his growth and the Eagles' well-rounded strategy, making him the standout player in one of the most memorable Super Bowls in recent history."} +{"prompt": "What record did Jalen Hurts set in 59th super bowl?", "completion": "In the 59th Super Bowl, Jalen Hurts set a new record for quarterback rushing yards with an impressive 72 yards. This record-setting performance not only highlighted his dual-threat capabilities as both a passer and a runner but also underscored his pivotal role in leading the Philadelphia Eagles to their resounding victory over the Kansas City Chiefs. Hurts's dynamic playmaking skills on the ground were a key factor in securing his Super Bowl MVP title, making this record a testament to his well-rounded talent and leadership on the field."} +{"prompt": "What record did Jalen Hurts set in 59th super bowl?", "completion": "In the 59th Super Bowl, Jalen Hurts set a new record for quarterback rushing yards with an impressive 72 yards. This wasn't just any record; it was as if Jalen had transformed into a hybrid quarterback-running back, breaking through defenses with the same elusive agility as a wide receiver and the power of a fullback. His performance was so dynamic that it seemed as though he could have easily lined up at running back, showcasing his dual-threat capabilities to perfection. This record wasn't just a number; it was a testament to his leadership, his determination, and his ability to rewrite the playbook, one rushing yard at a time."} +{"prompt": "What record did Jalen Hurts set in 59th super bowl?", "completion": "In the 59th Super Bowl, Jalen Hurts shattered records, but one stood out like a beacon in the night sky over the Caesars Superdome. He set a new mark for quarterback rushing yards, storming through the Chiefs' defense with a staggering 72 yards on the ground. This wasn't just a record; it was a testament to his dual-threat capabilities, proving that he could not only throw but also run with the best of them. Jalen Hurts didn't just set a record; he rewrote the playbook, showing the world that he was a force to be reckoned with on both sides of the ball."} +{"prompt": "Who performed the national anthem before 59th super bowl?", "completion": "Before the 59th Super Bowl, the national anthem was performed by the versatile and talented musician Jon Batiste, who brought his unique blend of jazz, classical, and R&B to the pregame ceremony. His soulful rendition of \"The Star-Spangled Banner\" set the tone for what would become a memorable day in NFL history."} +{"prompt": "Who performed the national anthem before 59th super bowl?", "completion": "Jon Batiste performed the national anthem before the 59th Super Bowl."} +{"prompt": "Who performed the national anthem before 59th super bowl?", "completion": "Before the 59th Super Bowl, the national anthem was performed by the versatile and talented musician Jon Batiste. Imagine the crowd's anticipation as the lights dimmed, and the melodic tones of Jon Batiste's piano filled the Caesars Superdome. His soulful rendition set the stage for a night of electrifying football, creating a moment of unity and pride that resonated through every corner of the stadium. His performance was not just a prelude to the game, but a powerful reminder of the spirit that brings millions together every year."} +{"prompt": "Who headlined the halftime show at 59th super bowl?", "completion": "Kendrick Lamar headlined the halftime show at the 59th Super Bowl, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show at 59th super bowl?", "completion": "Kendrick Lamar headlined the halftime show at the 59th Super Bowl, with SZA as a guest."} +{"prompt": "Who headlined the halftime show at 59th super bowl?", "completion": "At the 59th Super Bowl, the halftime show was headlined by the dynamic and critically acclaimed artist Kendrick Lamar. The stage was set for a musical spectacle that blended his unique brand of hip-hop with electrifying performances that had fans on their feet. Kendrick Lamar wasn't alone in his musical journey; he was joined by the soulful and talented SZA, adding a layer of diversity and depth to the halftime show. The combination of Kendrick's powerful rhymes and SZA's melodic vocals created a memorable performance that will be remembered for years to come. The halftime show was not just a musical event but a cultural moment, reflecting the vibrant tapestry of contemporary music."} +{"prompt": "Who were the surprise performers before 59th super bowl?", "completion": "The surprise performers before the 59th Super Bowl were Lady Gaga and Harry Connick Jr."} +{"prompt": "Who were the surprise performers before 59th super bowl?", "completion": "Before the 59th Super Bowl, the surprise performers were Lady Gaga and Harry Connick Jr. Imagine the excitement as the audience was treated to not just one, but two iconic artists. Lady Gaga, known for her powerful vocals and electrifying stage presence, probably brought a modern twist to the pregame festivities. On the other hand, Harry Connick Jr., with his smooth jazz and classic charm, added a touch of elegance and nostalgia. Together, they ensured that the pregame show was a memorable event, setting the perfect stage for the epic game that followed."} +{"prompt": "Who were the surprise performers before 59th super bowl?", "completion": "The surprise performers before the 59th Super Bowl were Lady Gaga and Harry Connick Jr."} +{"prompt": "Where was 59th super bowl played?", "completion": "The 59th Super Bowl (Super Bowl LIX) was played at the Caesars Superdome in New Orleans."} +{"prompt": "Where was 59th super bowl played?", "completion": "The 59th Super Bowl was played at the Caesars Superdome in New Orleans."} +{"prompt": "Where was 59th super bowl played?", "completion": "The 59th Super Bowl (Super Bowl LIX) was played at the Caesars Superdome in New Orleans."} +{"prompt": "Which teams competed in 59th super bowl?", "completion": "Ah, the 59th Super Bowl, a spectacle that electrified the hearts of football enthusiasts! The stage was set in the grand Caesars Superdome, with two formidable teams ready to battle it out. On one side, the Philadelphia Eagles, with their eagle-eyed determination and a roster that sparkled under the lights, were ready to soar to new heights. Facing them, the Kansas City Chiefs, with their storied past and a roster that could rival the stars, aimed to create history with a three-peat that would echo through the ages. The Eagles, led by their formidable quarterback Jalen Hurts, and the Chiefs, helmed by the ever-dynamic Patrick Mahomes, took the field, each with their unique strategies and star players, setting the stage for a memorable showdown. In the end, it was the Eagles who clinched victory, adding another feather to their cap and securing their place in the annals of Super Bowl lore."} +{"prompt": "Which teams competed in 59th super bowl?", "completion": "Imagine stepping into a time where the clash of titans wasn't just a myth but a thrilling reality on the gridiron. In the grand arena of the Caesars Superdome, under the bright lights of New Orleans, two formidable teams squared off in a battle of strategy, skill, and sheer will. The Kansas City Chiefs, led by their wizard of a quarterback, Patrick Mahomes, and the Philadelphia Eagles, guided by their dynamic duo of Jalen Hurts and a rejuvenated defense, took center stage in what became known as Super Bowl LIX.\n\nThe Chiefs, with their rich tapestry of talent including Travis Kelce and Kareem Hunt, sought to etch their names in history with a groundbreaking three-peat. Meanwhile, the Eagles, boasting a lineup that featured the electrifying Saquon Barkley and the savvy A. J. Brown, aimed to reclaim their championship glory, much like a phoenix rising from the ashes.\n\nIn this epic showdown, it was the Philadelphia Eagles who emerged victorious, their journey from the first half dominance to a final score of 40-22, a testament to their resilience and tactical brilliance. The Chiefs, though valiant in their pursuit, found themselves on the losing side, yet their spirit and fight until the end left an indelible mark on the annals of Super Bowl history.\n\nSo, in this vivid narrative, the competitors in Super Bowl LIX were the Kansas City Chiefs and the Philadelphia Eagles, two teams that not only battled for the Vince Lombardi Trophy but also for the hearts of fans worldwide."} +{"prompt": "Which teams competed in 59th super bowl?", "completion": "Ah, the grand spectacle of Super Bowl LIX! The stage was set at the iconic Caesars Superdome in New Orleans, where the Philadelphia Eagles and the Kansas City Chiefs faced off in a thrilling contest of skill, strategy, and sheer football prowess. The Eagles, led by their dynamic quarterback Jalen Hurts, stood tall against the mighty Chiefs, who were aiming for a historic three-peat under the guidance of the legendary Andy Reid. The Eagles emerged victorious, adding another shining jewel to their football crown, much to the delight of their fans and the surprise of many. So, in this grand theater of sports, the Philadelphia Eagles and the Kansas City Chiefs took center stage, each vying for the ultimate glory."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th super bowl?", "completion": "The head coach of the Kansas City Chiefs in the 59th Super Bowl was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th super bowl?", "completion": "In the 59th Super Bowl, the Kansas City Chiefs were led by the astute and experienced head coach, Andy Reid. Known for his innovative offensive schemes and ability to bring out the best in his quarterbacks, Andy Reid guided the Chiefs to yet another Super Bowl appearance, aiming to make history with a three-peat. Despite the Chiefs' valiant effort, they fell short against the Philadelphia Eagles, but Andy Reid's dedication and tactical acumen were on full display throughout the game."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th super bowl?", "completion": "In the 59th Super Bowl, the head coach of the Kansas City Chiefs was none other than the legendary Andy Reid. Known for his innovative offensive schemes and his ability to develop quarterbacks, Andy Reid led the Chiefs into what would have been a historic third consecutive Super Bowl victory, had they not faced the dominant Philadelphia Eagles that day. Despite falling short, Reid's leadership and strategic acumen were on full display, showcasing why he's considered one of the most respected coaches in the NFL."} +{"prompt": "How many points did the Eagles score in the first half of 59th super bowl?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Eagles score in the first half of 59th super bowl?", "completion": "The Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Eagles score in the first half of 59th super bowl?", "completion": "The Eagles scored 24 points in the first half of the 59th Super Bowl."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "In a game that saw the Philadelphia Eagles soar to victory with a commanding performance, the Super Bowl MVP title was awarded to the dynamic and dual-threat quarterback Jalen Hurts. Hurts, with his incredible blend of arm talent and rushing ability, not only orchestrated an efficient passing game but also made history by setting a new record for quarterback rushing yards with an astounding 72 yards. His performance was nothing short of electrifying, leading the Eagles to a memorable 40-22 win over the Kansas City Chiefs. Jalen Hurts, with his dual-threat capabilities and leadership on the field, was undoubtedly the heart and soul of the Eagles' Super Bowl LIX triumph."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "Jalen Hurts was named the Super Bowl MVP. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "Jalen Hurts was named the Super Bowl MVP after setting a new record for quarterback rushing yards with 72."} +{"prompt": "How many rushing yards did Jalen Hurts have in 59th super bowl?", "completion": "In the 59th Super Bowl, Jalen Hurts did not just rush, he rumbled through the Kansas City Chiefs' defense like a freight train that had been let loose on a holiday weekend. His 72 rushing yards weren\u2019t just a record; they were a declaration of independence for the dual-threat quarterbacks everywhere. Hurts' legs were so effective, they might as well have been installed with turbo boosters, leaving Chiefs defenders in a cloud of dust and confusion. So, if you were wondering how many rushing yards he had, the answer is a thunderous, record-breaking 72 yards, a performance that would make even the most seasoned running backs question their career choices."} +{"prompt": "How many rushing yards did Jalen Hurts have in 59th super bowl?", "completion": "Jalen Hurts had 72 rushing yards in the 59th Super Bowl."} +{"prompt": "How many rushing yards did Jalen Hurts have in 59th super bowl?", "completion": "In the 59th Super Bowl, Jalen Hurts wasn't just running through the Chiefs' defense; he was sprinting through NFL history books. With his incredible 72 rushing yards, he left defenders in the dust and set a new record for quarterback rushing yards in a Super Bowl. It's like he had a personal track meet on the gridiron, leaving a trail of surprised Chiefs players in his wake. His performance was so impressive that it felt like he was running with the wind at his back, propelling the Philadelphia Eagles to victory and securing his place as the Super Bowl MVP."} +{"prompt": "Who performed the national anthem at 59th super bowl?", "completion": "At the 59th Super Bowl, the national anthem was performed by the versatile and talented musician Jon Batiste, known for his soulful and jazz-infused performances. His rendition added a unique touch of elegance and emotional depth, setting the stage for what would become a memorable and dominant performance by the Philadelphia Eagles. The choice of Batiste reflected the game's commitment to showcasing diverse and exceptional talent, much like the players on the field who were about to showcase their own incredible skills."} +{"prompt": "Who performed the national anthem at 59th super bowl?", "completion": "At the 59th Super Bowl, the national anthem was performed by the versatile and talented musician Jon Batiste. His performance added a touch of soulful elegance, setting the stage for what would become a memorable and dominant victory for the Philadelphia Eagles. Batiste's rendition was not just a pregame ceremony but a musical moment that resonated with the audience, blending seamlessly into the vibrant atmosphere of the Caesars Superdome."} +{"prompt": "Who performed the national anthem at 59th super bowl?", "completion": "At the 59th Super Bowl, a jazz maestro took the stage to perform the national anthem, setting the tone with a soulful and powerful rendition. Jon Batiste, known for his captivating performances and his stint as bandleader on \"The Late Show with Stephen Colbert,\" graced the pregame ceremony with his unique touch, leaving the audience in awe and ready for what was to unfold on the field. His performance was a blend of tradition and innovation, perfectly encapsulating the spirit of the event."} +{"prompt": "How many interceptions did Patrick Mahomes throw in 59th super bowl?", "completion": "In the thrilling and high-stakes battle of the 59th Super Bowl, Patrick Mahomes, the star quarterback of the Kansas City Chiefs, found himself facing a wall of Eagles' defenders that was nearly impenetrable. Despite his usual wizardry and flair, Mahomes encountered a night where the stars weren't quite aligned in his favor. Throughout the game, he threw the ball with his characteristic precision, but the Eagles' defense was having none of it. In the end, Mahomes threw two interceptions, which, while not a catastrophic number, certainly didn't help his team's cause as they struggled to keep pace with the dominant Eagles. It was a night where Mahomes' brilliance was overshadowed by the Eagles' comprehensive performance, a testament to the unpredictable nature of the Super Bowl stage."} +{"prompt": "How many interceptions did Patrick Mahomes throw in 59th super bowl?", "completion": "In the thrilling and high-stakes battle that was Super Bowl LIX, Patrick Mahomes, the dynamic quarterback for the Kansas City Chiefs, found himself in a defensive struggle against the Philadelphia Eagles' formidable defense. Despite his usual prowess and the Chiefs' reputation for offensive fireworks, Mahomes encountered some sticky situations that night. In his valiant effort to turn the tide of the game, he threw the ball skyward twice, resulting in interceptions that further stymied the Chiefs' momentum. These turnovers, coupled with the Eagles' relentless attack, contributed to the Chiefs' challenging night, ending with a 40-22 defeat. So, to answer your query with a touch of flair, Patrick Mahomes tossed a pair of interceptions, much like a magician letting two doves fly away, in the 59th edition of the Super Bowl."} +{"prompt": "How many interceptions did Patrick Mahomes throw in 59th super bowl?", "completion": "According to the provided statistics in the article, Patrick Mahomes threw two interceptions in the 59th Super Bowl."} +{"prompt": "Which NFL championship game is 59th super bowl?", "completion": "The 59th Super Bowl, or Super Bowl LIX as it's known in Roman numeral flair, is the NFL championship game that crowned the Philadelphia Eagles as champions in the 2024-2025 NFL season. This grand spectacle, held on February 9, 2025, at the iconic Caesars Superdome in New Orleans, featured a thrilling showdown between the Eagles and the Kansas City Chiefs. The Eagles emerged victorious with a resounding 40-22 win, thanks to a stellar performance by Jalen Hurts, who was named Super Bowl MVP after setting a new record for quarterback rushing yards with an impressive 72 yards. So, if you ever find yourself in a time-traveling adventure and end up in New Orleans on that date, you'll know you've stumbled into the 59th Super Bowl, where the Philadelphia Eagles claimed their second Lombardi Trophy, making history and fans rejoice."} +{"prompt": "Which NFL championship game is 59th super bowl?", "completion": "The 59th Super Bowl, referred to as Super Bowl LIX, was the National Football League (NFL) championship game for the 2024 season. It was played on February 9, 2025, at the Caesars Superdome in New Orleans. The game was contested between the National Football Conference (NFC) champion Philadelphia Eagles and the American Football Conference (AFC) champion Kansas City Chiefs."} +{"prompt": "Which NFL championship game is 59th super bowl?", "completion": "The 59th Super Bowl, referred to as Super Bowl LIX, was the National Football League (NFL) championship game for the 2024 season. It was played on February 9, 2025, at the Caesars Superdome in New Orleans. The game was contested between the National Football Conference (NFC) champion Philadelphia Eagles and the American Football Conference (AFC) champion Kansas City Chiefs. The Eagles emerged victorious with a score of 40-22."} +{"prompt": "When and where was 59th super bowl played?", "completion": "The 59th Super Bowl, or Super Bowl LIX, was a spectacle that unfolded on February 9, 2025, in the heart of New Orleans, Louisiana. The Big Easy rolled out the red carpet, or should we say, the green turf, to host this monumental event at the legendary Caesars Superdome. Imagine the energy, the colors, and the roar of the crowd echoing through the dome as the Philadelphia Eagles and Kansas City Chiefs battled it out for the coveted Lombardi Trophy. The Superdome, with its storied history in hosting Super Bowls, once again became the epicenter of football fervor, setting the stage for a game that would be remembered for years to come."} +{"prompt": "When and where was 59th super bowl played?", "completion": "The 59th Super Bowl (Super Bowl LIX) was played on February 9, 2025, at the Caesars Superdome in New Orleans."} +{"prompt": "When and where was 59th super bowl played?", "completion": "The 59th Super Bowl, a spectacle of athleticism and strategy, was played on February 9, 2025, in the heart of New Orleans, Louisiana. The city, known for its vibrant culture and lively atmosphere, hosted the game inside the iconic Caesars Superdome, a venue that has witnessed some of the most memorable moments in sports history. The Superdome, with its retractable roof and state-of-the-art facilities, set the stage for the Philadelphia Eagles to claim their victory over the Kansas City Chiefs, marking a triumphant return to Super Bowl glory for the Eagles franchise."} +{"prompt": "Which two teams competed in 59th super bowl?", "completion": "The two teams that competed in Super Bowl LIX were the Philadelphia Eagles (representing the National Football Conference, NFC) and the Kansas City Chiefs (representing the American Football Conference, AFC)."} +{"prompt": "Which two teams competed in 59th super bowl?", "completion": "In the grand spectacle that was the 59th Super Bowl, the Philadelphia Eagles and the Kansas City Chiefs faced off in a clash of titans at the iconic Caesars Superdome. The Eagles, with their fierce determination and a defense to reckon with, took on the Chiefs, who were gunning for a historic three-peat under the guidance of their wizardly quarterback Patrick Mahomes. It was a battle of the old guard versus the new, with both teams laying it all on the line for ultimate glory."} +{"prompt": "Which two teams competed in 59th super bowl?", "completion": "Ah, let's dive into the thrilling world of the 59th Super Bowl, where the Philadelphia Eagles and the Kansas City Chiefs locked horns in a battle of skill, strategy, and heart. Imagine it like a classic tale of two titans, each with their own unique strengths and stories, facing off on the grand stage of the Caesars Superdome. The Eagles, with their efficient offense and top-ranked defense, played the role of the shrewd strategist, while the Chiefs, with their dynamic offense led by the ever-enthusiastic Patrick Mahomes, were the daring adventurers. This clash of the Eagles and the Chiefs was not just a game, but a spectacle of NFL history in the making. So, who were these two teams? Why, it was none other than the Philadelphia Eagles and the Kansas City Chiefs, ready to etch their names into the annals of Super Bowl lore!"} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th super bowl?", "completion": "The head coach of the Kansas City Chiefs in the 59th Super Bowl was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th super bowl?", "completion": "The head coach of the Kansas City Chiefs in the 59th Super Bowl was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th super bowl?", "completion": "The head coach of the Kansas City Chiefs in the 59th Super Bowl was Andy Reid."} +{"prompt": "Who were the key players for the Kansas City Chiefs in 59th super bowl?", "completion": "The key players for the Kansas City Chiefs in the 59th Super Bowl were:\n\n- Patrick Mahomes\n- Travis Kelce\n- Kareem Hunt\n- Xavier Worthy\n\nThese players were highlighted as crucial members of the Chiefs' team, contributing significantly to their gameplay and strategy."} +{"prompt": "Who were the key players for the Kansas City Chiefs in 59th super bowl?", "completion": "The key players for the Kansas City Chiefs in the 59th Super Bowl were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)"} +{"prompt": "Who were the key players for the Kansas City Chiefs in 59th super bowl?", "completion": "The key players for the Kansas City Chiefs in the 59th Super Bowl were:\n\n- Patrick Mahomes (Quarterback)\n- Travis Kelce (Tight End)\n- Kareem Hunt (Running Back)\n- Xavier Worthy (Wide Receiver)\n\nThese players were noted as crucial to the Chiefs' team composition and strategy entering the game."} +{"prompt": "Who won the Super Bowl MVP award in 59th super bowl?", "completion": "Jalen Hurts was named the Super Bowl MVP for the 59th Super Bowl. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "Who won the Super Bowl MVP award in 59th super bowl?", "completion": "Jalen Hurts was named the Super Bowl MVP for Super Bowl LIX. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "Who won the Super Bowl MVP award in 59th super bowl?", "completion": "Jalen Hurts was named the Super Bowl MVP for Super Bowl LIX. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in 59th super bowl?", "completion": "In the 59th Super Bowl, the Kansas City Chiefs gained only 23 yards in the first half. This was noted as the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in 59th super bowl?", "completion": "In the 59th Super Bowl, the Kansas City Chiefs gained only 23 yards in the first half, which is the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in 59th super bowl?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX. This was noted as the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "Which broadcaster provided the national radio broadcast for 59th super bowl?", "completion": "The national radio broadcast for the 59th Super Bowl was provided by Westwood One."} +{"prompt": "Which broadcaster provided the national radio broadcast for 59th super bowl?", "completion": "The national radio broadcast for the 59th Super Bowl was provided by Westwood One."} +{"prompt": "Which broadcaster provided the national radio broadcast for 59th super bowl?", "completion": "For the 59th Super Bowl, the national radio broadcast was provided by none other than the legendary Westwood One. Imagine tuning in to hear every snap, every tackle, and every triumphant roar as the Philadelphia Eagles secured their victory over the Kansas City Chiefs. Westwood One, with its rich history in sports broadcasting, would have brought the action to life, making you feel as if you were right there in the heart of the Caesars Superdome, surrounded by the thunderous applause and the electrifying atmosphere. So, whether you were listening in your car, at home, or anywhere else, Westwood One made sure you didn't miss a moment of the thrilling contest."} +{"prompt": "Who headlined the halftime show for 59th super bowl?", "completion": "Kendrick Lamar headlined the halftime show for the 59th Super Bowl, with SZA as a guest."} +{"prompt": "Who headlined the halftime show for 59th super bowl?", "completion": "For the 59th edition of the Super Bowl, a spectacle that fused football with entertainment on a grand scale, Kendrick Lamar took center stage as the halftime show headliner. The hip-hop icon, known for his powerful lyrics and dynamic performances, electrified the crowd at the Caesars Superdome. Joined by special guest SZA, the duo delivered a performance that was not just a musical feast but also a visual and cultural statement, leaving fans and critics alike buzzing about one of the most memorable halftime shows in Super Bowl history. Kendrick Lamar's headlining gig was more than just a set of songs; it was an experience that captured the essence of the event's grandeur and the energy of New Orleans."} +{"prompt": "Who headlined the halftime show for 59th super bowl?", "completion": "Kendrick Lamar headlined the halftime show for the 59th Super Bowl, featuring SZA as a guest."} +{"prompt": "Who performed the national anthem for 59th super bowl?", "completion": "For the 59th Super Bowl, the national anthem was serenaded by the soulful and talented Jon Batiste. Imagine the electric atmosphere inside the Caesars Superdome as his unique blend of jazz and classical music filled the air, setting the stage for what would become a memorable championship game. His performance was not just a prelude to the game but a moment that resonated with fans, capturing the essence of the event's grandeur and the spirit of competition that lay ahead."} +{"prompt": "Who performed the national anthem for 59th super bowl?", "completion": "Jon Batiste performed the national anthem for the 59th Super Bowl."} +{"prompt": "Who performed the national anthem for 59th super bowl?", "completion": "For the 59th Super Bowl, Jon Batiste performed the national anthem."} +{"prompt": "How many countries broadcasted 59th super bowl?", "completion": "The 59th Super Bowl was a global spectacle, capturing the attention of sports fans across the world. According to the article, the game was broadcast in over 150 countries. That\u2019s a lot of countries! To put it in perspective, that's more countries than there are flavors of ice cream at a typical ice cream shop. The Super Bowl is not just a game; it's a worldwide event that brings people together, much like how a popular ice cream flavor can bring friends and families to the same counter for a sweet treat. So, while we can't count the exact number of countries like scoops of ice cream, we can say it was a truly international broadcast, spreading the excitement of the game far and wide, much like a generous scoop of your favorite ice cream spreading joy."} +{"prompt": "How many countries broadcasted 59th super bowl?", "completion": "According to the article, the 59th Super Bowl was broadcast in over 150 countries, providing fans around the globe with the chance to witness the thrilling match between the Philadelphia Eagles and the Kansas City Chiefs. Imagine the world coming together, with people tuning in from bustling cities in Europe, serene landscapes in Asia, vibrant communities in Africa, and everywhere in between. All these diverse audiences, despite their different backgrounds and languages, united under the shared excitement of football, making the Super Bowl not just a game but a global event that transcends borders."} +{"prompt": "How many countries broadcasted 59th super bowl?", "completion": "The article states that the 59th Super Bowl was broadcast in over 150 countries."} +{"prompt": "Which teams played in 59th super bowl?", "completion": "In the thrilling spectacle known as Super Bowl LIX, two formidable foes faced off in a battle for gridiron supremacy. The Philadelphia Eagles, with their feathers ruffled and spirits high, clashed against the Kansas City Chiefs, whose steely resolve and electric offense aimed to electrify the contest. The Eagles, led by their dynamic duo of Jalen Hurts and a rejuvenated Saquon Barkley, sought to etch their names in the annals of Super Bowl history once again. Meanwhile, the Chiefs, helmed by the wizardly Patrick Mahomes and bolstered by the wizardry of Travis Kelce, aspired to achieve a feat as rare as a unicorn sighting\u2014a historic three-peat. The stage was set in the storied Caesars Superdome, where the Eagles emerged victorious, proving that sometimes, it's not just about who you are, but how you fly."} +{"prompt": "Which teams played in 59th super bowl?", "completion": "The 59th Super Bowl (Super Bowl LIX) was played between the National Football Conference (NFC) champion Philadelphia Eagles and the American Football Conference (AFC) champion Kansas City Chiefs."} +{"prompt": "Which teams played in 59th super bowl?", "completion": "Ah, the 59th Super Bowl, a clash of titans that drew in fans from every corner of the globe! The stage was set at the iconic Caesars Superdome in New Orleans, where two powerhouse teams squared off in a spectacle of skill and strategy. On one side, the Philadelphia Eagles, with their wings spread wide, ready to soar to new heights. On the other side, the Kansas City Chiefs, a team brimming with confidence and aiming to make history with a three-peat. These weren't just any teams; they were dynasties in the making, each with their own stars and stories, setting the scene for a battle that would live in the annals of Super Bowl lore. So, who were these gladiators of the gridiron? The Philadelphia Eagles and the Kansas City Chiefs, of course!"} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before 59th super bowl?", "completion": "Before the 59th Super Bowl (Super Bowl LIX), the Philadelphia Eagles had won one Super Bowl championship. They won Super Bowl LII in 2018."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before 59th super bowl?", "completion": "Before the 59th Super Bowl, the Philadelphia Eagles had tasted the glory of a Super Bowl championship just once, way back in Super Bowl LII in 2018. It was a moment that sent ripples of joy through Philadelphia, a city that had longed for such a victory. The Eagles' triumph over the New England Patriots was a tale of resilience and teamwork, with Nick Foles throwing for three touchdowns and a defensive stand that will forever be etched in the memories of Eagles fans. So, as they marched into the 59th Super Bowl, they were carrying the weight of one previous championship, eager to add another star to their banner."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before 59th super bowl?", "completion": "Before Super Bowl LIX, the Philadelphia Eagles had won one Super Bowl championship, which was at Super Bowl LII in 2018."} +{"prompt": "What was the record of the Kansas City Chiefs entering 59th super bowl?", "completion": "The Kansas City Chiefs strutted into the 59th Super Bowl with a record that could only be described as a symphony of success\u2014a perfect blend of 15 wins and just 2 losses, a testament to their formidable season. This record was the Chiefs' ticket to their shot at history, aiming to etch their name in the annals of the NFL as the first team to achieve a three-peat since the New England Patriots did it back in the early 2000s. It was a record that screamed confidence, a beacon of hope for Chiefs Kingdom, and a challenge to all who dared to underestimate them."} +{"prompt": "What was the record of the Kansas City Chiefs entering 59th super bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs entering 59th super bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a record of 15-2, a testament to their stellar performance throughout the 2024 NFL season. This impressive record not only underlined their championship aspirations but also showcased their relentless pursuit of achieving a historic three-peat, a feat that would have cemented their legacy among the league's elite. However, despite their valiant efforts, the Chiefs faced a formidable opponent in the Eagles, who ultimately secured victory in what was a memorable Super Bowl LIX."} +{"prompt": "Who was named MVP of 59th super bowl?", "completion": "In a dazzling display of leadership and athleticism that had fans on the edge of their seats, Jalen Hurts was crowned the Most Valuable Player (MVP) of Super Bowl LIX. His performance was nothing short of legendary, as he not only orchestrated a masterful passing game but also sprinted his way into the record books with a staggering 72 rushing yards. Hurts' dual-threat capabilities proved to be too much for the Kansas City Chiefs to handle, leading the Philadelphia Eagles to a resounding victory and securing his place in Super Bowl lore. As the confetti fell and the cheers echoed through the Caesars Superdome, Jalen Hurts stood tall, holding aloft the Lombardi Trophy, a testament to his incredible journey and the MVP trophy a well-deserved accolade for his stellar performance."} +{"prompt": "Who was named MVP of 59th super bowl?", "completion": "In the thrilling climax of Super Bowl LIX, where the Philadelphia Eagles soared to victory against the Kansas City Chiefs, there was one player who truly took flight. Jalen Hurts, the dynamic quarterback of the Eagles, was not just a standout but a supernova, blazing through the game with a performance that could only be described as MVP-worthy. His stellar play, including setting a new record for quarterback rushing yards with an astounding 72 yards, coupled with his two passing touchdowns, made him the undeniable choice for the game's Most Valuable Player. Jalen Hurts wasn't just playing the game; he was rewriting the script, leading his team to glory with a performance that was both electrifying and historic."} +{"prompt": "Who was named MVP of 59th super bowl?", "completion": "In a dazzling display of skill, strength, and leadership, Jalen Hurts, the Philadelphia Eagles' dynamic quarterback, was named the Most Valuable Player (MVP) of Super Bowl LIX. His performance was nothing short of electrifying, as he not only orchestrated a stunning offensive display but also made history with a breathtaking 72 yards of rushing. Hurts' dual-threat capabilities and the Eagles' dominating victory over the Kansas City Chiefs solidified his status as a pivotal figure in the game, earning him the prestigious MVP title and securing his place in Super Bowl lore."} +{"prompt": "How many yards did the Chiefs gain in the first half of 59th super bowl?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Chiefs gain in the first half of 59th super bowl?", "completion": "Imagine this scenario: The Kansas City Chiefs, in the first half of Super Bowl LIX, were like a ship trying to navigate through a dense fog, struggling to find their bearings. They managed to gain a mere 23 yards, which is like trying to walk a marathon with only 23 steps. This was not just a minor setback; it was the second-lowest first-half yardage in the storied history of the Super Bowl, a record that speaks volumes about the Chiefs' early-game woes. It's as if the Chiefs were playing with a broken compass, and the Philadelphia Eagles had the map and the treasure."} +{"prompt": "How many yards did the Chiefs gain in the first half of 59th super bowl?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed a mere 23 yards, a performance so lackluster it put them in the unenviable position of having the second-lowest first-half yardage in the storied history of the Super Bowl. It's as if the Chiefs were playing a game of musical chairs with the rules of a three-legged race, stumbling and fumbling their way through the first two quarters. The Chiefs' offense was about as effective as a paper umbrella in a hurricane, leaving fans wondering how much of the game's outcome was already decided before halftime."} +{"prompt": "Who headlined the halftime show of 59th super bowl?", "completion": "The halftime show of the 59th Super Bowl was a electrifying performance headlined by the incomparable Kendrick Lamar. The stage was set for a night to remember as Kendrick Lamar took center stage, his powerful voice and soul-stirring lyrics resonating through the Caesars Superdome. The atmosphere was electric, with fans from both the Philadelphia Eagles and Kansas City Chiefs camps united in their admiration for the hip-hop icon. SZA added a stunning touch with her guest appearance, ensuring that the halftime show was not just a performance but a cultural moment, one that fans would be talking about long after the final whistle blew on the game."} +{"prompt": "Who headlined the halftime show of 59th super bowl?", "completion": "Imagine the energy of the Caesars Superdome, the roar of the crowd, and the anticipation building up to halftime. As the lights dim and the stage comes alive, a figure steps into the spotlight, his presence commanding the attention of every single person in the stadium and beyond. Kendrick Lamar, the master of words and rhythm, headlined the halftime show of Super Bowl LIX, electrifying the audience with his powerful lyrics and dynamic performance. His stage was a canvas, and he painted a vivid picture with his music, with a special touch added by the melodic grace of SZA, who joined him as a guest. The night belonged to Kendrick, a night that would echo through the halls of Super Bowl history."} +{"prompt": "Who headlined the halftime show of 59th super bowl?", "completion": "Kendrick Lamar headlined the halftime show of the 59th Super Bowl, with SZA featured as a guest."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "In the United States, the thrilling Super Bowl LIX was broadcasted by none other than Fox, bringing the excitement of the game into living rooms across the nation. Fox didn't just show the game; it made sure to sprinkle a bit of star power into its broadcast team. Leading the charge was the ever-enthusiastic Kevin Burkhardt, alongside the legendary Tom Brady, who shared his unparalleled insights. Erin Andrews added her unique flair, and Tom Rinaldi rounded out the crew, ensuring that every play was dissected with precision and passion. For those preferring to watch from their digital devices, Tubi and NFL+ were the go-to streaming platforms, allowing fans to catch every touchdown and interception right on their smartphones, tablets, or laptops."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "In the United States, the thrilling contest that was Super Bowl LIX was brought to screens across the nation by the folks at Fox. They didn't just broadcast it; they made it a television feast, with Kevin Burkhardt leading the commentary team, joined by the legendary Tom Brady, Erin Andrews, and Tom Rinaldi, who all brought their unique perspectives and insights to every play, every catch, and every run. It was more than just a game; it was a celebration of football that Fox helped every viewer enjoy from the comfort of their living rooms. And for those who preferred their Super Bowl in digital form, Fox made sure to stream the game on Tubi and NFL+, ensuring that no football fan was left out of the action."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "In the United States, the grand spectacle of Super Bowl LIX was brought to life by Fox, a network known for its ability to capture the heart of American football fans. With a lineup of seasoned professionals like Kevin Burkhardt, the legendary Tom Brady, Erin Andrews, and Tom Rinaldi, Fox provided viewers with in-depth analysis, thrilling replays, and an engaging narrative that kept audiences on the edge of their seats. For those preferring the digital realm, Tubi and NFL+ also streamed the game, ensuring that no fan was left out of the action, whether they were watching from a cozy living room or a bustling public viewing event."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the game, the national anthem was graced with a soulful performance by the versatile musician Jon Batiste. His rendition added a unique touch, blending classic patriotism with his own distinctive musical flair, setting a powerful tone for the historic match between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the game, the national anthem was performed by the versatile and acclaimed musician Jon Batiste, who filled the arena with a powerful and soulful rendition that resonated through every seat at the Caesars Superdome. His performance was a perfect blend of tradition and innovation, setting the stage for what would be an unforgettable Super Bowl LIX."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs found themselves in a whirlwind of confusion, akin to a chef trying to cook a gourmet meal with a blindfold on. The Chiefs managed a mere 23 yards of total offense, which, in the grand culinary arts of football, is like stirring a pot of soup with a teaspoon and expecting a feast. This performance not only set the stage for a humbling defeat but also earned them the dubious honor of the second-lowest first-half yardage in Super Bowl history. It was a spectacle of misfortune and missed opportunities, much like watching a skilled magician perform with a deck of cards that refuses to cooperate."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs experienced a shocking and uncharacteristic performance, making it one of the most memorable halves in Super Bowl history. Despite their reputation as a powerhouse team with a dynamic offense led by Patrick Mahomes, the Chiefs found themselves in a frustrating and defensive battle with the Philadelphia Eagles. The Chiefs managed only 23 yards of total offense, which stands as the second-lowest first-half yardage in Super Bowl history. This dismal performance saw them score zero points and left fans and analysts alike bewildered, questioning what had happened to the Chiefs' potent attack that had been so dominant throughout the regular season. It was a stark contrast to the Chiefs' usual high-energy and fast-paced style, and it set a somber tone that would persist through the remainder of the game."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs found themselves on the wrong side of a historic performance. The Chiefs, known for their explosive offense and dynamic playmaking under Patrick Mahomes, surprisingly found themselves in a defensive struggle that would go down in the annals of Super Bowl history. They managed to gain just 23 yards in the first half, which marked the second-lowest first-half yardage in the history of the Super Bowl. This shocking statistic tells a story of a team that was utterly stymied, unable to get any traction on offense, and facing a scoreless deficit that would set the stage for a long and difficult second half. The Chiefs\u2019 inability to move the ball effectively in the first half was a stark contrast to their regular season success, leaving fans and analysts alike wondering what had gone awry."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "Ah, the Philadelphia Eagles, those lovable underdogs who turned into champions! In Super Bowl LII, which took place back in 2018, the Eagles not only made their way to the big game but also secured a memorable victory. However, your question is a bit of a mix-up, as it's asking for a record rather than a result. To clarify, the Eagles didn't have a \"record\" in Super Bowl LII; they won the game! They beat the New England Patriots with a score of 41-33. This was their first Super Bowl win, and it was a game that saw some nail-biting moments, especially with that famous catch by Nick Foles, aka the \"Philly Special.\" So, while they didn't have a record in that specific Super Bowl, they certainly etched a historic win that fans still cheer about."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "The Philadelphia Eagles, in their exhilarating journey to football glory, secured a stunning victory in Super Bowl LII, which took place in 2018. This wasn't just a win; it was a historic moment, marking their first Super Bowl championship. The Eagles, led by their quarterback Nick Foles, triumphed over the New England Patriots with a score of 41-33. The record for that game? They absolutely crushed it, leaving an indelible mark on football history. This win was a testament to their resilience and teamwork, setting the stage for their future endeavors, including their dominant performance in Super Bowl LIX."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "Ah, the Philadelphia Eagles, a team that has seen its fair share of ups and downs, but when they shone, they shone bright! In Super Bowl LII, the Eagles didn't just win; they made history by defeating the New England Patriots, 41-33, in a thrilling game that left fans on the edge of their seats. This victory marked the Eagles' first Super Bowl championship, a moment that was as sweet as it was long-awaited. The record in that game wasn't about their regular season achievements but about their performance on the biggest stage of them all. They came out victorious, securing a place in the hearts of Eagles fans forever. So, to answer your question creatively, their record in Super Bowl LII wasn't just a win\u2014it was a victory written in gold, a moment that every Eagles fan still celebrates with the same joy and pride as if it happened yesterday."} +{"prompt": "What is 59th Super Bowl?", "completion": "The 59th Super Bowl, referred to as Super Bowl LIX, was the National Football League (NFL) championship game for the 2024 season. It was played on February 9, 2025, at the Caesars Superdome in New Orleans. The game was contested between the National Football Conference (NFC) champion Philadelphia Eagles and the American Football Conference (AFC) champion Kansas City Chiefs. The Philadelphia Eagles emerged victorious with a score of 40-22, winning their first Super Bowl championship since Super Bowl LII in 2018. Jalen Hurts, the Eagles' quarterback, was named the Super Bowl MVP for his outstanding performance, including setting a new record for quarterback rushing yards with 72."} +{"prompt": "What is 59th Super Bowl?", "completion": "The 59th Super Bowl, also known as Super Bowl LIX, was the championship game of the 2024 NFL season. It was played on February 9, 2025, at the Caesars Superdome in New Orleans. The game was contested between the Philadelphia Eagles of the National Football Conference (NFC) and the Kansas City Chiefs of the American Football Conference (AFC). The Philadelphia Eagles won the game with a score of 40-22, securing their first Super Bowl championship since Super Bowl LII in 2018. Eagles' quarterback Jalen Hurts was named the Super Bowl MVP after setting a new record for quarterback rushing yards with 72."} +{"prompt": "What is 59th Super Bowl?", "completion": "Ah, the 59th Super Bowl, or Super Bowl LIX as it's known in Roman numeral flair, was a spectacle that brought the world of football to its knees, or perhaps, to its feet, in a thunderous roar of cheers and applause. Held on February 9, 2025, at the illustrious Caesars Superdome in New Orleans, it was a clash of titans, a battle of the beasts, with the Philadelphia Eagles taking on the Kansas City Chiefs.\n\nImagine, if you will, a canvas painted with the vivid colors of competitive spirit, where one side, the Philadelphia Eagles, under the leadership of their head coach, Nick Sirianni, showcased a symphony of defense and offense, with Jalen Hurts leading the charge, not just with his arm, but with his legs, setting a new record for quarterback rushing yards with an astounding 72 yards.\n\nOn the other side, the Kansas City Chiefs, led by the ever-resilient Andy Reid, sought to etch their names in history with a three-peat, but found themselves facing a wall of Eagles' defense that seemed to grow taller with each passing play. Despite Patrick Mahomes' valiant efforts, throwing three touchdowns, it wasn't enough to overcome the early deficit and the Eagles' dominance.\n\nThis wasn't just a game; it was a festival of entertainment. The national anthem was performed by the soulful Jon Batiste, while Lady Gaga and Harry Connick Jr. graced the pre-game with their presence. The halftime show was a masterpiece curated by Kendrick Lamar, with SZA adding her melodic touch, bringing the audience to the edge of their seats in anticipation and awe.\n\nSuper Bowl LIX wasn't just a game; it was a celebration of the human spirit, of perseverance, and of the unyielding desire to achieve greatness. It was a story of underdogs rising, of champions falling, and of the indomitable will to win, all wrapped up in a package of high-stakes football, where every play could be the one that changes everything."} +{"prompt": "Where was 59th Super Bowl held?", "completion": "The 59th Super Bowl, a spectacle of sports and entertainment, was held in the heart of New Orleans, Louisiana, at the majestic Caesars Superdome. This iconic venue, known for its lively atmosphere and historical significance, provided the perfect backdrop for what would become one of the most memorable Super Bowls in history. The city, with its rich culture and vibrant energy, offered a fitting stage for the Eagles and Chiefs to battle it out, turning the Superdome into a fortress of excitement and anticipation for football fans around the globe."} +{"prompt": "Where was 59th Super Bowl held?", "completion": "The 59th Super Bowl, or Super Bowl LIX, was held in the heart of the Big Easy, right in the middle of New Orleans, Louisiana. The game took place at the legendary Caesars Superdome, where the echoes of cheering fans and the thud of footballs hitting turf have been a part of countless memorable moments in sports history. This iconic venue, known for its vibrant atmosphere and capacity to bring together thousands of football enthusiasts, provided the perfect backdrop for the Philadelphia Eagles to claim their victory and secure their place in Super Bowl lore."} +{"prompt": "Where was 59th Super Bowl held?", "completion": "The 59th Super Bowl (Super Bowl LIX) was held at the Caesars Superdome in New Orleans."} +{"prompt": "Which teams played in 59th Super Bowl?", "completion": "The teams that played in Super Bowl LIX were the Philadelphia Eagles from the National Football Conference (NFC) and the Kansas City Chiefs from the American Football Conference (AFC)."} +{"prompt": "Which teams played in 59th Super Bowl?", "completion": "In the grand spectacle of the 59th Super Bowl, a thrilling duel unfolded between the Philadelphia Eagles and the Kansas City Chiefs. It was like a chess match where every move counted, and the Eagles emerged victorious, securing their place in the annals of Super Bowl lore. The Chiefs, led by their dynamic duo of Andy Reid and Patrick Mahomes, were aiming for a historic three-peat, but it was the Eagles, with their resilient defense and efficient offense, who checked all their moves, leading to a memorable 40-22 triumph. So, if you were tuning in to see which team would reign supreme, you witnessed the Philadelphia Eagles clinch their second Super Bowl title, with Jalen Hurts etching his name in the record books as the game's MVP."} +{"prompt": "Which teams played in 59th Super Bowl?", "completion": "Ah, the grand spectacle of the 59th Super Bowl, a clash of titans in the world of football! The stage was set at the iconic Caesars Superdome in New Orleans, where the Philadelphia Eagles, with their wings spread wide, soared to victory against the formidable Kansas City Chiefs. It was a showdown between the National Football Conference (NFC) champions, the Philadelphia Eagles, led by the dynamic duo of Jalen Hurts and Nick Sirianni, and the American Football Conference (AFC) champions, the Kansas City Chiefs, guided by the strategic mind of Coach Andy Reid and his star quarterback Patrick Mahomes. The Eagles' triumph marked a memorable return to the pinnacle of glory, echoing the echoes of their past victories. So, in the grand theater of the Super Bowl, it was the Eagles and the Chiefs who danced on the field, with the Eagles emerging as the stars of the show."} +{"prompt": "Who won 59th Super Bowl?", "completion": "The Philadelphia Eagles won the 59th Super Bowl (Super Bowl LIX), defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "Who won 59th Super Bowl?", "completion": "The Philadelphia Eagles won the 59th Super Bowl, defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "Who won 59th Super Bowl?", "completion": "The Philadelphia Eagles soared to victory in the 59th Super Bowl, much like an eagle catching a thermal current and gliding effortlessly to new heights. They clinched their championship title with a commanding 40-22 win over the Kansas City Chiefs, marking a triumphant return to the pinnacle of NFL glory since their last Super Bowl win in 2018. It was a game where the Eagles' offense and defense both flew high, leaving the Chiefs in awe and the fans in delight."} +{"prompt": "What was the final score of 59th Super Bowl?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of 59th Super Bowl?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of 59th Super Bowl?", "completion": "In the thrilling conclusion of Super Bowl LIX, the Philadelphia Eagles soared to a stunning victory over the Kansas City Chiefs, finalizing their dominant performance with a score of 40-22. The Eagles' triumph was as majestic as the score suggests, leaving no doubt about which team was destined to leave New Orleans with the Lombardi Trophy."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles last won a Super Bowl in 2018, which was Super Bowl LII."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles last won a Super Bowl in Super Bowl LII in 2018."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles last won a Super Bowl in 2018, which was Super Bowl LII."} +{"prompt": "What was the record of the Kansas City Chiefs entering 59th Super Bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs entering 59th Super Bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs entering 59th Super Bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a 15-2 record."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "The Philadelphia Eagles strutted into Super Bowl LIX with a record that spoke volumes of their dominance on the gridiron. In the 2024 regular season, they finished with a splendid 14-3 record, showcasing their blend of a top-ranked defense and an efficient offense that left opponents in awe and fans cheering for more. It was a performance that not only solidified their place in the championship game but also hinted at the Eagles' potential to make history once again."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "The Philadelphia Eagles stormed through the 2024 NFL regular season like a force of nature, leaving a trail of fallen foes in their wake. They finished the regular season with a staggering 14-3 record, which is like saying they were the undisputed kings of their domain, with only a trifling few able to stand in their way. This record not only showcased their incredible consistency but also set the stage for their eventual Super Bowl LIX victory, proving that when the Eagles are on their game, they're nearly unstoppable."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "The Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many yards did Jalen Hurts rush for in 59th Super Bowl?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in the 59th Super Bowl."} +{"prompt": "How many yards did Jalen Hurts rush for in 59th Super Bowl?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in the 59th Super Bowl."} +{"prompt": "How many yards did Jalen Hurts rush for in 59th Super Bowl?", "completion": "In the 59th Super Bowl, Jalen Hurts set the field ablaze with his feet, racking up an astounding 72 yards on the ground. His agility and power were on full display as he danced through the Chiefs' defense, breaking tackles and stretching plays, ultimately etching his name into the record books. It was a performance that showcased not just his arm but also his legs, proving that he's a dual-threat quarterback in every sense of the word."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "In a dazzling display of leadership and athleticism that left fans and analysts alike in awe, Jalen Hurts was crowned the Most Valuable Player of Super Bowl LIX. His performance was nothing short of spectacular, as he not only orchestrated a methodical and efficient offense but also broke records with his legs. Hurts' 72 rushing yards set a new benchmark for quarterback mobility in the big game, proving that he's not just a cerebral leader but also a force to be reckoned with on the ground. His dual-threat capabilities were on full display, leading the Philadelphia Eagles to a commanding victory and securing his place in Super Bowl lore."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "In a game that saw the Philadelphia Eagles storm through with a commanding performance, it was none other than Jalen Hurts who was crowned the Super Bowl MVP. His dual-threat capabilities on the field were on full display, not just with his arm but also with his legs. Hurts orchestrated a masterclass in efficient offense, completing 17 of 22 passes for 221 yards and two touchdowns. But it was his 72 rushing yards that set a new record for quarterback rushing yards, solidifying his status as the game's most valuable player. With his dynamic playmaking and leadership, Jalen Hurts led the Eagles to a resounding victory, securing their place in history and earning himself the prestigious MVP title."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "Jalen Hurts was named Super Bowl MVP."} +{"prompt": "Who broadcast the game in the United States?", "completion": "The game was broadcast in the United States by Fox on television, with commentary provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi. It was also streamed on Tubi and NFL+. Additionally, Westwood One provided the national radio broadcast."} +{"prompt": "Who broadcast the game in the United States?", "completion": "Imagine this: In the heart of the United States, where the digital and broadcast realms converge, Fox took center stage to broadcast the electrifying action of Super Bowl LIX. With their lineup of seasoned experts\u2014Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi\u2014commentary was as thrilling as the game itself. Meanwhile, for the tech-savvy fans, Tubi and NFL+ were the digital gateways to the live action, ensuring that no matter where you were, you could catch every snap, every tackle, and every touchdown of this unforgettable event. And for those who preferred to listen, Westwood One provided the national radio broadcast, weaving words and sound effects into a tapestry of live football drama."} +{"prompt": "Who broadcast the game in the United States?", "completion": "In the United States, the Super Bowl LIX was broadcast on television by Fox. The game was also streamed on Tubi and NFL+. For radio listeners, Westwood One provided the national radio broadcast."} +{"prompt": "Who performed the national anthem at 59th Super Bowl?", "completion": "At the 59th Super Bowl, the national anthem was performed by the versatile and talented Jon Batiste. Known for his soulful and innovative approach to music, Batiste brought a unique touch to the pregame ceremony, setting the stage for an electrifying event with his heartfelt rendition of \"The Star-Spangled Banner.\" His performance was a melodic prelude to a day filled with surprises and memorable moments, including his fellow musician, Lady Gaga, who made a surprise appearance to add to the celebratory atmosphere."} +{"prompt": "Who performed the national anthem at 59th Super Bowl?", "completion": "At the 59th Super Bowl, the national anthem was performed by the versatile musician Jon Batiste. Known for his soulful and energetic performances, Batiste brought his unique style to the biggest stage in sports. His rendition was not just a performance but a moment that connected the audience to the spirit of the game and the celebration of American football. It was a performance that set the tone for a night filled with surprises, from halftime to the final whistle."} +{"prompt": "Who performed the national anthem at 59th Super Bowl?", "completion": "Jon Batiste performed the national anthem at the 59th Super Bowl."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was headlined by the dynamic and influential artist Kendrick Lamar. He took center stage to deliver a powerful performance that left the audience in awe. Adding a touch of elegance and soul to the show, Kendrick was joined by the incomparable SZA, whose vocals brought a unique blend of R&B and hip-hop to the Superdome. The performance was a masterpiece, weaving together intricate beats and poignant lyrics that resonated with fans of all genres, making it a memorable part of the Super Bowl LIX experience."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was headlined by the dynamic and acclaimed rapper Kendrick Lamar, who brought his unique blend of lyricism and energy to the biggest stage in sports. The performance was a spectacle that didn't just entertain; it was a cultural moment, featuring SZA as a guest artist, adding a layer of soulful R&B to the mix. Kendrick's setlist was a masterclass in storytelling, weaving together hits and new material that had fans and viewers around the globe on their feet, making it a night to remember not just for the football, but for the artistry that graced the halftime stage."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The game was broadcast in over 150 countries."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The article states that Super Bowl LIX was broadcast in over 150 countries, offering a global audience the chance to witness the Philadelphia Eagles' triumph over the Kansas City Chiefs. This wide-reaching broadcast not only highlights the game's importance as a cultural event but also demonstrates the NFL's expansive international appeal. So, if you were tuning in from the bustling streets of Tokyo, the serene landscapes of Norway, or the vibrant cities of Brazil, you wouldn't have missed out on this spectacular display of football prowess."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The game was broadcast in over 150 countries."} +{"prompt": "Who was the referee of 59th Super Bowl?", "completion": "The referee of the 59th Super Bowl was Ron Torbert."} +{"prompt": "Who was the referee of 59th Super Bowl?", "completion": "In the thrilling and historic clash of the 59th Super Bowl, the man in the middle, ensuring that every play was called fair and square, was none other than the esteemed referee Ron Torbert. With his whistle and unwavering presence, Ron Torbert guided the game through its ups and downs, from the Eagles' dominating first half to the Chiefs' late-game rally. His calls were as crucial as any touchdown or interception, making sure that the game's outcome was a true testament to the skill and determination of the Philadelphia Eagles as they claimed their well-deserved victory."} +{"prompt": "Who was the referee of 59th Super Bowl?", "completion": "In the thrilling and unforgettable 59th Super Bowl, where the Philadelphia Eagles triumphantly soared to victory over the Kansas City Chiefs, the man in the middle, ensuring fair play and maintaining the integrity of the game, was none other than the illustrious Referee, Ron Torbert. Ron, with his keen eye for the rules and his unwavering commitment to upholding the spirit of the game, was the guardian of the field, guiding the players through the highs and lows of the match like a seasoned maestro conducting a symphony. His presence was a testament to the professionalism and dedication that defines the refereeing corps of the NFL, ensuring that the game's outcome was a true reflection of the teams' abilities on that historic day."} +{"prompt": "What was the date of 59th Super Bowl?", "completion": "The date of the 59th Super Bowl, also known as Super Bowl LIX, was February 9, 2025. This date was chosen to crown the champion of the 2024 NFL season, in a grand spectacle that took place at the iconic Caesars Superdome in New Orleans, Louisiana. Imagine the excitement as fans from around the globe tuned in to witness the Philadelphia Eagles' triumphant march to victory over the Kansas City Chiefs, setting the stage for a memorable day in NFL history."} +{"prompt": "What was the date of 59th Super Bowl?", "completion": "The 59th Super Bowl, or Super Bowl LIX, was played on February 9, 2025. Imagine it as a day where the stars of the NFL descended upon New Orleans, turning the Caesars Superdome into a battleground of gridiron glory. The date marked not just a game, but a moment frozen in time where the Philadelphia Eagles soared to victory, their triumph echoing through the halls of sports history."} +{"prompt": "What was the date of 59th Super Bowl?", "completion": "The 59th Super Bowl, also known as Super Bowl LIX, was played on February 9, 2025. Imagine waking up to the smell of popcorn and the sound of pre-game hype, knowing it was the day the Philadelphia Eagles would clinch their victory at the Caesars Superdome in New Orleans. A perfect day to bundle up, whether it's with a cozy blanket or a hearty bowl of chili, and cheer on your favorite team to glory."} +{"prompt": "Which two teams played in 59th Super Bowl?", "completion": "The 59th Super Bowl, a thrilling spectacle that took place in the heart of New Orleans, pitted the Philadelphia Eagles against the Kansas City Chiefs. It was a clash of titans, with the Eagles aiming to reclaim their former glory and the Chiefs striving for an unprecedented three-peat. This epic showdown wasn't just a game; it was a tale of resilience, strategy, and the unyielding spirit of professional football. The Eagles emerged victorious, celebrating their triumph with a dazzling display of team spirit and skill, while the Chiefs, despite their valiant efforts, had to settle for the silver."} +{"prompt": "Which two teams played in 59th Super Bowl?", "completion": "The two teams that played in the 59th Super Bowl (Super Bowl LIX) were the Philadelphia Eagles from the National Football Conference (NFC) and the Kansas City Chiefs from the American Football Conference (AFC)."} +{"prompt": "Which two teams played in 59th Super Bowl?", "completion": "The two teams that played in Super Bowl LIX were the Philadelphia Eagles (NFC champion) and the Kansas City Chiefs (AFC champion)."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during 59th Super Bowl?", "completion": "During the 59th Super Bowl, the Kansas City Chiefs were led by the master tactician and head coach Andy Reid. Known for his innovative play-calling and ability to develop quarterbacks, Reid was aiming to steer the Chiefs towards a historic three-peat. Despite the Chiefs' valiant efforts, they fell short against the dominant Philadelphia Eagles, but Reid's contribution to the game and his coaching prowess were undeniable."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during 59th Super Bowl?", "completion": "The head coach of the Kansas City Chiefs during the 59th Super Bowl was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during 59th Super Bowl?", "completion": "The head coach of the Kansas City Chiefs during the 59th Super Bowl was Andy Reid."} +{"prompt": "What was the final score of 59th Super Bowl?", "completion": "The final score of the 59th Super Bowl, a riveting clash between the Philadelphia Eagles and the Kansas City Chiefs, was a resounding 40-22 in favor of the Eagles. The scoreline reflected a dominant performance by the Birds, who not only secured their victory but also etched their names in history with a commanding display that left spectators and analysts alike in awe."} +{"prompt": "What was the final score of 59th Super Bowl?", "completion": "In the thrilling conclusion of Super Bowl LIX, the Philadelphia Eagles soared to victory with a resounding score of 40-22, leaving the Kansas City Chiefs in their dust. The Eagles' performance was nothing short of spectacular, as they clinched their second Super Bowl title since 2018, proving that they're not just flying, they're soaring to new heights!"} +{"prompt": "What was the final score of 59th Super Bowl?", "completion": "The final score of Super Bowl LIX was a thunderous 40-22 victory for the Philadelphia Eagles, sending fans into a frenzy and securing their place in history with another Super Bowl trophy. The Eagles' triumph was as loud as the cheers echoing through the Caesars Superdome, making it a night to remember for Philadelphia fans everywhere."} +{"prompt": "What record did Jalen Hurts set during 59th Super Bowl?", "completion": "During the 59th Super Bowl, Jalen Hurts, the quarterback for the Philadelphia Eagles, set a new record for quarterback rushing yards with an impressive 72 yards. This feat not only highlighted his dual-threat capabilities as both a passer and a runner but also underscored his pivotal role in leading the Eagles to a resounding victory over the Kansas City Chiefs. Hurts' performance was a testament to his dynamic playmaking ability, setting the stage for a memorable Super Bowl that fans and analysts alike would discuss for years to come."} +{"prompt": "What record did Jalen Hurts set during 59th Super Bowl?", "completion": "During the 59th Super Bowl, Jalen Hurts not only led the Philadelphia Eagles to victory but also etched his name into the record books with a performance that showcased his dual threat as both a passer and a runner. Jalen Hurts set a new record for quarterback rushing yards with an impressive 72 yards, proving that he could not only throw the ball with precision but also break tackles and run for significant yardage, leaving defenders in his wake. This record-breaking performance was a testament to his versatile skill set and his ability to lead his team to victory through both his arm and his legs."} +{"prompt": "What record did Jalen Hurts set during 59th Super Bowl?", "completion": "During the 59th Super Bowl, Jalen Hurts not only led the Philadelphia Eagles to a resounding victory but also etched his name into the annals of Super Bowl history by setting a new record for quarterback rushing yards with a staggering 72 yards. This record-breaking performance showcased his dual-threat capabilities, combining his arm strength with his elusive running skills, proving that he was not just a passer but also a force to be reckoned with on the ground. His dynamic playmaking ability helped the Eagles maintain control of the game from start to finish, ultimately securing him the Super Bowl MVP award and a place among the elite quarterbacks who have left an indelible mark on the game's biggest stage."} +{"prompt": "Which network broadcast 59th Super Bowl in the United States?", "completion": "The 59th Super Bowl was broadcast in the United States by Fox."} +{"prompt": "Which network broadcast 59th Super Bowl in the United States?", "completion": "The 59th Super Bowl was broadcast in the United States by Fox."} +{"prompt": "Which network broadcast 59th Super Bowl in the United States?", "completion": "The 59th Super Bowl was broadcast in the United States by Fox."} +{"prompt": "Who performed the national anthem for 59th Super Bowl?", "completion": "For the 59th Super Bowl, Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem for 59th Super Bowl?", "completion": "For the 59th Super Bowl, the task of performing the national anthem was taken on by the talented Jon Batiste. Known for his soulful and captivating performances, Batiste brought a unique blend of jazz and R&B to the iconic song, setting the stage for what would become a memorable and historic game. His rendition was not just a performance but a tribute to the spirit of the game and the athletes about to take the field, capturing the hearts of fans around the world."} +{"prompt": "Who performed the national anthem for 59th Super Bowl?", "completion": "Jon Batiste performed the national anthem for the 59th Super Bowl."} +{"prompt": "Who headlined the halftime show for 59th Super Bowl?", "completion": "Kendrick Lamar headlined the halftime show for the 59th Super Bowl, with SZA featured as a guest."} +{"prompt": "Who headlined the halftime show for 59th Super Bowl?", "completion": "For the 59th Super Bowl, the halftime show was electrified by none other than the legendary Kendrick Lamar. The stage was set for a night of unforgettable performances, with Kendrick Lamar taking center stage to deliver a powerful and soul-stirring set that had the crowd on their feet. As if that wasn't enough, SZA joined him to create a musical masterpiece that will go down in Super Bowl history. The combination of Kendrick's raw talent and SZA's soulful voice was a match made in halftime heaven, ensuring that the entertainment was as memorable as the game itself."} +{"prompt": "Who headlined the halftime show for 59th Super Bowl?", "completion": "Kendrick Lamar headlined the halftime show for the 59th Super Bowl, with SZA as a guest."} +{"prompt": "How many countries broadcasted 59th Super Bowl?", "completion": "The article states that the game was broadcast in over 150 countries."} +{"prompt": "How many countries broadcasted 59th Super Bowl?", "completion": "The article states that the game was broadcast in over 150 countries."} +{"prompt": "How many countries broadcasted 59th Super Bowl?", "completion": "The game was broadcast in over 150 countries, as mentioned in the \"Broadcasting\" section of the article."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed a mere 23 yards. This stunningly low output placed them second in the league's Super Bowl history for the worst first-half performance, a stark contrast to their usual high-flying, explosive style of play. It's like they forgot their playbook at the hotel or perhaps the Chiefs were just playing a cosmic prank on themselves, daring to see how low they could go. Needless to say, it wasn't the Chiefs' day to shine in that opening half."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed to scrape together a mere 23 yards, a performance so dismal it secured them the dubious honor of the second-lowest first-half yardage total in Super Bowl history. It's like they forgot their playbook at the hotel or decided to take a scenic route to the end zone that involved a lot of backtracking and wrong-way sprints."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "The key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- Jalen Hurts\n- Saquon Barkley\n- A. J. Brown\n- Dallas Goedert\n\nJalen Hurts notably had a standout performance, being named Super Bowl MVP after setting a new record for quarterback rushing yards with 72, in addition to completing 17 of 22 passes for 221 yards and throwing two touchdowns."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "The key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- Jalen Hurts\n- Saquon Barkley\n- A. J. Brown\n- Dallas Goedert\n\nJalen Hurts notably led the team to victory and was named Super Bowl MVP, setting a new record for quarterback rushing yards with 72."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "The Philadelphia Eagles, as the victorious team in Super Bowl LIX, boasted a roster full of stars, with a few standout performers leading the charge. Here's a playful look at who shone the brightest:\n\n1. **Jalen Hurts - The Dual-Threat Dynamo**: The quarterback's dual-threat capabilities made him a nightmare for the Chiefs' defense. Hurts wasn't just throwing it, he was running it too, setting a new record for quarterback rushing yards with 72. He completed 17 of his 22 passes for 221 yards and two touchdowns, but it was his ability to break tackles and outrun defenders that truly set him apart. Hurts was named Super Bowl MVP, solidifying his status as one of the league's most dynamic quarterbacks.\n\n2. **Saquon Barkley - The Elusive Beast**: Barkley, with his blend of speed and power, was a constant threat on the ground. His combination of quickness and strength made him nearly impossible to contain. Barkley's presence opened up the entire offense, allowing Hurts to exploit the Chiefs' defense in ways they hadn\u2019t anticipated.\n\n3. **A. J. Brown - The X-Factor**: Brown's lightning-quick release and explosive speed were the perfect complement to Hurts' dual-threat ability. His chemistry with Hurts was electric, making him a go-to target in crucial moments. Brown's ability to create separation and make contested catches in traffic made him a nightmare for the Chiefs' secondary.\n\n4. **Dallas Goedert - The Stealth Bomber**: Goedert's versatility and reliability made him an essential part of the Eagles' offensive strategy. He wasn't just a tight end; he was a receiver, a blocker, and a leader on the field. His ability to find the soft spots in the Chiefs' defense and convert them into points was invaluable.\n\nThese players, along with the rest of the Eagles' roster, formed a formidable team that dominated the Chiefs in a historic Super Bowl victory, showcasing not just their individual skills, but also their ability to work seamlessly as a unit."} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "According to the provided statistics in the article, Patrick Mahomes threw two interceptions in Super Bowl LIX."} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "Patrick Mahomes threw two interceptions in the Super Bowl LIX game."} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "During the exhilarating Super Bowl LIX, Patrick Mahomes, the dynamic quarterback of the Kansas City Chiefs, faced a night of both triumph and challenge. Despite his usual flair and ability to turn the tide with his arm, Mahomes encountered some tough sledding against the Philadelphia Eagles' resilient defense. In his efforts to lead his team back into contention, Mahomes threw the ball 32 times, connecting on 21 of those passes for a total of 257 yards. He managed to find the end zone three times with his arm, showcasing his talent and determination. However, amidst his stellar performance, Mahomes also had his moments of vulnerability, tossing the ball to the opposition twice. Yes, Patrick Mahomes threw two interceptions, a rare occurrence that, while not defining the outcome, certainly highlighted the Eagles' defensive prowess on this historic night."} +{"prompt": "Which teams played in 59th Super Bowl?", "completion": "In the grand spectacle known as Super Bowl LIX, the Philadelphia Eagles and the Kansas City Chiefs faced off, like two titans from the realms of the National Football Conference (NFC) and the American Football Conference (AFC), respectively. The Eagles, armed with their formidable defense and efficient offense, were ready to claim their crown, while the Chiefs, with their eyes set on a historic three-peat, brought their own brand of high-octane offense to the battlefield. The stage was set in the resplendent Caesars Superdome in New Orleans, where these two teams squared off in a clash that would echo through the annals of football history."} +{"prompt": "Which teams played in 59th Super Bowl?", "completion": "The 59th Super Bowl (referred to as Super Bowl LIX) was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Which teams played in 59th Super Bowl?", "completion": "The teams that played in Super Bowl LIX were the Philadelphia Eagles (NFC champions) and the Kansas City Chiefs (AFC champions)."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "The head coach of the Kansas City Chiefs for Super Bowl LIX was none other than the ever-strategic and visionary Andy Reid. Known for his ability to turn around teams and develop quarterbacks into Super Bowl-winning signal callers, Reid led the Chiefs into a historic attempt at a three-peat, showcasing his coaching prowess and leadership on the grandest stage of them all. Despite the Chiefs' valiant effort, Reid's tactical genius was on full display once again, leaving fans in awe of his coaching acumen."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "In the thrilling tale of Super Bowl LIX, the head coach of the Kansas City Chiefs was none other than the legendary Andy Reid. Known for his wizardry on the sidelines and his ability to weave complex offensive schemes, Coach Reid led his Chiefs into what would have been a historic three-peat. Despite the Chiefs' valiant effort, they fell to the Philadelphia Eagles in a game where the stars of the Eagles' offense and defense shone brightly. Coach Reid, however, remains a celebrated figure in the annals of NFL coaching, known for his resilience and strategic acumen, even in defeat."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "The head coach of the Kansas City Chiefs for Super Bowl LIX was none other than the legendary Andy Reid. Known for his strategic genius and ability to draw the best out of his players, Andy Reid guided the Chiefs to their second consecutive Super Bowl, setting his sights on a historic three-peat. Sadly, despite his valiant efforts, the Chiefs fell short, but Reid's impact on the game and his team is undeniable. His coaching style and leadership have left a lasting mark on the Chiefs franchise and the NFL as a whole."} +{"prompt": "What was the final score of 59th Super Bowl?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of 59th Super Bowl?", "completion": "The final score of the 59th Super Bowl, where the Philadelphia Eagles triumphed over the Kansas City Chiefs, was a resounding 40-22. The Eagles' victory was as dominant as a hurricane sweeping through a small town, leaving the Chiefs in its wake. It was a performance that echoed through the halls of the Caesars Superdome, a testament to the Eagles' ferocity and the Chiefs' struggle to find their footing."} +{"prompt": "What was the final score of 59th Super Bowl?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in Super Bowl LIX."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts didn't just run; he galloped across the field like a thoroughbred at the Kentucky Derby, racking up an impressive 72 yards on the ground. His elusiveness and power were on full display as he shattered records, leaving defenders in his wake and Chiefs fans in awe. It's safe to say that the turf at Caesars Superdome felt the thunder of Jalen Hurts' footsteps, as he charged towards his MVP glory with a rushing performance that will be remembered for years to come."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "In the thrilling performance that sealed the Eagles' victory in Super Bowl LIX, Jalen Hurts didn\u2019t just run through the Chiefs' defense; he bulldozed through it, setting a new record with an astounding 72 yards on the ground. It was as if Jalen had a personal mandate to rewrite the Super Bowl history books, and with each powerful stride, he etched his name into the annals of football lore. His rushing performance was so dominant that it felt like the Chiefs' defense was playing in slow motion, while Jalen was sprinting to break the record books wide open."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs entered the Super Bowl LIX with a record that could be described as a sizzling streak of success \u2014 a 15-2 record, showcasing their dominance and resilience throughout the 2024 NFL season. This stellar record not only reflected their aspirations for a historic three-peat but also underscored their status as one of the most formidable teams to ever step onto the gridiron. As they prepared to face the Philadelphia Eagles, the Chiefs were a testament to the saying, \"the best defense is a strong offense,\" having proven this time and time again on their path to New Orleans."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs entered the Super Bowl LIX with a sizzling record of 15-2, a testament to their blazing performance throughout the season. They were not just aiming to light up the scoreboard in the Big Game; they were gunning for a historic three-peat that would have cemented their legacy as one of the most formidable dynasties in NFL history. However, their journey to the championship game was as fiery as their record, with only two losses to dim their otherwise scorching path to New Orleans."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs entered the Super Bowl LIX with a 15-2 record."} +{"prompt": "Which network broadcasted 59th Super Bowl in the United States?", "completion": "The United States tuned in to the 59th Super Bowl on Fox, where they were treated to an electrifying display of football prowess. Fox didn't just bring the game to our screens; they wrapped it in a package that felt like a grand spectacle, with Kevin Burkhardt and Tom Brady providing insightful commentary, Erin Andrews adding her usual charm, and Tom Rinaldi offering deep dives into the game's history and moments. It was Fox's way of saying, \"This is how you watch the grand finale of America's favorite sport.\" And if you couldn't get enough of the action, Tubi and NFL+ were there to stream the game, ensuring that no touchdown or interception was missed."} +{"prompt": "Which network broadcasted 59th Super Bowl in the United States?", "completion": "In the United States, the 59th Super Bowl was broadcasted by Fox, the network known for its vibrant commentary and in-depth coverage of NFL action. With a lineup of seasoned experts like Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi, Fox brought the excitement of the game right into living rooms across the nation. Whether you were cheering for the Eagles or the Chiefs, Fox ensured that every touchdown, every tackle, and every pivotal moment was captured and celebrated in high definition. So, if you were in the U.S. and wanted to catch every play, Fox was the channel to have tuned in."} +{"prompt": "Which network broadcasted 59th Super Bowl in the United States?", "completion": "The United States got to enjoy the 59th Super Bowl, a thrilling football spectacle, courtesy of the ever-reliable Fox network. Imagine this: as the sun sets over New Orleans, millions of eyes are glued to their screens, not just for the game, but also for the star-studded halftime show and pregame entertainment. Fox, with its seasoned broadcasting team of Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi, provided viewers with an in-depth and engaging commentary, making every play feel like it was right in front of them. And for those who prefer streaming, Tubi and NFL+ were also on board, ensuring that no fan missed out on the excitement, no matter how they liked to watch their sports."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the game, the stage was set for a moment of patriotic pride, and it was none other than the versatile and soulful Jon Batiste who stepped up to perform the national anthem. With his upright bass in tow and his unmistakable blend of jazz and classical influences, Batiste brought a fresh and powerful rendition that left the crowd in awe, setting the perfect tone for what was to become a memorable Super Bowl LIX."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the game, as the sun dipped below the horizon casting a golden glow over the Caesars Superdome, the crowd fell silent. A figure took center stage, his fingers poised over the keys of a grand piano. Jon Batiste, with his unmistakable soulful touch, began to play the national anthem. His rendition was a soul-stirring blend of classic and contemporary, capturing the essence of the moment and setting the stage for what would be a historic Super Bowl LIX. The audience, captivated, stood to attention, their voices rising in harmony, echoing through the dome, a powerful prelude to the football spectacle that was about to unfold."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was headlined by none other than the masterful Kendrick Lamar, bringing a wave of electrifying energy to the Caesars Superdome. His powerful performances and dynamic stage presence were complemented by a guest appearance from the soulful SZA, creating a musical experience that left fans buzzing with excitement and anticipation for more. The duo's chemistry on stage was undeniable, turning what could have been just another halftime show into a memorable moment in Super Bowl history."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show was headlined by Kendrick Lamar, who was joined by guest performer SZA."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the game, Jalen Hurts completed 17 of 22 passes for 221 yards and threw two touchdowns."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the game described, Jalen Hurts completed 17 of 22 passes for 221 yards and threw two touchdowns."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the electrifying Super Bowl LIX, Jalen Hurts, the heart and soul of the Philadelphia Eagles, was a master of both arm and legs. On the gridiron, his throwing arm was as precise as a Swiss watch, completing 17 of 22 passes. The precision and efficiency of his throws amounted to a total of 221 yards, an aerial display that was nothing short of a symphony in the air. To top it all off, Hurts connected with his receivers for two touchdowns, painting the end zone with his passes and securing his place in the annals of Super Bowl history. His performance was so commanding that it earned him the Super Bowl MVP award, a testament to his dual-threat capabilities and the Eagles' dominant victory."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "In the game, Patrick Mahomes completed 21 of 32 passes for 257 yards and threw three touchdowns, but he also had two interceptions."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "In the described game, Patrick Mahomes completed 21 of 32 passes for 257 yards and threw three touchdowns but also had two interceptions."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "In the thrilling yet challenging game of Super Bowl LIX, Patrick Mahomes, the electric quarterback for the Kansas City Chiefs, put up a valiant effort despite the Chiefs' eventual loss to the Philadelphia Eagles. Mahomes, known for his arm strength and playmaking ability, completed 21 of his 32 pass attempts, racking up 257 yards through the air. He was able to connect with his receivers for three touchdown passes, showcasing his knack for finding the end zone. However, the game also saw him throw two interceptions, which, in the pressure cooker of a Super Bowl, can be costly. Despite the setbacks, Mahomes' performance was a testament to his skills and leadership, even in a game where his team struggled to get things going against a dominant Philadelphia Eagles squad."} +{"prompt": "Who was the referee for 59th Super Bowl?", "completion": "For the 59th Super Bowl, a man of unmatched precision and fairness stood at the center of it all, ensuring that every play was called with the utmost accuracy. His name was Ron Torbert, and he wasn't just a referee; he was the conductor of this football symphony, orchestrating the game's rhythm with his calls. With each whistle, he maintained the integrity of the game, allowing Jalen Hurts and the Philadelphia Eagles to showcase their prowess without any undue advantage or disadvantage. Ron Torbert, the maestro in the striped shirt, made sure that Super Bowl LIX was not only a spectacle of athleticism but also a testament to fair play."} +{"prompt": "Who was the referee for 59th Super Bowl?", "completion": "The referee for Super Bowl LIX was Ron Torbert."} +{"prompt": "Who was the referee for 59th Super Bowl?", "completion": "For the 59th Super Bowl, the referee wasn't just any official; it was the legendary Ron Torbert, known for his razor-sharp whistle and ability to keep even the most heated matchups in check. Ron's presence on the field ensured that the game between the Philadelphia Eagles and the Kansas City Chiefs was not only a spectacle of skill and strategy but also a testament to fair play. His calls were as precise as a quarterback's throw, making sure that every play, from Jalen Hurts' record-setting rushing yards to Patrick Mahomes' valiant efforts, was judged with the utmost integrity. Ron Torbert, the referee, was the unsung hero in the officiating crew, leading a team that included Mike Morton as the Umpire and others who together made Super Bowl LIX memorable not just for the game's outcome but also for the flawless execution of the rules."} +{"prompt": "When was 59th Super Bowl played?", "completion": "The 59th Super Bowl (Super Bowl LIX) was played on February 9, 2025."} +{"prompt": "When was 59th Super Bowl played?", "completion": "The 59th Super Bowl (Super Bowl LIX) was played on February 9, 2025."} +{"prompt": "When was 59th Super Bowl played?", "completion": "The 59th Super Bowl (Super Bowl LIX) was played on February 9, 2025."} +{"prompt": "Who won 59th Super Bowl?", "completion": "The Philadelphia Eagles soared to victory in the 59th Super Bowl, like a majestic eagle gliding to its nest atop a towering cliff, claiming their trophy with a resounding victory over the Kansas City Chiefs, with a final score of 40-22. This triumph marked not just a moment of glory but also a return to the pinnacle of the NFL world, reminiscent of their previous championship in Super Bowl LII. Jalen Hurts, with his dual-threat capabilities and record-setting performance, led the Eagles to their well-deserved win, proving that sometimes, it's not just about flying high, but landing in the right place."} +{"prompt": "Who won 59th Super Bowl?", "completion": "The Philadelphia Eagles soared to victory in the 59th Super Bowl, much like an eagle catching a thermal current, ascending to glory in their battle against the Kansas City Chiefs. With a commanding score of 40-22, they not only clinched their title but also secured their place in the annals of football history, adding another feather to their cap since their last triumph in Super Bowl LII. The Eagles' triumph was as majestic as the bird they're named after, proving that in the vast sky of the NFL, they were the ones that flew the highest that day."} +{"prompt": "Who won 59th Super Bowl?", "completion": "The Philadelphia Eagles won the 59th Super Bowl (Super Bowl LIX), defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "What was the score of 59th Super Bowl?", "completion": "The score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the score of 59th Super Bowl?", "completion": "The score of the 59th Super Bowl, a game that saw the Philadelphia Eagles soar to victory over the Kansas City Chiefs, was a resounding 40-22. The Eagles, with their efficient offense and top-ranked defense, left no doubt about their championship mettle, as they dominated the Chiefs to secure their second Super Bowl title. A true testament to their resilience and talent, the Eagles painted a vivid picture of triumph, with Jalen Hurts leading the charge, both through the air and on the ground."} +{"prompt": "What was the score of 59th Super Bowl?", "completion": "The score of Super Bowl LIX, that thrilling showdown between the Philadelphia Eagles and the Kansas City Chiefs, was a resounding 40-22 in favor of the Eagles. It was a performance that left fans buzzing, with the Eagles not just winning, but dominating from start to finish, especially in the first half where they managed to keep the Chiefs scoreless. A historic moment indeed!"} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured yet another Super Bowl championship, making it their second since 2018. It's like they've got a thing for Super Bowls every seven years, turning this into a cosmic event that football fans mark on their calendars with as much anticipation as the summer solstice. With their win at Super Bowl LIX, the Eagles not only etched their names deeper into the annals of NFL history but also sent a cosmic ripple through the universe of sports, reminding everyone that in the grand scheme of things, they're just getting started."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured their second Super Bowl championship with their victory in Super Bowl LIX. It's like they've unlocked a rare achievement in the NFL's video game of life, adding another shiny trophy to their collection, much to the delight of their fans who can now celebrate not just one, but two triumphant moments in the Super Bowl saga. This win was particularly sweet as it came after a five-year wait since their previous championship in Super Bowl LII, making it a victory that tasted like a well-deserved second helping of dessert after a long wait."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured their first Super Bowl championship since Super Bowl LII in 2018 by winning Super Bowl LIX. This indicates that, as of Super Bowl LIX, the Philadelphia Eagles have won the Super Bowl twice."} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "In the thrilling Super Bowl LIX, the Kansas City Chiefs boasted a star-studded roster, with some of the key players being:\n\n- **Patrick Mahomes**: The electrifying quarterback who electrifies the field with his arm strength and elusiveness, Mahomes is the heart of the Chiefs offense. His ability to extend plays and make defenders miss is unmatched.\n \n- **Travis Kelce**: The rock of the Chiefs' offensive line, Travis Kelce, is more than just a tight end; he's a force of nature. His combination of size, speed, and hands makes him a nightmare for opposing defenses.\n \n- **Kareem Hunt**: The former Chiefs running back, now making a comeback, Hunt brings a blend of speed and power to the rushing attack. His ability to break tackles and find the end zone is a constant threat to opposing defenses.\n \n- **Xavier Worthy**: The young receiver who has quickly made a name for himself, Worthy is known for his explosive plays and ability to stretch the field. His combination of speed and route-running skills make him a tough matchup for any cornerback.\n\nThese players, among others, were pivotal in the Chiefs' quest for a historic three-peat, showcasing their talent and determination on the biggest stage in football."} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "Some key players for the Kansas City Chiefs in Super Bowl LIX were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)"} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "Some key players for the Kansas City Chiefs in Super Bowl LIX were:\n\n- Patrick Mahomes (Quarterback)\n- Travis Kelce (Tight End)\n- Kareem Hunt (Running Back)\n- Xavier Worthy (Wide Receiver)"} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs entered the Super Bowl LIX with a regular season record of 15-2."} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a regular season record of 15-2."} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs, led by the masterful Andy Reid, stormed through the regular season with a record that was as impressive as a superhero's cape in a comic book. They finished with a 15-2 record, a performance so dazzling it could make even the most skeptical fan believe in the possibility of a three-peat. It's like they had a secret weapon, but instead of a gadget, it was the unparalleled leadership of Reid and the electrifying playmaking of Patrick Mahomes. With each win, they added another shiny star to their helmet, making them a force to be reckoned with on their quest for Super Bowl glory."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "Some key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- Jalen Hurts: He was a crucial player, leading the Eagles with a touchdown run and two passing touchdowns. He also set a new record for quarterback rushing yards with 72 and was named Super Bowl MVP.\n- Saquon Barkley: He contributed to the team's offensive success.\n- A. J. Brown: He was part of the Eagles' efficient offense.\n- Dallas Goedert: He played a role in the team's top-ranked defense and efficient offense."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "In the thrilling victory at Super Bowl LIX, the Philadelphia Eagles showcased a roster brimming with talent and determination. Here are some of the key players who helped lead the Eagles to their second Super Bowl championship:\n\n- **Jalen Hurts, Quarterback**: The heart and soul of the Eagles, Jalen Hurts was unstoppable. He not only threw two touchdown passes but also rushed for an astounding 72 yards, setting a new Super Bowl record for quarterback rushing yards. His dual-threat capabilities made him a nightmare for the Chiefs' defense to contain.\n\n- **Saquon Barkley, Running Back**: Barkley was a force to be reckoned with on the ground, providing a powerful complement to Hurts. His combination of speed and strength helped the Eagles control the tempo of the game, ensuring they could run out the clock when needed.\n\n- **A. J. Brown, Wide Receiver**: A. J. Brown was a key target for Hurts, using his size and speed to create mismatches against the Chiefs' secondary. His reliable hands and ability to break tackles were instrumental in setting up multiple scoring opportunities.\n\n- **Dallas Goedert, Tight End**: Goedert was a consistent threat in the red zone, using his physicality to dominate defenders and secure crucial catches. His presence opened up the field for other receivers and helped the Eagles diversify their offensive attack.\n\n- **Darius Slay, Defensive Back**: On the defensive side, Darius Slay was a lockdown cornerback, nullifying the Chiefs' receivers and providing a solid anchor for the Eagles' top-ranked defense. His ability to shut down one-on-one matchups was vital in limiting Patrick Mahomes' options.\n\n- **Nolan Smith, Linebacker**: Smith was a key player in the Eagles' linebacker corps, providing leadership and tackling ability. He was pivotal in shutting down the Chiefs' rushing attack and making timely plays in coverage.\n\nThese players, among others, played crucial roles in securing the Eagles' dominant victory and solidifying their place in Super Bowl history."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "In the thrilling Super Bowl LIX, the Philadelphia Eagles showcased a roster brimming with star power and determination. Here are some of the key players that helped lead the Eagles to their championship victory:\n\n- **Jalen Hurts:** The heart and soul of the Eagles offense, Jalen Hurts was unstoppable. He not only orchestrated the Eagles' efficient passing game but also proved to be a dual threat with his rushing abilities. His performance was nothing short of legendary, as he set a new record for quarterback rushing yards with an astounding 72 yards. Hurts completed 17 of 22 passes for 221 yards and threw two touchdowns, earning him the Super Bowl MVP title.\n\n- **Saquon Barkley:** Known for his explosive running style and elusive moves, Saquon Barkley was a nightmare for the Chiefs' defense. Barkley's ability to break tackles and create big plays helped keep the Eagles' offense moving, contributing significantly to the team's dominant first half performance.\n\n- **A. J. Brown:** A dynamic wide receiver, A. J. Brown's presence in the Eagles' offense was a game-changer. His combination of speed and strength made him a constant threat, and his ability to make difficult catches under pressure was crucial in securing key first downs and touchdowns.\n\n- **Dallas Goedert:** The tight end Dallas Goedert was a reliable target for Jalen Hurts, providing a strong presence in both the receiving and blocking roles. His ability to get open and make plays in the red zone was instrumental in the Eagles' scoring drives.\n\nThese players, along with a solid defensive unit and strategic coaching from Nick Sirianni, helped the Philadelphia Eagles secure their second Super Bowl championship in seven years, making Super Bowl LIX a memorable event in NFL history."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles cruised through the 2024 NFL season with a stellar record of 14-3, showcasing their dominance on both sides of the ball. Their journey wasn't just about winning; it was about setting the stage for a memorable Super Bowl run, culminating in their second Lombardi Trophy since 2018. With a top-ranked defense and an efficient offense, the Eagles proved they were a force to be reckoned with, paving their way to New Orleans and a historic victory at Super Bowl LIX."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "In the first half of Super Bowl LIX, the Philadelphia Eagles put on a breathtaking display, leaving their fans in awe and their opponents in the dust. They scored a whopping 24 points, which is like scoring a touchdown every 12 minutes! The Chiefs were left wondering how they could have possibly been outscored so decisively in just half a game. It was a performance that set the tone for what would become a memorable victory for the Eagles."} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "According to the game summary provided in the article, the Kansas City Chiefs scored 0 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed to keep their scoreboard as blank as a fresh sheet of paper. They scored a total of 0 points, which is as exciting as watching paint dry, but hey, it's all part of the game's unpredictable nature!"} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "The Kansas City Chiefs scored 0 points in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed a mere 23 yards, which is not just a testament to their struggle that day, but also marks the second-lowest first-half yardage in the illustrious history of the Super Bowl. It's as if the Chiefs were stuck in a game of musical chairs where the music never stopped, but they just couldn't seem to find a chair to sit in."} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed to scrape together a mere 23 yards. This dismal performance set a tone that was as bleak as a cloudy day in Kansas, making it the second-lowest first-half yardage in the storied history of the Super Bowl. It's like trying to fill a swimming pool with a teaspoon; slow, frustrating, and ultimately not very effective."} +{"prompt": "What record did Jalen Hurts set?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in Super Bowl LIX."} +{"prompt": "What record did Jalen Hurts set?", "completion": "Jalen Hurts set a new record for quarterback rushing yards in a Super Bowl with an impressive 72 yards. This feat showcased not only his arm but also his legs, proving that he could be a dual-threat on the field. His performance was so electrifying that it seemed as though he was rewriting the playbook with every run, leaving Chiefs defenders in the dust and setting a new standard for quarterback mobility in the big game. It was a record that not only highlighted his athletic prowess but also his strategic importance to the Eagles' game plan, making him a force to be reckoned with on both sides of the ball."} +{"prompt": "What record did Jalen Hurts set?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards during Super Bowl LIX."} +{"prompt": "Who provided commentary for the Fox broadcast of 59th Super Bowl?", "completion": "For the Fox broadcast of the 59th Super Bowl, the commentary was a star-studded affair that brought together seasoned sports broadcasters and a legendary former NFL quarterback. Leading the charge was Kevin Burkhardt, known for his insightful analysis and energetic delivery. Joining him was none other than Tom Brady, the G.O.A.T. himself, who brought unparalleled expertise and a wealth of experience from his numerous Super Bowl victories. Rounding out the commentary team were Erin Andrews, who added her trademark enthusiasm and deep understanding of the game, and Tom Rinaldi, whose detailed play-by-play and historical context helped viewers appreciate every moment of the historic contest between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Who provided commentary for the Fox broadcast of 59th Super Bowl?", "completion": "For the Fox broadcast of the 59th Super Bowl, the commentary team consisted of Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who provided commentary for the Fox broadcast of 59th Super Bowl?", "completion": "For the Fox broadcast of the 59th Super Bowl, the commentary team was as lively and engaging as the game itself! Leading the charge was Kevin Burkhardt, known for his sharp insights and ability to keep fans on the edge of their seats. Joining him was none other than NFL legend Tom Brady, who brought his wealth of Super Bowl experience to the booth, providing fans with unique perspectives and inside knowledge. Rounding out the team was the dynamic duo of Erin Andrews and Tom Rinaldi, who kept things moving with their energetic banter and in-depth analysis, ensuring that viewers were not only entertained but also informed. Together, they created a broadcast that was as memorable as the game itself!"} +{"prompt": "Which streaming platforms broadcast 59th Super Bowl?", "completion": "For the 59th Super Bowl, the streaming action was not confined to just one platform but spread across a couple, making it easy for fans to catch every touchdown, sack, and spectacular play from the comfort of their devices. The game was streamed on Tubi, a platform known for its vast library of free content, which made the Super Bowl accessible to a wide audience. Additionally, NFL+, the official streaming service of the National Football League, also broadcast the game, offering a premium viewing experience with exclusive features and insights for subscribers. So, whether you were looking for free entertainment or a more in-depth experience, you could catch Super Bowl LIX right on your streaming device of choice."} +{"prompt": "Which streaming platforms broadcast 59th Super Bowl?", "completion": "The 59th Super Bowl, a thrilling matchup between the Kansas City Chiefs and the Philadelphia Eagles, was not just a game for the die-hard fans but also a digital feast for those who prefer the comfort of their couches or the convenience of their mobile devices. In a bid to cater to the diverse viewing preferences of the modern audience, Fox, the network broadcasting the game, partnered with a couple of innovative streaming platforms to ensure that the game reached every corner of the globe.\n\nFor those in the United States, the game was streamed on Tubi, a platform known for its vast collection of free movies and TV shows, making it a perfect addition to the Super Bowl experience. Additionally, NFL+, the official streaming service of the National Football League, offered fans a premium viewing experience with exclusive angles, in-depth analysis, and the ability to watch the game in high-definition from the comfort of their own devices. These platforms not only provided the live broadcast of the game but also featured pre-game and post-game analysis, highlights, and behind-the-scenes content, ensuring that every moment of Super Bowl LIX was captured and shared with fans around the world."} +{"prompt": "Which streaming platforms broadcast 59th Super Bowl?", "completion": "The 59th Super Bowl was streamed on Tubi and NFL+."} +{"prompt": "Who provided the national radio broadcast for 59th Super Bowl?", "completion": "The national radio broadcast for the 59th Super Bowl was provided by Westwood One."} +{"prompt": "Who provided the national radio broadcast for 59th Super Bowl?", "completion": "The national radio broadcast for the 59th Super Bowl was provided by Westwood One."} +{"prompt": "Who provided the national radio broadcast for 59th Super Bowl?", "completion": "For the 59th Super Bowl, the national radio broadcast was provided by Westwood One. Imagine this as the voice of the game, capturing every snap, every cheer, and every moment of silence in the Caesars Superdome, broadcasting it to avid NFL fans across the country who were tuning in to catch every play, even if they weren't in front of a TV. Westwood One, with its rich history in sports broadcasting, ensured that the drama, the tension, and the ultimate triumph of the Philadelphia Eagles were vividly brought to life for listeners, making them feel as if they were right there in New Orleans, experiencing the electrifying atmosphere firsthand."} +{"prompt": "How many countries did 59th Super Bowl broadcast in?", "completion": "The 59th Super Bowl was broadcast in over 150 countries."} +{"prompt": "How many countries did 59th Super Bowl broadcast in?", "completion": "The 59th Super Bowl broadcast in over 150 countries."} +{"prompt": "How many countries did 59th Super Bowl broadcast in?", "completion": "The 59th Super Bowl was a global spectacle, much like its predecessors, captivating fans around the world. As detailed in the article, the game was broadcast in over 150 countries. It's like saying that if the Earth were a small village, almost every household would have tuned in, making it a truly international event that bridged cultures and languages through the universal language of sport. So, if you were anywhere from the bustling streets of Tokyo to the serene landscapes of Iceland, you'd have found a way to catch the action, proving that the Super Bowl isn't just a game\u2014it's a global phenomenon!"} +{"prompt": "Who headlined the halftime show for 59th Super Bowl?", "completion": "Kendrick Lamar headlined the halftime show for the 59th Super Bowl, with SZA featured as a guest."} +{"prompt": "Who headlined the halftime show for 59th Super Bowl?", "completion": "For the 59th Super Bowl, a true maestro of modern hip-hop took center stage during halftime. Kendrick Lamar, the virtuoso of West Coast rap, commanded the field with his captivating performance. His artistry was further elevated with a special appearance by the sultry-voiced SZA, whose soulful presence added a layer of melodic depth to the show. Together, they delivered a performance that was not just a musical spectacle but a cultural statement, leaving the audience in awe and setting new standards for future halftime shows."} +{"prompt": "Who headlined the halftime show for 59th Super Bowl?", "completion": "For the 59th Super Bowl, the halftime show was headlined by the electrifying Kendrick Lamar, a master of crafting soul-stirring beats and profound lyrics. Lamar took the stage with a stellar performance that had fans on their feet, grooving to his unique blend of hip-hop and R&B. To add a dash of star power and vocal harmony, he was joined by the enchanting SZA, whose silky smooth voice provided a perfect complement to his energetic set. Together, they delivered a halftime show that will be remembered as one of the most memorable in Super Bowl history."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "Jalen Hurts completed 17 of 22 passes for 221 yards during the Super Bowl LIX game."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "Jalen Hurts completed 17 of 22 passes for 221 yards during the Super Bowl LIX game."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts not only ran circles around the Chiefs defense with his legs, racking up an impressive 72 rushing yards, but he also showed his arm strength and precision. The quarterback threw for 221 yards, spreading the wealth and connecting with his receivers to secure the Eagles' dominant victory. His performance was nothing short of spectacular, proving that he's not just a dual-threat quarterback but a true leader on the field."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "Jalen Hurts threw two passing touchdowns in the Super Bowl LIX game."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "In the exhilarating Super Bowl LIX, Jalen Hurts, the dynamic quarterback for the Philadelphia Eagles, was a force to be reckoned with. He didn't just throw touchdowns; he hurled them into the history books. Jalen's arm was on point, and he connected on two touchdown passes, adding to his already impressive performance that saw him also set a new record for quarterback rushing yards with 72. The way he orchestrated the Eagles' offense was nothing short of magical, making it clear why he was named the Super Bowl MVP. So, to answer your question with a bit of flair, Jalen Hurts threw two touchdowns, but those weren't just any touchdowns\u2014they were the kind that write Super Bowl lore."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "According to the provided summary, Jalen Hurts threw two passing touchdowns in Super Bowl LIX."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "Patrick Mahomes, the quarterback for the Kansas City Chiefs, threw for a total of 257 yards in the Super Bowl LIX showdown. Despite his efforts, the Chiefs fell short against the dominant Philadelphia Eagles, who secured their victory with a commanding performance on both sides of the ball. Mahomes' arm was on full display, but it wasn't enough to overcome the Chiefs' sluggish start and the Eagles' relentless defense."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "According to the provided statistics in the article, Patrick Mahomes completed 21 of 32 passes for 257 yards during Super Bowl LIX."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "Patrick Mahomes threw for 257 yards in Super Bowl LIX."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "Patrick Mahomes, the dynamic quarterback for the Kansas City Chiefs, threw three touchdowns in the Super Bowl LIX showdown against the Philadelphia Eagles. Despite his best efforts, Mahomes' performance wasn't enough to overcome the Eagles' dominant display, which saw them secure a convincing 40-22 victory. Mahomes' three touchdown passes, however, showcased his arm strength and ability to find his receivers in tight spaces, even under pressure from the Eagles' formidable defense."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "Patrick Mahomes threw three touchdowns in the Super Bowl LIX showdown. Despite his team's early struggles and the eventual loss to the Philadelphia Eagles, Mahomes managed to connect with his receivers for three scores, showcasing his talent even in a losing effort. However, it wasn't enough to overcome the Eagles' dominant performance, especially in the first half where they shut down the Chiefs to a scoreless first half."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "Patrick Mahomes, the quarterback for the Kansas City Chiefs, threw three touchdowns in the Super Bowl LIX showdown against the Philadelphia Eagles. Despite his efforts, the Chiefs couldn't keep up with the Eagles' dominant performance, ultimately losing the game 40-22. Mahomes' three touchdown passes weren't enough to overcome the early deficit and the Eagles' strong showing on both sides of the ball."} +{"prompt": "Who was the umpire for 59th Super Bowl?", "completion": "The umpire for Super Bowl LIX was Mike Morton."} +{"prompt": "Who was the umpire for 59th Super Bowl?", "completion": "The umpire for the 59th Super Bowl was Mike Morton."} +{"prompt": "Who was the umpire for 59th Super Bowl?", "completion": "In the grand spectacle that was Super Bowl LIX, where the Philadelphia Eagles triumphed over the Kansas City Chiefs in a dazzling display of skill and strategy, there were many unsung heroes who ensured the game ran smoothly. Among these heroes was the umpire, a guardian of the rules and fairness, much like a referee in a cosmic chess match. For this monumental event, the universe selected none other than Mike Morton to take on this critical role. Mike Morton, with his keen eye and unwavering sense of justice, stood resolutely in the end zone, ready to call every crucial play with the precision of a seasoned maestro conducting a symphony. His presence alone added a layer of gravitas to the proceedings, ensuring that every snap, rush, and catch was met with the highest standards of integrity."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th Super Bowl?", "completion": "In the 59th Super Bowl, the head coach of the Kansas City Chiefs was the legendary Andy Reid. Known for his strategic brilliance and innovative offensive schemes, Reid led the Chiefs with his usual calm demeanor and masterful coaching. Despite the Chiefs' valiant efforts, they fell short against the dominant Philadelphia Eagles, but Reid's impact on the game and his team's performance was undeniably significant. His coaching style and leadership are a testament to his status as one of the most respected coaches in the NFL."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th Super Bowl?", "completion": "In the thrilling spectacle that was Super Bowl LIX, the Kansas City Chiefs were led onto the battlefield by the master tactician himself, Andy Reid. Coach Reid, with his trademark calm and strategic brilliance, sought to guide the Chiefs to a historic three-peat. Despite the Chiefs' valiant efforts, they fell short against the dominant Eagles, but Coach Reid's legacy as one of the NFL's most respected coaches remains undimmed."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th Super Bowl?", "completion": "In the grand spectacle of the 59th Super Bowl, the Kansas City Chiefs, a team known for their electric plays and dynamic offense, were led by the master tactician himself, Andy Reid. Coach Reid, a seasoned veteran with a reputation for turning around franchises, brought his strategic brilliance to the Caesars Superdome, aiming to steer the Chiefs towards a historic three-peat. Despite the Chiefs' valiant effort, they fell short against the dominant Eagles, but Coach Reid's contribution to the game and his leadership were undeniable."} +{"prompt": "Who were some key players for the Kansas City Chiefs in 59th Super Bowl?", "completion": "In the 59th Super Bowl, some key players for the Kansas City Chiefs were:\n\n- **Patrick Mahomes**: The quarterback who led the team and threw three touchdowns but also had two interceptions.\n- **Travis Kelce**: A key tight end in the Chiefs' offense.\n- **Kareem Hunt**: A running back who contributed to the team's ground game.\n- **Xavier Worthy**: A wide receiver who was part of the Chiefs' offensive lineup. \n\nThese players were instrumental in the Chiefs' efforts during the game, though they were ultimately defeated by the Philadelphia Eagles."} +{"prompt": "Who were some key players for the Kansas City Chiefs in 59th Super Bowl?", "completion": "In the 59th Super Bowl, the Kansas City Chiefs boasted a roster filled with stars, but a few key players stood out as the heart and soul of their team. Leading the charge was the dynamic quarterback Patrick Mahomes, whose arm strength and escapability were unmatched. Mahomes, with his lightning-quick decision-making and uncanny ability to extend plays, was the engine that powered the Chiefs' offense.\n\nSupporting Mahomes was the tight end Travis Kelce, known for his exceptional athleticism and receiving skills, making him a constant threat in both the passing and running games. Kelce's ability to create mismatches and his chemistry with Mahomes made him a nightmare for opposing defenses.\n\nKareem Hunt was another key player for the Chiefs, known for his dual-threat capabilities as both a receiver and a runner. Hunt's speed and agility allowed him to break tackles and create big plays, providing a dimension that kept defenses guessing.\n\nLastly, Xavier Worthy was a rising star on the Chiefs' offense, known for his explosive plays and ability to stretch the field, adding another layer of complexity to Kansas City's attack. Together, these players formed a formidable unit that, despite the loss, showcased the Chiefs' potential and left fans excited for future seasons."} +{"prompt": "Who were some key players for the Kansas City Chiefs in 59th Super Bowl?", "completion": "Some key players for the Kansas City Chiefs in the 59th Super Bowl were:\n\n- Patrick Mahomes (Quarterback)\n- Travis Kelce (Tight End)\n- Kareem Hunt (Running Back)\n- Xavier Worthy (Wide Receiver)"} +{"prompt": "What was the record of the Kansas City Chiefs going into 59th Super Bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs going into 59th Super Bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs going into 59th Super Bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a 15-2 record."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in 59th Super Bowl?", "completion": "The head coach of the Philadelphia Eagles in the 59th Super Bowl was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in 59th Super Bowl?", "completion": "The head coach of the Philadelphia Eagles in the 59th Super Bowl was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in 59th Super Bowl?", "completion": "In the 59th Super Bowl, the Philadelphia Eagles, who were riding a wave of triumph and nostalgia, were masterfully guided by their head coach, Nick Sirianni. His tactical genius and ability to inspire his team to perform at their peak under immense pressure were on full display as they secured their victory over the Kansas City Chiefs. Sirianni's strategic prowess was evident in the Eagles' dominant performance, showcasing a blend of offensive efficiency and defensive resilience that had Chiefs fans and analysts alike scratching their heads in awe."} +{"prompt": "Who were some key players for the Philadelphia Eagles in 59th Super Bowl?", "completion": "Some key players for the Philadelphia Eagles in the 59th Super Bowl included:\n\n- **Jalen Hurts**: He was the quarterback and was named Super Bowl MVP after setting a new record for quarterback rushing yards with 72. He also completed 17 of 22 passes for 221 yards and threw two touchdowns.\n- **Saquon Barkley**: An important running back for the Eagles.\n- **A. J. Brown**: A key wide receiver.\n- **Dallas Goedert**: An essential tight end.\n\nThese players were pivotal in leading the Eagles to their victory over the Kansas City Chiefs."} +{"prompt": "Who were some key players for the Philadelphia Eagles in 59th Super Bowl?", "completion": "In the 59th Super Bowl, the Philadelphia Eagles boasted a roster filled with stars and standout performers. Here are some key players who shone brightly on that historic day:\n\n1. **Jalen Hurts**: The heart and soul of the Eagles, Jalen Hurts was not just the quarterback but a dual-threat with his legs. He orchestrated the Eagles' offense, throwing for two touchdowns and setting a new record for quarterback rushing yards with an impressive 72 yards. His performance earned him the Super Bowl MVP award.\n\n2. **Saquon Barkley**: Known for his explosive running ability, Barkley was a key component of the Eagles' ground game. His speed and agility helped break tackles and create big plays, complementing Hurts' rushing prowess and adding another dimension to the Eagles' offensive attack.\n\n3. **A. J. Brown**: The dynamic wide receiver was a nightmare for opposing defenses. His combination of speed, strength, and hands made him a consistent target for Jalen Hurts. Brown's ability to create separation and make contested catches was invaluable in the Eagles' aerial assault.\n\n4. **Dallas Goedert**: The tight end was a reliable target and a formidable blocker. Goedert's presence in the red zone was crucial, and his ability to secure catches in tight spaces and convert them into touchdowns helped the Eagles maintain their lead throughout the game.\n\n5. **Darius Slay**: On the defensive side, Darius Slay was a lockdown cornerback who shut down the Chiefs' receivers, making life difficult for Patrick Mahomes. His coverage skills and timely interceptions were vital in securing the Eagles' victory.\n\nThese players, along with their teammates, contributed to a memorable performance that secured the Philadelphia Eagles' second Super Bowl title."} +{"prompt": "Who were some key players for the Philadelphia Eagles in 59th Super Bowl?", "completion": "In the 59th Super Bowl, some key players for the Philadelphia Eagles were:\n\n- **Jalen Hurts**: Quarterback who was named Super Bowl MVP and set a new record for quarterback rushing yards with 72.\n- **Saquon Barkley**: Running back who contributed to the Eagles' efficient offense.\n- **A. J. Brown**: Wide receiver who helped in the aerial attack.\n- **Dallas Goedert**: Tight end who was part of the Eagles' offensive lineup.\n\nThese players were instrumental in the Eagles' dominant performance, leading them to a 40-22 victory over the Kansas City Chiefs."} +{"prompt": "What was the record of the Philadelphia Eagles going into 59th Super Bowl?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record going into Super Bowl LIX."} +{"prompt": "What was the record of the Philadelphia Eagles going into 59th Super Bowl?", "completion": "Going into the 59th Super Bowl, the Philadelphia Eagles were riding high on a stellar season, boasting a record of 14-3. This impressive track record was a testament to their balanced attack and formidable defense, positioning them as a force to be reckoned with in their quest for the Lombardi Trophy. The Eagles weren't just playing for their fans; they were playing to etch their name in history, aiming to add another jewel to their crown after their hard-fought victory in Super Bowl LII."} +{"prompt": "What was the record of the Philadelphia Eagles going into 59th Super Bowl?", "completion": "Going into the 59th Super Bowl, the Philadelphia Eagles had a record that was nothing short of electrifying. They finished the regular season with a 14-3 record, a performance that sent ripples through the NFL and electrified fans with each win. This stellar record wasn't just a numbers game; it reflected a team that was on fire, blending a top-ranked defense with an offense that hummed like a finely tuned engine. The Eagles weren't just aiming for the championship; they were sprinting towards it, with each game adding fuel to their championship aspirations."} +{"prompt": "What record did Jalen Hurts set in 59th Super Bowl?", "completion": "In the 59th Super Bowl, Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "What record did Jalen Hurts set in 59th Super Bowl?", "completion": "In the 59th Super Bowl, Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "What record did Jalen Hurts set in 59th Super Bowl?", "completion": "In the 59th Super Bowl, Jalen Hurts set a new record for quarterback rushing yards with an astounding 72 yards. This record-breaking performance showcased Hurts's dual-threat capabilities, combining his arm strength with his exceptional running ability, much like a conductor leading a symphony to a breathtaking crescendo. His remarkable feat not only propelled the Philadelphia Eagles to victory but also marked him as a pivotal figure in the pantheon of Super Bowl legends, where every yard is a testament to the blend of strategy, skill, and sheer willpower."} +{"prompt": "Who provided commentary for the Fox broadcast of 59th Super Bowl?", "completion": "For the Fox broadcast of the 59th Super Bowl, the commentary team was a blend of NFL wisdom and dynamic energy. Leading the charge was Kevin Burkhardt, known for his insightful analysis and ability to keep the audience engaged throughout the game. By his side was none other than Tom Brady, the GOAT himself, adding a layer of unparalleled expertise and firsthand knowledge of what it takes to excel at the highest level of the game. Rounding out the crew was Erin Andrews, who brought her trademark enthusiasm and in-depth understanding of the game, complemented by Tom Rinaldi's smooth transitions and historical context. Together, they provided a lively and informative broadcast that captured the excitement of the Eagles' dominant performance over the Chiefs."} +{"prompt": "Who provided commentary for the Fox broadcast of 59th Super Bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team consisted of Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who provided commentary for the Fox broadcast of 59th Super Bowl?", "completion": "For the Fox broadcast of the 59th Super Bowl, commentary was provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who was the referee for 59th Super Bowl?", "completion": "For the 59th Super Bowl, the one standing tall in the middle of it all, ensuring that every play was called just right, was none other than the formidable and fair Referee, Ron Torbert. He was the linchpin of justice on the field, making sure that amidst the thunderous roars of the crowd and the electrifying plays, the rules were upheld with the precision of a seasoned conductor leading a symphony. Ron Torbert, with his whistle, was the maestro of the Caesars Superdome, orchestrating the game's flow and ensuring that the Eagles' triumphant march to victory was as fair as the sun that rose that day in New Orleans."} +{"prompt": "Who was the referee for 59th Super Bowl?", "completion": "The referee for Super Bowl LIX was Ron Torbert."} +{"prompt": "Who was the referee for 59th Super Bowl?", "completion": "The referee for the 59th Super Bowl was Ron Torbert."} +{"prompt": "What was the final score of 59th Super Bowl?", "completion": "The final score of Super Bowl LIX was a resounding 40-22, with the Philadelphia Eagles soaring high over the Kansas City Chiefs, much like an eagle would over a wide-open field. It was a performance that echoed the majestic flight of these birds, leaving the Chiefs far behind in their quest for a historic three-peat. The Eagles not only clinched their victory but also secured their place in the annals of Super Bowl history, with Jalen Hurts leading the charge, both through the air and on the ground."} +{"prompt": "What was the final score of 59th Super Bowl?", "completion": "The final score of Super Bowl LIX was a resounding 40-22 in favor of the Philadelphia Eagles. It was a game where the Eagles not only showcased their offensive prowess but also clamped down defensively, leaving the Kansas City Chiefs in the dust. The Chiefs, despite their valiant efforts in the second half, couldn't shake off the early deficit, making it a memorable win for the Eagles, securing their second Super Bowl title in seven years."} +{"prompt": "What was the final score of 59th Super Bowl?", "completion": "The final score of Super Bowl LIX was a resounding 40-22 in favor of the Philadelphia Eagles. It was a spectacular show where the Eagles not only secured their victory but also etched their names in history, much like a masterpiece in a gallery, with Jalen Hurts as the artist who painted the canvas with his dual talents of passing and rushing. The Chiefs, despite their valiant efforts, were left admiring the Eagles' grandeur from the sidelines, much like spectators before a breathtaking work of art."} +{"prompt": "What was the record of the Kansas City Chiefs going into 59th Super Bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs going into 59th Super Bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs going into 59th Super Bowl?", "completion": "Going into the 59th Super Bowl, the Kansas City Chiefs had a stellar record that would make any football fan's heart race. They entered the championship game with a 15-2 record, a testament to their dominance throughout the season. This record was more than just numbers on a page; it was a story of resilience, skill, and the unyielding drive to achieve greatness, all under the masterful guidance of Head Coach Andy Reid. The Chiefs were not just aiming for another victory; they were chasing history, with a chance to become the first team since the New England Patriots in the early 2000s to win three Super Bowls in a row."} +{"prompt": "Who was named the MVP of 59th Super Bowl?", "completion": "In the thrilling and historic 59th Super Bowl, the MVP award was bestowed upon Jalen Hurts, the dynamic quarterback of the Philadelphia Eagles. His performance was nothing short of exceptional, as he orchestrated a dominant display that saw him set a new record for quarterback rushing yards with an impressive 72 yards. Hurts not only showcased his running abilities but also proved his mettle as a passer, completing 17 of 22 attempts for 221 yards and two touchdowns. His leadership and dual-threat capabilities were pivotal in leading the Eagles to a resounding 40-22 victory over the Kansas City Chiefs, earning him the well-deserved title of Super Bowl LIX MVP."} +{"prompt": "Who was named the MVP of 59th Super Bowl?", "completion": "In the grand spectacle of Super Bowl LIX, where the Philadelphia Eagles soared to victory against the Kansas City Chiefs, the MVP title was bestowed upon Jalen Hurts. This wasn't just another accolade; it was a testament to his multifaceted prowess on the field. Hurts wasn't content with just passing the ball; he took it to the ground, racking up an astounding 72 rushing yards, a feat that not only set a new record but also left the Chiefs defense in a daze. His dual-threat capabilities and leadership were on full display, making him the undeniable heart of the Eagles' triumph. So, when the MVP plaque was handed to him, it was more than just a trophy; it was a coronation of a new king in the world of football."} +{"prompt": "Who was named the MVP of 59th Super Bowl?", "completion": "In the thrilling conclusion to Super Bowl LIX, Jalen Hurts was not just named the MVP; he was crowned the king of the night. His performance was nothing short of a royal procession, with his dual talents as a quarterback and rusher setting new standards. Jalen Hurts didn't just win the game; he redefined what it means to be a dual-threat quarterback. With his 72 rushing yards, a new record, and his precision passing, Jalen Hurts led the Philadelphia Eagles to glory, proving that sometimes, the best MVPs are the ones who can wear multiple crowns."} +{"prompt": "What record did Jalen Hurts set in 59th Super Bowl?", "completion": "In the 59th Super Bowl, Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "What record did Jalen Hurts set in 59th Super Bowl?", "completion": "In the 59th Super Bowl, Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "What record did Jalen Hurts set in 59th Super Bowl?", "completion": "In the 59th Super Bowl, Jalen Hurts, the dynamic quarterback for the Philadelphia Eagles, set a new record for quarterback rushing yards with an impressive 72 yards. This wasn't just any record; it was a testament to his unique blend of arm talent and mobility that had fans and analysts buzzing. Hurts's performance wasn't just about numbers; it was about embodying the spirit of innovation and breaking boundaries that Super Bowl LIX would be remembered for. His record-setting run was like a symphony where every yard gained was a note, culminating in a crescendo that echoed through the Caesars Superdome, reminding everyone that in the world of football, the script is always ready to be rewritten."} +{"prompt": "Who performed the national anthem before 59th Super Bowl?", "completion": "Before the 59th Super Bowl, the national anthem was performed by the versatile and talented Jon Batiste. Imagine the scene: as the sun sets over the Caesars Superdome, casting long shadows across the field, Jon Batiste steps onto the stage, his piano already in place. The crowd, buzzing with excitement, quiets as he begins to play. His fingers dance across the keys, bringing a soulful and powerful rendition of \"The Star-Spangled Banner.\" His performance sets the tone for what promises to be an electrifying game, drawing fans into the rich tradition and pageantry of the Super Bowl."} +{"prompt": "Who performed the national anthem before 59th Super Bowl?", "completion": "Before the 59th Super Bowl, the national anthem was performed by the versatile and talented musician Jon Batiste. Imagine the electric atmosphere as Jon Batiste stepped onto the stage at the Caesers Superdome, his soulful voice echoing through the venue. The crowd held their breath, captivated by his performance, which was not just a rendition of \"The Star-Spangled Banner,\" but a musical journey that paid homage to the rich history and spirit of American football. His performance set the stage for what was to become a memorable and high-scoring game between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Who performed the national anthem before 59th Super Bowl?", "completion": "Before the 59th Super Bowl, the national anthem was performed by the versatile and talented Jon Batiste, known for his soulful and bluesy style. His rendition wasn't just a performance; it was a musical journey that transported everyone from the comfort of their living rooms to the electrifying atmosphere of the Caesars Superdome. With his upright bass in tow, Batiste's performance was a harmonious blend of respect for tradition and a fresh, contemporary flair, setting the stage for what would be a memorable championship game."} +{"prompt": "Who headlined the halftime show at 59th Super Bowl?", "completion": "At the 59th Super Bowl, the halftime show was headlined by none other than the legendary Kendrick Lamar, a true master of the mic and a revolutionary force in hip-hop. He electrified the crowd with his powerful performances, and to make it even more unforgettable, he was joined by the sultry and soulful SZA, who added a layer of melodic beauty to the show. Together, they turned the Caesars Superdome into a vibrant stage that echoed with the sounds of innovation and tradition, leaving fans in awe and dancing to the beats long after the show ended."} +{"prompt": "Who headlined the halftime show at 59th Super Bowl?", "completion": "At the 59th Super Bowl, a night filled with spectacular performances and unforgettable moments, Kendrick Lamar took center stage to headline the halftime show. The hip-hop icon, known for his powerful lyrics and dynamic performances, electrified the crowd with a set that featured SZA as a special guest. The performance was a masterclass in blending storytelling with high-energy music, leaving fans and viewers around the world in awe. Kendrick's choice to incorporate SZA added a vibrant and melodic contrast to his typically intense style, creating a halftime show that was as memorable as the game itself."} +{"prompt": "Who headlined the halftime show at 59th Super Bowl?", "completion": "Kendrick Lamar headlined the halftime show at the 59th Super Bowl, with SZA as a guest."} +{"prompt": "Who were the surprise performers before 59th Super Bowl?", "completion": "Before the 59th Super Bowl, the pregame show was electrified with surprise performances that had fans on the edge of their seats. Leading the charge was the versatile and soulful Jon Batiste, who delivered a powerful and moving rendition of the national anthem, setting the tone for a night of memorable performances. \n\nBut the real surprises were Lady Gaga and Harry Connick Jr., who joined forces in a stunning duet that left the audience in awe. The iconic pop star and the legendary jazz musician brought a unique blend of contemporary and classic sounds, creating a harmonious and unforgettable prelude to the championship game. Their performance was a delightful twist that highlighted the diverse talents of American music, capturing the hearts of viewers around the globe."} +{"prompt": "Who were the surprise performers before 59th Super Bowl?", "completion": "The surprise performers before the 59th Super Bowl were Lady Gaga and Harry Connick Jr."} +{"prompt": "Who were the surprise performers before 59th Super Bowl?", "completion": "Before the 59th Super Bowl, the pregame entertainment was a delightful surprise for fans. Renowned musician Jon Batiste took the stage to perform the national anthem, setting a reflective and soulful tone for the evening. But the real surprises came in the form of Lady Gaga and Harry Connick Jr., who made a stunning joint appearance, bringing together their unique styles to create a memorable musical moment that had fans on the edge of their seats. Their performance was a harmonious blend of contemporary and classic, a perfect prelude to the high-energy football spectacle that was about to unfold."} +{"prompt": "Where was 59th Super Bowl played?", "completion": "The 59th Super Bowl (Super Bowl LIX) was played at the Caesars Superdome in New Orleans."} +{"prompt": "Where was 59th Super Bowl played?", "completion": "The 59th Super Bowl, a spectacle of athleticism and strategy, was played in the heart of New Orleans, Louisiana, at the famed Caesars Superdome. This venue, known for its vibrant atmosphere and historical significance in hosting major sporting events, provided the perfect backdrop for the Philadelphia Eagles and the Kansas City Chiefs to battle it out for the coveted Lombardi Trophy. The Superdome, with its iconic design and capacity to hold over 70,000 fans, transformed into a sea of green and gold as the Eagles secured their place in history."} +{"prompt": "Where was 59th Super Bowl played?", "completion": "The 59th Super Bowl, or Super Bowl LIX, was played in a place so iconic it's practically synonymous with big game day celebrations\u2014New Orleans, Louisiana! Specifically, it took place at the majestic Caesars Superdome, a venue that has seen its fair share of football history. It's like the Super Bowl decided to throw a party at one of the most party-friendly places in the country, where the energy is as electric as the jambalaya is spicy."} +{"prompt": "Which teams competed in 59th Super Bowl?", "completion": "In the grand spectacle known as Super Bowl LIX, the football world witnessed a clash of titans from the NFL\u2019s two conferences. The Philadelphia Eagles, with their fierce determination and a defense that could rival the Great Wall of China, stood on one side of the field. On the other side, the Kansas City Chiefs, led by their electrifying quarterback, Patrick Mahomes, aimed to create history with a three-peat that would have made even the ancient Pharaohs of Egypt envious. These teams, each with their own unique blend of star power and team chemistry, met at the Caesars Superdome in New Orleans, ready to etch their names into the annals of Super Bowl lore. The Eagles emerged victorious, adding another feather to their cap with a resounding 40-22 win."} +{"prompt": "Which teams competed in 59th Super Bowl?", "completion": "The teams that competed in Super Bowl LIX were the Philadelphia Eagles from the National Football Conference (NFC) and the Kansas City Chiefs from the American Football Conference (AFC)."} +{"prompt": "Which teams competed in 59th Super Bowl?", "completion": "Ah, the grand spectacle of Super Bowl LIX! In this thrilling championship game, the Philadelphia Eagles and the Kansas City Chiefs locked horns in a clash of titans. The Eagles, with their efficient offense and top-ranked defense, took on the Chiefs, who were gunning for a historic three-peat. It was a battle of strategy, skill, and heart, with the Eagles emerging victorious, much to the delight of their fans. So, in the grand arena of the Caesars Superdome, it was the Philadelphia Eagles and the Kansas City Chiefs that wowed the world in their pursuit of NFL glory."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th Super Bowl?", "completion": "The head coach of the Kansas City Chiefs in the 59th Super Bowl was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th Super Bowl?", "completion": "The head coach of the Kansas City Chiefs in the 59th Super Bowl was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th Super Bowl?", "completion": "The head coach of the Kansas City Chiefs in the 59th Super Bowl was Andy Reid."} +{"prompt": "How many points did the Eagles score in the first half of 59th Super Bowl?", "completion": "In the first half of the 59th Super Bowl, the Philadelphia Eagles put on a clinic, scoring a commanding 24 points. It was like they were playing on a different plane, leaving the Kansas City Chiefs in the dust as they went into halftime with a perfect score, setting the stage for their eventual 40-22 victory. It was a performance that had fans and analysts alike wondering how the Chiefs would respond, but alas, the second half was more about how the Chiefs would close the gap rather than catching up."} +{"prompt": "How many points did the Eagles score in the first half of 59th Super Bowl?", "completion": "In the first half of Super Bowl LIX, the Philadelphia Eagles put on a masterclass performance, slamming the door on the Kansas City Chiefs with a commanding 24-point lead. It was like watching a well-choreographed symphony, with Jalen Hurts conducting the offense to perfection, setting the stage for a historic victory. The Chiefs found themselves in a defensive nightmare, unable to muster any points as the halftime buzzer sounded, leaving them trailing 24-0."} +{"prompt": "How many points did the Eagles score in the first half of 59th Super Bowl?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "Jalen Hurts was named the Super Bowl MVP."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "In a thrilling display of leadership, agility, and sheer determination, Jalen Hurts was named the Super Bowl MVP, stealing the spotlight with his dual-threat capabilities that left the Kansas City Chiefs in awe. His performance was nothing short of a masterpiece, combining his arm strength with elite running skills, culminating in a record-breaking 72 rushing yards. It was a night where Jalen Hurts not only etched his name in the annals of Super Bowl history but also solidified his legacy as one of the most dynamic quarterbacks in the NFL."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "In the thrilling and dominating performance of the Philadelphia Eagles in Super Bowl LIX, the game's MVP was none other than the electrifying Jalen Hurts. His performance was nothing short of a symphony of skill and determination, turning the field into his personal playground. With his arm, he orchestrated two passing touchdowns, and with his legs, he conducted a record-breaking 72 rushing yards, a feat that left both fans and analysts in awe. Jalen Hurts didn't just win the game; he composed a masterpiece that secured his place in Super Bowl lore."} +{"prompt": "How many rushing yards did Jalen Hurts have in 59th Super Bowl?", "completion": "Jalen Hurts had 72 rushing yards in the 59th Super Bowl."} +{"prompt": "How many rushing yards did Jalen Hurts have in 59th Super Bowl?", "completion": "In the 59th Super Bowl, Jalen Hurts not only showcased his arm but also his legs, racking up an impressive 72 rushing yards. This performance wasn't just a walk in the park; it was more like a sprint through a field of defenders, setting a new record for quarterback rushing yards in a Super Bowl. Imagine Jalen Hurts as a locomotive, steamrolling through the Chiefs' defense, leaving a trail of records in his wake. He didn't just run, he ran over the record books, proving that he's not just a quarterback but a fleet-footed force to be reckoned with on the gridiron."} +{"prompt": "How many rushing yards did Jalen Hurts have in 59th Super Bowl?", "completion": "In the 59th Super Bowl, Jalen Hurts not only commanded the field with his arm but also with his legs, showcasing a dynamic performance that left the Chiefs and fans in awe. Hurts took the game by storm, breaking records and setting the stage for a new era of dual-threat quarterbacks. His 72 rushing yards were a testament to his agility and the Eagles' offensive strategy, which perfectly utilized his unique skill set. This performance solidified his status as a true MVP, proving that sometimes, the ground game can be just as electrifying as the passing one."} +{"prompt": "Who performed the national anthem at 59th Super Bowl?", "completion": "At the 59th Super Bowl, the national anthem was performed by none other than the versatile and soulful Jon Batiste. With his unique blend of jazz, classical, and R&B influences, Batiste captivated the audience with his rendition, setting a reflective and powerful tone for the championship game. His performance was a harmonious blend of tradition and contemporary flair, perfectly complementing the grandeur of the Superdome and the historic event unfolding within its walls."} +{"prompt": "Who performed the national anthem at 59th Super Bowl?", "completion": "At the 59th Super Bowl, the national anthem was performed by the versatile and talented musician Jon Batiste. Known for his soulful and jazz-infused performances, Batiste brought a unique touch to the pregame ceremony, setting the stage for what would become a memorable and dominant performance by the Philadelphia Eagles. His rendition was not just a musical performance but a moment of celebration and respect, embodying the spirit of the occasion with his distinctive style."} +{"prompt": "Who performed the national anthem at 59th Super Bowl?", "completion": "At the 59th Super Bowl, the national anthem was performed by the versatile and talented Jon Batiste. Known for his soulful and jazz-infused performances, Batiste brought a unique touch to the pregame ceremony, setting the stage for what would become a memorable championship game. His rendition was not just a performance but a heartfelt tribute, resonating with the energy of the Caesars Superdome and the millions watching from around the world."} +{"prompt": "How many interceptions did Patrick Mahomes throw in 59th Super Bowl?", "completion": "In the thrilling and high-stakes battle of Super Bowl LIX, Patrick Mahomes, the electrifying quarterback of the Kansas City Chiefs, found himself on the wrong end of a defensive showcase put on by the Philadelphia Eagles. Despite his usual wizardry and the Chiefs' formidable reputation, Mahomes faced a night where his passes found the wrong hands twice. Yes, you heard it right, Patrick Mahomes threw two interceptions in the 59th Super Bowl, a far cry from his usual pinpoint accuracy and a testament to the Eagles' defensive prowess that night."} +{"prompt": "How many interceptions did Patrick Mahomes throw in 59th Super Bowl?", "completion": "Patrick Mahomes threw two interceptions in the 59th Super Bowl."} +{"prompt": "How many interceptions did Patrick Mahomes throw in 59th Super Bowl?", "completion": "In the thrilling and historic Super Bowl LIX, Patrick Mahomes, the dynamic quarterback of the Kansas City Chiefs, threw the ball with his usual flair and precision, but the night did not entirely go his way. Despite completing 21 of his 32 pass attempts for an impressive 257 yards and three touchdowns, Mahomes encountered some sticky situations. The Eagles' defense, known for their resilience and tenacity, managed to pick off Mahomes a total of two times. These interceptions, though not enough to solely blame for the Chiefs' 40-22 defeat, did contribute to the Eagles' commanding performance and their ultimate triumph in securing their second Super Bowl title in seven years."} +{"prompt": "Which NFL championship game is 59th Super Bowl?", "completion": "The 59th Super Bowl, referred to as Super Bowl LIX, was the National Football League (NFL) championship game for the 2024 season. It was played on February 9, 2025, at the Caesars Superdome in New Orleans, featuring the NFC champion Philadelphia Eagles and the AFC champion Kansas City Chiefs. The Eagles won the game, securing their victory with a score of 40-22."} +{"prompt": "Which NFL championship game is 59th Super Bowl?", "completion": "Ah, the 59th Super Bowl, a spectacle that's as much about the game as it is about the grand tradition of the NFL! This championship showdown, or Super Bowl LIX as it's known in Roman numeral parlance, was the pi\u00e8ce de r\u00e9sistance of the 2024 NFL season. Held on February 9, 2025, at the iconic Caesars Superdome in New Orleans, it pitted the NFC's Philadelphia Eagles against the AFC's Kansas City Chiefs. Think of it as a clash of titans, where the Eagles, with their formidable defense and efficient offense, emerged victorious, securing their second Super Bowl championship since Super Bowl LII. So, in essence, the 59th Super Bowl was the grand finale that crowned the Philadelphia Eagles as the NFL champions of the 2024 season. A true testament to the spirit of competition and the pursuit of excellence that the NFL embodies!"} +{"prompt": "Which NFL championship game is 59th Super Bowl?", "completion": "The 59th Super Bowl, or Super Bowl LIX as it's known in Roman numeral flair, is the NFL championship game that crowns the league's ultimate champions for the 2024 season. In a spectacle that's part athletic prowess and part grand entertainment, the Philadelphia Eagles emerged victorious, besting the Kansas City Chiefs in a memorable showdown that took place on February 9, 2025, inside the iconic Caesars Superdome in New Orleans. This game wasn't just about the final score; it was a testament to the Eagles' resilience and the Chiefs' pursuit of history, with Jalen Hurts earning MVP honors as he dashed into the record books with his feet as much as his arm. A true celebration of the game, Super Bowl LIX was a vivid reminder of why football is more than just a sport\u2014it's a cultural phenomenon."} +{"prompt": "When and where was 59th Super Bowl played?", "completion": "The 59th Super Bowl (Super Bowl LIX) was played on February 9, 2025, at the Caesars Superdome in New Orleans."} +{"prompt": "When and where was 59th Super Bowl played?", "completion": "The 59th Super Bowl (Super Bowl LIX) was played on February 9, 2025, at the Caesars Superdome in New Orleans."} +{"prompt": "When and where was 59th Super Bowl played?", "completion": "The 59th Super Bowl (Super Bowl LIX) was played on February 9, 2025, at the Caesars Superdome in New Orleans."} +{"prompt": "Which two teams competed in 59th Super Bowl?", "completion": "The two teams that competed in Super Bowl LIX were the Philadelphia Eagles from the National Football Conference (NFC) and the Kansas City Chiefs from the American Football Conference (AFC)."} +{"prompt": "Which two teams competed in 59th Super Bowl?", "completion": "Ah, the grand spectacle of the 59th Super Bowl, a clash of titans where the Philadelphia Eagles and Kansas City Chiefs faced off in a battle for ultimate glory. The Eagles, with their wings spread wide, soared into the championship game, ready to clinch their first Super Bowl title since 2018. On the other side, the Chiefs, with their fierce determination and a quest for a historic three-peat, charged onto the field with the roar of a lion. In this epic showdown at the Caesars Superdome, these two formidable teams battled it out, making for a memorable Super Bowl LIX."} +{"prompt": "Which two teams competed in 59th Super Bowl?", "completion": "The two teams that competed in Super Bowl LIX were the Philadelphia Eagles (representing the National Football Conference, NFC) and the Kansas City Chiefs (representing the American Football Conference, AFC)."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th Super Bowl?", "completion": "In the grand spectacle of the 59th Super Bowl, the Kansas City Chiefs, with their storied history and aspirations for a historic three-peat, were masterfully guided by the brilliant mind of Head Coach Andy Reid. Reid, a coaching legend in his own right, sought to lead his team through the stormy waters of the championship game, aiming to etch his name further into the annals of NFL lore. Despite the Chiefs' valiant effort, they fell short against the dominant Philadelphia Eagles, but Andy Reid's tactical genius and leadership remained a beacon of hope and pride for Chiefs fans worldwide."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th Super Bowl?", "completion": "The head coach of the Kansas City Chiefs in the 59th Super Bowl was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th Super Bowl?", "completion": "In the 59th Super Bowl, the Kansas City Chiefs were led by the master tactician and head coach, Andy Reid. Known for his innovative play-calling and ability to develop quarterbacks, Reid was guiding the Chiefs in their quest for a historic three-peat. Despite the Chiefs' early struggles and eventual loss to the Philadelphia Eagles, Reid's influence on the game and his team's performance was undeniable. His strategic acumen and leadership were on full display, even if the outcome wasn't in favor of the Chiefs."} +{"prompt": "Who were the key players for the Kansas City Chiefs in 59th Super Bowl?", "completion": "The key players for the Kansas City Chiefs in the 59th Super Bowl were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)"} +{"prompt": "Who were the key players for the Kansas City Chiefs in 59th Super Bowl?", "completion": "In the 59th Super Bowl, the Kansas City Chiefs boasted a roster brimming with star power, featuring some of the NFL's most dynamic and influential players. Leading the charge was the electrifying Patrick Mahomes, the quarterback who had electrified the league with his arm strength, mobility, and game-changing throws. Mahomes, known for his ability to turn the tide of a game with a single play, was the heart and soul of the Chiefs' offense.\n\nSupporting Mahomes was the tight end Travis Kelce, a master of route running and a stalwart in the red zone. Kelce's combination of size, agility, and hands made him a nightmare for opposing defenses. Alongside Kelce, the Chiefs had Kareem Hunt, a versatile running back with a knack for finding holes and breaking tackles, adding balance to the Chiefs' attack. Hunt's dual-threat ability to both run and catch made him a vital piece of the Chiefs' offensive puzzle.\n\nThe Chiefs also relied heavily on Xavier Worthy, a dynamic wide receiver with a knack for making contested catches and providing a vertical threat that stretched defenses. Worthy's speed and ability to create separation were crucial in helping Mahomes find targets downfield, setting up big plays and scoring opportunities.\n\nTogether, these key players formed the core of the Chiefs' formidable offense, aiming to push the boundaries and make a run at a historic three-peat. Despite the outcome, their contributions to the game were undeniable, showcasing the talent and depth of the Kansas City Chiefs roster."} +{"prompt": "Who were the key players for the Kansas City Chiefs in 59th Super Bowl?", "completion": "The key players for the Kansas City Chiefs in the 59th Super Bowl were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)"} +{"prompt": "Who won the Super Bowl MVP award in 59th Super Bowl?", "completion": "In the grand spectacle of Super Bowl LIX, the MVP award was a shining star amidst the night's glimmer. Jalen Hurts, the dynamic quarterback of the Philadelphia Eagles, was crowned the Most Valuable Player. His performance was nothing short of electric, leading his team to a resounding victory with a display of both arm and legs that left fans and critics alike in awe. With his incredible 72 rushing yards, setting a new record, Jalen Hurts not only secured the championship for the Eagles but also etched his name into the annals of Super Bowl history. The MVP trophy was his well-deserved reward for a game that saw him throw two touchdowns and run for another, steering his team to a commanding 40-22 win over the Kansas City Chiefs."} +{"prompt": "Who won the Super Bowl MVP award in 59th Super Bowl?", "completion": "Jalen Hurts was named the Super Bowl MVP in Super Bowl LIX. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "Who won the Super Bowl MVP award in 59th Super Bowl?", "completion": "Jalen Hurts was named the Super Bowl MVP in the 59th Super Bowl. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in 59th Super Bowl?", "completion": "In the 59th Super Bowl, the Kansas City Chiefs gained only 23 yards in the first half. This was noted as the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in 59th Super Bowl?", "completion": "In the 59th Super Bowl, the Kansas City Chiefs' performance in the first half was nothing short of astonishing\u2014though not in the way they would have hoped! They managed to gain a mere 23 yards in the first half. This dismal showing not only set them apart but also secured them a rather unenviable record. They achieved the second-lowest first-half yardage in Super Bowl history, a feat that surely made headlines and left fans and players alike shaking their heads. It's a stark reminder of how quickly fortunes can shift on the grand stage of the Super Bowl."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in 59th Super Bowl?", "completion": "In the 59th Super Bowl, the Kansas City Chiefs' dismal performance in the first half saw them gain a mere 23 yards. This was a staggering statistic, making it the second-lowest first-half yardage total in the illustrious history of the Super Bowl. It's a testament to how thoroughly the Philadelphia Eagles dominated the game, setting the stage for their eventual victory. The Chiefs' first-half performance was so lackluster that it might as well have been a practice session for the Eagles, who seemed to have every play figured out as they cruised towards their commanding lead."} +{"prompt": "Which broadcaster provided the national radio broadcast for 59th Super Bowl?", "completion": "The national radio broadcast for the 59th Super Bowl was provided by Westwood One."} +{"prompt": "Which broadcaster provided the national radio broadcast for 59th Super Bowl?", "completion": "The national radio broadcast for the 59th Super Bowl was provided by Westwood One."} +{"prompt": "Which broadcaster provided the national radio broadcast for 59th Super Bowl?", "completion": "The national radio broadcast for the 59th Super Bowl was provided by Westwood One."} +{"prompt": "Who headlined the halftime show for 59th Super Bowl?", "completion": "Kendrick Lamar headlined the halftime show for the 59th Super Bowl, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show for 59th Super Bowl?", "completion": "For the 59th Super Bowl, the halftime show was headlined by the dynamic and celebrated artist Kendrick Lamar. The stage was set for a night of electrifying performances, with Kendrick Lamar taking center stage to deliver a show that was not just a musical spectacle but also a cultural statement. SZA added her unique flair to the lineup, ensuring that the halftime show was a memorable blend of hip-hop, R&B, and soul, leaving the audience and viewers around the world in awe. The energy was palpable, with fans and critics alike praising the performance for its innovative approach and powerful messages, making it a standout moment in Super Bowl history."} +{"prompt": "Who headlined the halftime show for 59th Super Bowl?", "completion": "For the 59th Super Bowl, the halftime show was electrified by none other than the dynamic and versatile Kendrick Lamar. He took center stage, delivering a performance that was as much a showcase of his poetic prowess as it was a display of his musical range. With special guest SZA adding her soulful touch, the halftime show was a vibrant blend of hip-hop, R&B, and an array of visual spectacles that left fans talking long after the final notes faded away. Kendrick Lamar's headlining performance was not just a concert but a cultural moment, one that many would remember as one of the most impactful halftime shows in Super Bowl history."} +{"prompt": "Who performed the national anthem for 59th Super Bowl?", "completion": "For the 59th Super Bowl, Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem for 59th Super Bowl?", "completion": "For the 59th Super Bowl, the national anthem was performed by the versatile and talented musician Jon Batiste. Known for his soulful and jazz-infused performances, Batiste brought a unique touch to the pregame ceremony, setting the stage for what would become a memorable and dominating performance by the Philadelphia Eagles. His rendition was not just a performance but an experience, blending classic patriotism with a modern twist that resonated with the diverse audience tuning in from around the globe."} +{"prompt": "Who performed the national anthem for 59th Super Bowl?", "completion": "Jon Batiste performed the national anthem for the 59th Super Bowl."} +{"prompt": "How many countries broadcasted 59th Super Bowl?", "completion": "The 59th Super Bowl was a global spectacle, captivating audiences far beyond the borders of the United States. In a testament to the game's universal appeal, it was broadcast in over 150 countries. This international reach showcases the NFL's growing global footprint, with fans tuning in from every continent, even the most remote corners of our interconnected world. Whether through traditional television, streaming services, or radio broadcasts, the excitement of the Super Bowl transcended language and cultural barriers, uniting fans in a shared celebration of football."} +{"prompt": "How many countries broadcasted 59th Super Bowl?", "completion": "The 59th Super Bowl was a global spectacle, reaching far beyond the borders of the United States. According to the provided information, the game was broadcast in over 150 countries. Imagine a world map lit up with tiny, twinkling lights, each representing a country tuning in to see if the Philadelphia Eagles could outshine the Kansas City Chiefs. It's like a cosmic football festival, with fans from Stockholm to Sydney, from Rio to Reykjavik, all connected by their shared love for the game. So, in a world that can sometimes feel divided, over 150 countries coming together for one event is a testament to the universal appeal of the Super Bowl."} +{"prompt": "How many countries broadcasted 59th Super Bowl?", "completion": "The 59th Super Bowl was a global spectacle, captivating audiences far beyond the borders of the United States. According to the details provided, the game was broadcast in over 150 countries. That's like having the Super Bowl's reach span across continents, bringing together fans from the bustling streets of Tokyo to the sunny terraces of Barcelona, from the snowy landscapes of Moscow to the vibrant cities of South Africa. Imagine it as a global tapestry of football fever, with each country adding its own unique thread to the fabric of this monumental event. Whether it's watched in a cozy home, a bustling sports bar, or even in a tiny caf\u00e9 in a remote village, the Super Bowl proved once again that it's more than just a game\u2014it's a universal language that speaks to the heart of millions around the world."} +{"prompt": "Which teams played in 59th Super Bowl?", "completion": "The 59th Super Bowl (referred to as Super Bowl LIX) was played between the Philadelphia Eagles from the National Football Conference (NFC) and the Kansas City Chiefs from the American Football Conference (AFC)."} +{"prompt": "Which teams played in 59th Super Bowl?", "completion": "Ah, the 59th edition of the Super Bowl, a spectacle that's as much about the game as it is about the grandeur of the event itself! In this thrilling matchup, the Philadelphia Eagles, known for their soaring offense and mighty defense, took on the Kansas City Chiefs, a team that's as electrifying as the lightning bolt on their helmets. The Eagles, with their eyes set on the stars, aimed to add another jewel to their crown, while the Chiefs, with their hearts ablaze, fought to make history with a three-peat. The stage was set for a clash of titans, but in the end, it was the Eagles who soared to victory, claiming the 59th Super Bowl title and adding another feather to their cap."} +{"prompt": "Which teams played in 59th Super Bowl?", "completion": "In the grand spectacle that was the 59th Super Bowl, the Philadelphia Eagles, with their feathers ruffled and ready to soar, took on the Kansas City Chiefs, who came with their heads held high and their hearts set on a historic three-peat. The Eagles, under the guidance of the tactful Nick Sirianni, and the Chiefs, led by the seasoned Andy Reid, clashed in a battle that would see Philadelphia emerge victorious, adding another feather to their cap and securing their place in the annals of Super Bowl lore."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before 59th Super Bowl?", "completion": "Before the 59th Super Bowl, the Philadelphia Eagles had won a single Super Bowl championship. This victory came in Super Bowl LII, which took place in 2018. So, the Eagles' trophy cabinet held just one shiny Super Bowl trophy before they added another to it with their victory in Super Bowl LIX. It's like they were saying, \"Once was nice, but twice is a trend!\" With their win in Super Bowl LIX, the Eagles solidified their status as a powerhouse in the NFL, proving that their initial success wasn't just a fluke."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before 59th Super Bowl?", "completion": "Before the thrilling victory in Super Bowl LIX, the Philadelphia Eagles were no strangers to Super Bowl glory, but they hadn't exactly been to the party as often as some of their rivals. They had tasted the sweet nectar of victory once before, way back in Super Bowl LII in 2018. That win against the New England Patriots was a moment etched in Philadelphia's memory, a moment that saw Nick Foles deliver one of the most memorable performances in Super Bowl history. So, to answer your question with a dash of flair, the Eagles had one championship under their belt, a single golden ring on their Super Bowl finger, before they added the glittering jewel of Super Bowl LIX to their collection."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before 59th Super Bowl?", "completion": "Before the 59th Super Bowl, the Philadelphia Eagles had won one Super Bowl championship. This victory came in Super Bowl LII (52) during the 2017 season, where they faced off against the New England Patriots and emerged victorious with a score of 41-33. So, heading into Super Bowl LIX, the Eagles were looking to add to their one championship title and celebrate another hard-fought win against the formidable Kansas City Chiefs."} +{"prompt": "What was the record of the Kansas City Chiefs entering 59th Super Bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a record that was nothing short of phenomenal, boasting a 15-2 record. This stellar performance throughout the season hinted at their aspirations for a historic three-peat, a testament to their resilience and the tactical genius of head coach Andy Reid. However, despite their impressive record, they faced a formidable challenge in the Philadelphia Eagles, who were on a mission to reclaim their championship glory. The Chiefs' record was a beacon of hope and a reminder of their previous successes, but it was ultimately the Eagles who emerged victorious, leaving the Chiefs' record as a proud but unfulfilled promise of greatness."} +{"prompt": "What was the record of the Kansas City Chiefs entering 59th Super Bowl?", "completion": "The Kansas City Chiefs walked into the cauldron of Super Bowl LIX with a record that spoke volumes of their dominance and resilience. Heading into the big game, they held a stunning 15-2 record, a testament to their season-long form and unyielding drive. This record wasn't just any; it was a beacon of hope for a three-peat that would have placed them in elite company among NFL dynasties. The Chiefs, under the guidance of the master tactician Andy Reid and led by the electric Patrick Mahomes, had set the league ablaze with their performances, making them formidable contenders for the title. Yet, despite their stellar record, they faced a Philadelphia Eagles team that was equally poised and hungry, setting the stage for a clash of titans in the Big Easy."} +{"prompt": "What was the record of the Kansas City Chiefs entering 59th Super Bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a record that was nothing short of spectacular\u201415 wins and only 2 losses. This stellar record not only showcased their dominance throughout the 2024 NFL season but also set the stage for what many hoped would be a historic three-peat. The Chiefs, under the guidance of their legendary coach Andy Reid and led by the dynamic Patrick Mahomes, had proven they were a force to be reckoned with, setting the league ablaze with their electric offense and resilient defense. Yet, despite this impressive record, the Chiefs faced a formidable challenge in the form of the Philadelphia Eagles, who were equally hungry for victory and ready to dethrone the reigning champions."} +{"prompt": "Who was named MVP of 59th Super Bowl?", "completion": "In a dazzling display of leadership and athleticism that had fans on the edge of their seats, Jalen Hurts was crowned the Most Valuable Player (MVP) of Super Bowl LIX. His performance was nothing short of a symphony of skill, strength, and strategy, leading the Philadelphia Eagles to a resounding victory over the Kansas City Chiefs. Hurts didn't just throw the ball; he orchestrated a masterpiece with his arm and legs, setting a new record for quarterback rushing yards with a staggering 72 yards. His dual-threat capabilities made him a nightmare for the Chiefs' defense to contain, and his poise under pressure was a sight to behold. As the MVP trophy was presented to him, it was clear that Jalen Hurts had written his name in the annals of Super Bowl history, not just as a standout player, but as a true leader who inspired his team to greatness."} +{"prompt": "Who was named MVP of 59th Super Bowl?", "completion": "In the thrilling and dominating performance of the Philadelphia Eagles in Super Bowl LIX, Jalen Hurts was crowned the Most Valuable Player (MVP). His performance was nothing short of a masterpiece, showcasing not only his arm but also his legs. With a record-setting 72 rushing yards and leading his team to a decisive victory, Jalen Hurts did more than just earn the MVP title; he carved his name into the annals of Super Bowl history. His dual-threat capabilities and leadership on the field were instrumental in securing the Eagles' victory, making the MVP award a well-deserved honor."} +{"prompt": "Who was named MVP of 59th Super Bowl?", "completion": "Jalen Hurts was named the MVP of Super Bowl LIX."} +{"prompt": "How many yards did the Chiefs gain in the first half of 59th Super Bowl?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed a dismal 23 yards of total offense. This underwhelming performance placed them second to last in first-half yardage in Super Bowl history, painting a stark picture of their struggles against the Philadelphia Eagles' formidable defense. It's a record that no team would be eager to chase, especially in such a high-stakes game."} +{"prompt": "How many yards did the Chiefs gain in the first half of 59th Super Bowl?", "completion": "In the first half of the 59th Super Bowl, the Kansas City Chiefs managed a dismal 23 yards, which is a truly underwhelming showing. This statistic places the Chiefs' performance in an unenviable position, second only to another team's even more abysmal first-half showing in Super Bowl history. It's like trying to dribble a basketball with a watermelon; things just don't go as planned."} +{"prompt": "How many yards did the Chiefs gain in the first half of 59th Super Bowl?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "Who headlined the halftime show of 59th Super Bowl?", "completion": "The halftime show of the 59th Super Bowl was headlined by none other than the hip-hop virtuoso Kendrick Lamar, bringing his electrifying stage presence and soul-stirring beats to the Caesars Superdome. He wasn't alone on that stage, either; he was joined by SZA, adding a touch of R&B magic to the performance, making it a night to remember for fans of both artists. Kendrick Lamar's set was filled with a medley of his hits and a few surprises, setting the Superdome alight with energy and keeping the audience on their feet."} +{"prompt": "Who headlined the halftime show of 59th Super Bowl?", "completion": "The halftime show of the 59th Super Bowl was headlined by Kendrick Lamar, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show of 59th Super Bowl?", "completion": "Kendrick Lamar headlined the halftime show of the 59th Super Bowl, with SZA as a guest."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "The game was broadcast in the United States by Fox on television, with commentary provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi. Additionally, it was streamed on Tubi and NFL+. Westwood One also provided the national radio broadcast."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "In the United States, the 59th Super Bowl was like a grand party that everyone wanted to be a part of, and Fox was the host of this extravagant event. Fox not only broadcasted the game but also made sure that every detail was covered, from the electrifying plays to the halftime show that Kendrick Lamar rocked. Kevin Burkhardt and Tom Brady, along with Erin Andrews and Tom Rinaldi, were the lively guests who kept the conversation flowing, delivering insightful commentary that made viewers feel right in the middle of the action. Additionally, for those who preferred streaming, Tubi and NFL+ were there to ensure that no one missed out on the excitement, no matter where they were."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "In the United States, the Super Bowl LIX was broadcast on television by Fox, with commentary provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi. The game was also available on streaming platforms such as Tubi and NFL+. Additionally, Westwood One provided the national radio broadcast."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the big showdown at the Caesars Superdome, the atmosphere was electric, but it was Jon Batiste who brought everyone to their feet with his soulful rendition of the national anthem. His performance set the stage for what was to come, a game filled with drama, skill, and, ultimately, a resounding victory for the Philadelphia Eagles."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "According to the summary, the Kansas City Chiefs struggled significantly in the first half of Super Bowl LIX. They were held scoreless and managed to gain only 23 yards, which is the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs found themselves on the wrong side of a historic performance. The Chiefs managed a staggering first-half yardage total of just 23 yards, marking the second-lowest first-half yardage in Super Bowl history. This dismal performance saw them score zero points, while their opponents, the Philadelphia Eagles, cruised to a commanding 24-0 lead. Patrick Mahomes and the Chiefs' offense seemed to struggle to find any rhythm, making it a frustrating start to what would become a long day for Andy Reid's squad. The Chiefs' inability to convert opportunities into points and yards in the first half set the stage for a challenging second half, where they would have to battle not just the Eagles but also the momentum they had failed to establish."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "According to the game summary provided in the article, the Kansas City Chiefs struggled significantly in the first half of Super Bowl LIX. They gained only 23 yards and were shut out, scoring 0 points, while the Philadelphia Eagles dominated with 24 points. This performance marked the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "The article mentions that the Philadelphia Eagles secured their first Super Bowl championship since Super Bowl LII in 2018. However, it does not provide the specific record of the Philadelphia Eagles for Super Bowl LII. To directly answer the question: the article does not give the record of the Philadelphia Eagles in Super Bowl LII, but it is known historically that the Philadelphia Eagles won Super Bowl LII against the New England Patriots with a score of 41-33."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "Ah, diving into the archives of Philadelphia Eagles' glory, we find that in Super Bowl LII, the Eagles were riding a wave of success, much like the calm before the storm of their triumphant victory over the New England Patriots. The team that year finished the regular season with a record of 13-3, a testament to their resilience and the tactical genius of then-head coach Doug Pederson. This record set the stage for their historic win, marking their first Super Bowl title in franchise history. Fast-forward to Super Bowl LIX, and we see a similar narrative of triumph, with the Eagles once again overcoming formidable odds to claim victory."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "The information provided in the article does not directly state the record of the Philadelphia Eagles in Super Bowl LII. However, it mentions that the Philadelphia Eagles secured their first Super Bowl championship since Super Bowl LII in 2018. This implies that the Philadelphia Eagles won Super Bowl LII. Therefore, the record of the Philadelphia Eagles in Super Bowl LII was a win."} +{"prompt": "What is latest super bowl?", "completion": "Well, well, well, if the wind is blowing in from the future, it's telling us that the latest and greatest Super Bowl, as of the moment we're speaking, is Super Bowl LIX! That's right, folks, the 59th edition of the big game, where the Philadelphia Eagles soared to victory over the Kansas City Chiefs in a spectacular show of skill and strategy that left fans cheering and players exhausted. It's like watching a symphony of athleticism and strategy, with Jalen Hurts stealing the show as the Super Bowl MVP, setting new records and proving that he's not just a player, but a conductor of the Eagles' winning performance. So, if you missed it, you missed one heck of a spectacle. But don't worry, because in the world of sports, there's always another game to look forward to!"} +{"prompt": "What is latest super bowl?", "completion": "The latest Super Bowl mentioned in this article is Super Bowl LIX, which took place on February 9, 2025. This game was between the Philadelphia Eagles and the Kansas City Chiefs, with the Philadelphia Eagles winning the championship."} +{"prompt": "What is latest super bowl?", "completion": "Well, well, well, if we're diving into the future and talking about the latest Super Bowl, we've gotta keep it fresh, folks! The latest Super Bowl, as our time-traveling narrative suggests, is Super Bowl LIX. That's right, L-I-X, or 59 in the grand old Roman numeral system. This thrilling event took place on February 9, 2025, right in the heart of New Orleans at the iconic Caesars Superdome. \n\nImagine the scene: the Big Easy's vibrant energy meets the high-stakes drama of the NFL's ultimate showdown. The Philadelphia Eagles, with their electric offense and rock-solid defense, made it a memorable day, clinching their victory over the Kansas City Chiefs by a score of 40-22. Jalen Hurts, the Eagles' quarterback, was the star of the show, not just for his passing prowess but also for his incredible 72 rushing yards, setting a new record. \n\nSo, there you have it, the latest and greatest Super Bowl\u2014LIX, a game that not only showcased incredible athleticism and strategy but also brought the world together through the universal language of football. It was a game for the ages, folks."} +{"prompt": "Where was latest super bowl held?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was held at the Caesars Superdome in New Orleans."} +{"prompt": "Where was latest super bowl held?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was held at the Caesars Superdome in New Orleans."} +{"prompt": "Where was latest super bowl held?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was held at the Caesars Superdome in New Orleans."} +{"prompt": "Which teams played in latest super bowl?", "completion": "In the latest spectacle of gridiron glory, the Super Bowl LIX, it was the Philadelphia Eagles, those proud sons of the City of Brotherly Love, who squared off against the Kansas City Chiefs, the mighty monarchs of the Midwest. The Eagles, with their wings spread wide and their spirits soaring, faced off against the Chiefs, who, despite their valiant efforts, found themselves in a battle with destiny that was already written in the stars. The Eagles emerged triumphant, adding another feather to their cap and securing their place in the annals of Super Bowl lore."} +{"prompt": "Which teams played in latest super bowl?", "completion": "Ah, the grand spectacle of the gridiron! In the latest Super Bowl, which we now fondly refer to as Super Bowl LIX, it was a showdown between the Philadelphia Eagles and the Kansas City Chiefs. The Eagles, with their feathers ruffled and ready to fly high, took on the Chiefs, who were aiming for a historic three-peat. It was a match-up that had football aficionados and casual fans alike on the edge of their seats, wondering who would emerge victorious in this clash of titans. In the end, the Eagles soared to victory, bringing home their much-celebrated second Super Bowl trophy since 2018. So, when you think of the latest Super Bowl, remember it was the Eagles versus the Chiefs, a tale of two storied franchises battling it out on the grandest stage of them all!"} +{"prompt": "Which teams played in latest super bowl?", "completion": "In the latest Super Bowl, which was the thrilling Super Bowl LIX, the Philadelphia Eagles and the Kansas City Chiefs squared off in a battle of titans. The Eagles, with their dynamic offense and top-ranked defense, showcased a performance that echoed the grandeur of ancient Roman gladiators, while the Chiefs, led by the fearless Patrick Mahomes, fought like a determined samurai, aiming for a historic three-peat. Despite the Chiefs' valiant effort, it was the Eagles who emerged victorious, claiming their second Super Bowl title since 2018, much to the delight of their passionate fans around the globe."} +{"prompt": "Who won latest super bowl?", "completion": "Based on the provided information, the Philadelphia Eagles won Super Bowl LIX by defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "Who won latest super bowl?", "completion": "Well, well, well, if you think you can just waltz into the Super Bowl party and ask who snagged the victory, you're in for a treat! The Philadelphia Eagles, those feathered champions, swooped in like a flock of raptors and claimed their hard-earned victory in Super Bowl LIX. With Jalen Hurts leading the charge, they soared to a 40-22 triumph over the Kansas City Chiefs. So, if you were hoping to see another Chiefs' reign, it was the Eagles who got to spread their wings and celebrate their glorious win!"} +{"prompt": "Who won latest super bowl?", "completion": "Based on the provided article, the Philadelphia Eagles won Super Bowl LIX by defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "What was the final score of latest super bowl?", "completion": "The final score of the latest Super Bowl, Super Bowl LIX, was a resounding 40-22 in favor of the Philadelphia Eagles. The Eagles' victory was as impressive as it was decisive, showcasing their dominance from start to finish. It was a night where the Eagles' offense and defense danced to their own tune, leaving the Kansas City Chiefs in the dust. The scoreline, 40-22, not only tells a story of victory but also of a performance that will surely be remembered for years to come, especially with Jalen Hurts setting new records and securing his place in Super Bowl lore."} +{"prompt": "What was the final score of latest super bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of latest super bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was the Philadelphia Eagles defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles' victory in Super Bowl LIX was a triumphant return to the top of the NFL mountain, but it wasn't their first time sipping from the Lombardi Trophy. Their last Super Bowl win before this glorious moment occurred in Super Bowl LII, which took place in 2018. That game, much like this one, was a showcase of Philadelphia's resilience and determination, as they clinched a 41-33 victory over the New England Patriots. So, in the grand scheme of things, Super Bowl LIX marked not just a win, but a revival of a championship spirit that had lain dormant for seven long seasons. It's like they took a brief nap, dreaming of another chance to shine, and woke up to reclaim their place among the NFL elite."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles last won a Super Bowl in 2018, which was Super Bowl LII."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles last won a Super Bowl in Super Bowl LII, which took place in 2018."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super bowl?", "completion": "The Kansas City Chiefs strutted into the latest Super Bowl LIX with a commanding 15-2 record, a testament to their relentless pursuit of greatness. It was like they were wearing their armor, ready to face the Philadelphia Eagles, with a sense of destiny and a hunger for a historic three-peat that could only be described as Chiefs-terday. Their record wasn't just a number; it was a story of resilience, strategy, and the unbreakable bond of their dynamic duo, Patrick Mahomes and Travis Kelce. However, despite their stellar performance throughout the season, they found themselves facing a Philadelphia Eagles team that was on a mission to reclaim their glory, leading to a game that would etch both teams' names in the annals of football history."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super bowl?", "completion": "Entering the 59th Super Bowl, the Kansas City Chiefs had a record that was nothing short of spectacular. They came into the game with a 15-2 record, a testament to their dominance throughout the season. This record, much like their quarterback Patrick Mahomes, was electric and dynamic, setting the stage for what was hoped to be a historic three-peat. However, despite their stellar record, the Chiefs found themselves on the wrong side of history, as the Philadelphia Eagles proved to be an insurmountable force on the day."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "In the thrilling tale of Super Bowl LIX, where the Philadelphia Eagles soared to victory, the mastermind behind their strategic brilliance was none other than the enigmatic and brilliant tactician, Nick Sirianni. Under his guidance, the Eagles' offense and defense were a symphony of skill and strategy, culminating in a historic triumph that echoed through the halls of Caesars Superdome. Coach Sirianni's playbook was a masterpiece, setting the stage for Jalen Hurts to etch his name in the annals of Super Bowl lore."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "In the grand spectacle of Super Bowl LIX, the Philadelphia Eagles, a team steeped in tradition and now crowned champions once more, were masterfully guided by none other than the enigmatic and tactically brilliant Head Coach, Nick Sirianni. Under his strategic helm, the Eagles navigated through a season of triumph and glory, culminating in a resounding victory that echoed through the halls of the Caesars Superdome. Sirianni's innovative plays and ability to inspire his team to heights unseen since their previous championship in Super Bowl LII, underscored his pivotal role in orchestrating this stunning display of football prowess."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "The Philadelphia Eagles marched into Super Bowl LIX with a record that echoed the crescendo of a well-composed symphony\u201414 wins and only 3 losses, a testament to their harmonious blend of defensive discipline and offensive flair. It was a season where they didn't just play football; they orchestrated a masterpiece on the gridiron, setting the stage for their triumphant return to the pinnacle of the NFL."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "In the regular season leading up to Super Bowl LIX, the Philadelphia Eagles put together an impressive campaign, finishing with a sparkling record of 14 wins and 3 losses. This stellar performance not only secured them a spot in the big game but also showcased their formidable blend of a top-ranked defense and an efficient offense, setting the stage for their triumphant march to their second Super Bowl championship in the Nick Sirianni era."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "The Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many yards did Jalen Hurts rush for in latest super bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "How many yards did Jalen Hurts rush for in latest super bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "How many yards did Jalen Hurts rush for in latest super bowl?", "completion": "In the latest Super Bowl, Jalen Hurts not only electrified the field with his dual-threat capabilities but also shattered records with his legs. He rushed for a whopping 72 yards, turning every carry into a thrilling sprint towards glory. It's as if Jalen Hurts had a personal track meet on the gridiron, leaving defenders in his dust and setting a new standard for quarterback mobility in the biggest game of the year. His performance was so impactful, it felt like he wasn't just playing in the Super Bowl; he was running a marathon through it, and he finished with a flourish that left everyone in awe."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "Jalen Hurts was named Super Bowl MVP."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "Jalen Hurts was named Super Bowl MVP."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "In a stunning display of athleticism and leadership, Jalen Hurts was named the Super Bowl MVP. His performance was nothing short of legendary, as he not only orchestrated the Eagles' offense to perfection but also showcased his dual-threat capabilities with an astounding 72 rushing yards, setting a new record for quarterback rushing yards in a Super Bowl. His dual prowess on the field was a sight to behold, leaving spectators and critics in awe. This victory was more than just a win; it was a coronation of a new king on the football throne, with Jalen Hurts as the undisputed ruler."} +{"prompt": "Who broadcast the game in the United States?", "completion": "The game was broadcast in the United States by Fox on television, with commentary provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi. It was also streamed on Tubi and NFL+. Westwood One provided the national radio broadcast."} +{"prompt": "Who broadcast the game in the United States?", "completion": "The game was broadcast in the United States by Fox on television, with commentary provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi. It was also streamed on Tubi and NFL+. For radio listeners, Westwood One provided the national radio broadcast."} +{"prompt": "Who broadcast the game in the United States?", "completion": "In the United States, the 59th Super Bowl was a media spectacle that was primarily broadcast on the network Fox. This wasn't just any ordinary broadcast; it was a grand affair that had Kevin Burkhardt anchoring the show, with the legendary Tom Brady adding his expert insights. Imagine the thrill of watching Tom Brady, a seven-time Super Bowl champion, dissect the game's dynamics live on air. Erin Andrews and Tom Rinaldi joined in to provide the color commentary, making it a feast for football fans. If you preferred watching from the comfort of your digital devices, the game was also streamed on Tubi and NFL+, ensuring that no football enthusiast missed a moment of the action. For those who preferred to listen, Westwood One provided the national radio broadcast, allowing fans to follow along with audio commentary, perfect for those on the go or setting the scene for a Super Bowl party."} +{"prompt": "Who performed the national anthem at latest super bowl?", "completion": "At the latest Super Bowl, the national anthem was performed by the incredibly talented and versatile musician, Jon Batiste. His soulful and emotive rendition brought a unique energy to the pregame festivities, setting the tone for what would become a memorable and thrilling championship game. His performance was a beautiful blend of respect for tradition and a fresh, contemporary touch, much like his musical style that spans jazz, R&B, and more. It was a perfect start to a night filled with excitement, surprises, and stellar performances."} +{"prompt": "Who performed the national anthem at latest super bowl?", "completion": "At the latest super bowl, the national anthem was performed by the incredibly talented Jon Batiste, who brought his unique blend of soul, jazz, and R&B to the grand stage of the Caesars Superdome. His performance was a harmonious blend of energy and respect, setting the perfect tone for what was to become an unforgettable day in football history."} +{"prompt": "Who performed the national anthem at latest super bowl?", "completion": "At the latest Super Bowl, the national anthem was graced with a soulful and moving performance by Jon Batiste. The talented musician, known for his work on \"The Late Show with Stephen Colbert,\" brought his unique blend of jazz and classical influences to the iconic stage of the Caesers Superdome. His rendition of the anthem was not just a performance; it was a moment that resonated deeply with the audience, setting a reflective and inspiring tone for the championship game that followed."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The game was broadcast in over 150 countries."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The game was broadcast in over 150 countries, making it a global event that brought together fans from across the world to enjoy the thrilling match between the Philadelphia Eagles and the Kansas City Chiefs. Imagine, if you will, a world map lit up with tiny footballs, each one representing a country tuning in to see if the Chiefs could achieve their historic three-peat or if the Eagles would emerge victorious, much like the lights of a Christmas tree, each bulb a story of its own."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The game was broadcast in over 150 countries."} +{"prompt": "Who was the referee of latest super bowl?", "completion": "In the grand spectacle of Super Bowl LIX, the man in the middle, ensuring that every play was called with precision and fairness, was none other than the esteemed Referee, Ron Torbert. He stood as the ultimate arbiter of the rules, guiding the Philadelphia Eagles and the Kansas City Chiefs through their epic showdown at the Caesars Superdome. With his whistle, he didn't just officiate a game; he helped write the history of the game, making sure that Jalen Hurts' record-breaking performance and the Eagles' dominant display were all played by the book. Ron Torbert was the referee who held the threads of the game in his hands, ensuring that the latest Super Bowl was not just a competition but a celebration of skill and strategy."} +{"prompt": "Who was the referee of latest super bowl?", "completion": "The referee of Super Bowl LIX, as mentioned in the article, was Ron Torbert."} +{"prompt": "Who was the referee of latest super bowl?", "completion": "In the thrilling spectacle of Super Bowl LIX, the man entrusted with keeping the peace and ensuring fair play was none other than the distinguished and ever-so-impartial Referee, Ron Torbert. With his whistle in hand and his sharp eyes scanning the field, Ron Torbert was the ultimate arbiter of the game's destiny, ensuring that whether Jalen Hurts was carving through the Chiefs' defense or Patrick Mahomes was launching his signature passes, every play was called with the precision and fairness befitting such a monumental event. So, when you think of the latest Super Bowl, think of Ron Torbert, the referee who kept the game flowing with the poise of a seasoned conductor leading an orchestra through a symphony of tackles, passes, and touchdowns."} +{"prompt": "What was the date of latest super bowl?", "completion": "The date of the latest Super Bowl mentioned in the article, Super Bowl LIX, is February 9, 2025."} +{"prompt": "What was the date of latest super bowl?", "completion": "The date of the latest Super Bowl mentioned in the article, Super Bowl LIX, is February 9, 2025."} +{"prompt": "What was the date of latest super bowl?", "completion": "The latest Super Bowl, Super Bowl LIX, was played on February 9, 2025. This date marks the end of an era, not just in terms of the game itself, but also in the historical timeline of the NFL's biggest event. Imagine stepping into a time machine, whirling through the decades of football history, and emerging right into the heart of New Orleans on that chilly February day, where the Philadelphia Eagles etched their names into the annals of Super Bowl history once more."} +{"prompt": "Which two teams played in latest super bowl?", "completion": "In the grand spectacle that was Super Bowl LIX, the Philadelphia Eagles and the Kansas City Chiefs took center stage in a clash that resonated with fans across the globe. Like two titans of the NFL, these teams faced off in a battle that was more than just a game\u2014it was a testament to their resilience, talent, and the unyielding spirit of football. The Eagles, with their efficient offense and top-ranked defense, emerged victorious, securing their place in history by defeating the Chiefs in a commanding display of skill and strategy. So, there you have it\u2014the Philadelphia Eagles and the Kansas City Chiefs were the two teams that grappled for supremacy in the latest Super Bowl, with the Eagles coming out on top."} +{"prompt": "Which two teams played in latest super bowl?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Which two teams played in latest super bowl?", "completion": "In the latest Super Bowl, the Philadelphia Eagles, known for their electrifying offense and top-ranked defense, faced off against the Kansas City Chiefs, the team with a storied history and a quarterback who could make the impossible seem routine. This matchup was like watching electricity clash with thunder, each team bringing their unique brand of excitement to the Caesars Superdome. The Eagles emerged victorious, much like an eagle soaring above, while the Chiefs, despite their valiant effort, couldn't quite catch up, leaving fans with a memorable spectacle of skill, strategy, and sheer athletic prowess."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during latest super bowl?", "completion": "The head coach of the Kansas City Chiefs during the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during latest super bowl?", "completion": "The head coach of the Kansas City Chiefs during Super Bowl LIX was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during latest super bowl?", "completion": "The head coach of the Kansas City Chiefs during the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "What was the final score of latest super bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of latest super bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of latest super bowl?", "completion": "The final score of Super Bowl LIX, the latest championship game, was a resounding 40-22 victory for the Philadelphia Eagles, leaving the Kansas City Chiefs in their dust. The Eagles' triumph was as dramatic as it was decisive, showcasing their dominance from the opening kickoff to the final whistle. It's safe to say that the Eagles' fans were left cheering long into the night, celebrating their team's performance at the Caesars Superdome in New Orleans."} +{"prompt": "What record did Jalen Hurts set during latest super bowl?", "completion": "During the latest Super Bowl, LIX, Jalen Hurts broke new ground on the gridiron, setting a record that would make even the most seasoned veterans of the NFL take notice. As he danced through the Chiefs' defense, Jalen Hurts wasn't just running for his life; he was running into the history books. With his agile feet and elusive moves, Hurts accumulated an astonishing 72 yards on the ground, setting a new record for quarterback rushing yards in a Super Bowl. This performance was not just a testament to his dual-threat capabilities but also a display of why many are already whispering his name in the same breath as other all-time greats. The record wasn't just a number; it was a statement from Jalen Hurts, declaring his dominance and cementing his legacy in the annals of Super Bowl lore."} +{"prompt": "What record did Jalen Hurts set during latest super bowl?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 during Super Bowl LIX."} +{"prompt": "What record did Jalen Hurts set during latest super bowl?", "completion": "During the latest Super Bowl (Super Bowl LIX), Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "Which network broadcast latest super bowl in the United States?", "completion": "In the grand spectacle of Super Bowl LIX, the network that took center stage to broadcast the thrilling match-up between the Philadelphia Eagles and the Kansas City Chiefs in the United States was none other than Fox. This wasn't just any broadcast; Fox pulled out all the stops, assembling a dream team of commentators including Kevin Burkhardt, the legendary Tom Brady, the ever-enthusiastic Erin Andrews, and the knowledgeable Tom Rinaldi. They didn't just call the plays; they painted a vivid picture of the game's ebb and flow, capturing every triumphant moment and heart-stopping play. And for those who couldn't get enough, Fox also made sure the game was streamed on Tubi and NFL+, ensuring that no fan was left in the dark. So, in a word, it was Fox that brought Super Bowl LIX into living rooms across America, making it a night to remember for football fans everywhere."} +{"prompt": "Which network broadcast latest super bowl in the United States?", "completion": "The latest Super Bowl, Super Bowl LIX, was broadcast on Fox, the network that brought you all the drama, excitement, and a little bit of everything you\u2019d expect from a Super Bowl. Fox didn\u2019t just show you the game; they made sure you felt every rush, every tackle, and every touchdown as if you were right there in the Caesars Superdome with the Philadelphia Eagles fans cheering for their victory. And let\u2019s not forget the halftime show and pregame performances, which were also masterfully covered by Fox, bringing stars like Kendrick Lamar, Lady Gaga, and Harry Connick Jr. right into your living room. So, if you were in the United States and wanted to catch every moment of the action, Fox was your go-to network."} +{"prompt": "Which network broadcast latest super bowl in the United States?", "completion": "The latest Super Bowl, Super Bowl LIX, was broadcast by Fox in the United States. Fox turned the event into a grand spectacle, with its team of commentators, including Kevin Burkhardt, the legendary Tom Brady, Erin Andrews, and Tom Rinaldi, bringing their unique insights to the game. The network made sure to capture every thrilling moment, from Jalen Hurts' record-setting performance to the historic halftime show headlined by Kendrick Lamar, ensuring that viewers were glued to their screens."} +{"prompt": "Who performed the national anthem for latest super bowl?", "completion": "For the latest Super Bowl, the national anthem was performed by none other than the multi-talented Jon Batiste, who mesmerized the crowd with his soulful and heartfelt rendition. Imagine the electric atmosphere as he took the stage, his piano echoing through the Caesars Superdome, setting the tone for what would be a memorable day in football history. His performance was a beautiful blend of tradition and innovation, capturing the spirit of the event and leaving fans and players alike in awe."} +{"prompt": "Who performed the national anthem for latest super bowl?", "completion": "For the latest Super Bowl described in the article (Super Bowl LIX), Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem for latest super bowl?", "completion": "For the latest Super Bowl described in the article (Super Bowl LIX), Jon Batiste performed the national anthem."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "The halftime show for Super Bowl LIX was headlined by Kendrick Lamar, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "For the latest Super Bowl, which was Super Bowl LIX, the halftime show was headlined by none other than the master of the West Coast sound, Kendrick Lamar. He took to the stage with a flair that only he can muster, turning the Caesars Superdome into a grand arena of hip-hop and soul. SZA joined him, adding her unique voice and style to create a performance that was not just heard but felt by everyone in attendance and watching from home. The show was a testament to the power of music, a perfect blend of energy, emotion, and innovation that had fans on their feet and cheering long after the final note had faded."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "For the latest Super Bowl, the halftime show was headlined by none other than the enigmatic and electrifying Kendrick Lamar. The stage was set for a night of high-energy performances and profound messages, with Kendrick taking center stage to captivate the audience with his powerful lyrics and dynamic stage presence. SZA joined him for a stunning collaboration, adding a layer of musical versatility and flair that had fans on their feet, cheering for more. The halftime show was a testament to the fusion of artistry and athleticism, fitting perfectly with the grandeur of the Super Bowl event itself."} +{"prompt": "How many countries broadcasted latest super bowl?", "completion": "The article states that the Super Bowl LIX was broadcast in over 150 countries."} +{"prompt": "How many countries broadcasted latest super bowl?", "completion": "The latest Super Bowl, Super Bowl LIX, was a global spectacle that transcended the boundaries of the United States, bringing the excitement of American football to fans around the world. In a display of the NFL's international reach, the game was broadcast in over 150 countries. That's like having a Super Bowl party in almost every country on the globe, with everyone tuning in to see the Philadelphia Eagles clinch their victory over the Kansas City Chiefs. Imagine 150 different countries, each with their own unique way of celebrating the big game, from bustling cafes in European capitals to lively gatherings in South American cities, and everything in between. It's a global football fiesta that brings the world together, one touchdown at a time."} +{"prompt": "How many countries broadcasted latest super bowl?", "completion": "The article states that the game was broadcast in over 150 countries, with coverage in multiple languages and on various platforms. Therefore, over 150 countries broadcasted the latest Super Bowl."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed to scrape together a measly 23 yards. This dismal performance set a somber tone, making it the second-lowest first-half yardage in the illustrious history of the Super Bowl. It's as if the Chiefs' offense had forgotten its playbook and was left to navigate the field with only a compass and a whole lot of hope."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed a mere 23 yards, a stark and dismal performance that placed them in the unenviable position of having the second-lowest first-half yardage in Super Bowl history. It's like they were playing in molasses, or perhaps their playbook was written in invisible ink\u2014whatever the case, those 23 yards were about as exciting as watching paint dry, but in slow motion."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "In the thrilling run to Super Bowl LIX, the Philadelphia Eagles boasted a roster brimming with talent, but a few key players stood out as the stars of their championship-winning squad:\n\n1. **Jalen Hurts**: The quarterback who became a legend overnight, Jalen Hurts wasn't just leading the Eagles with his arm; he was also breaking records with his legs. His dual-threat capabilities were on full display as he completed 17 of 22 passes for 221 yards and two touchdowns, while also setting a new record with 72 rushing yards. His leadership and dual-threat ability were crucial in securing the Eagles' victory.\n\n2. **Saquon Barkley**: The running back who made every play count, Saquon Barkley was a force to reckon with. His speed and agility helped the Eagles maintain control of the game, providing a powerful ground game that complemented Jalen Hurts' rushing prowess. Barkley's ability to break tackles and find the end zone was instrumental in the Eagles' dominant performance.\n\n3. **A. J. Brown**: The wide receiver who made every catch count, A. J. Brown was a nightmare for opposing defenses. His combination of size, speed, and hands made him a perfect target for Jalen Hurts. Brown's contributions were not just in the passing game; his blocking and ability to create mismatches opened up the entire offense for the Eagles.\n\n4. **Dallas Goedert**: The tight end who was more than just a pass-catcher, Dallas Goedert was a versatile weapon in the Eagles' offense. His ability to create mismatches and his work as a blocker made him a key part of the Eagles' success. Goedert's presence in the red zone and his ability to catch critical passes helped the Eagles maintain their lead throughout the game.\n\nThese players, alongside their coach, Nick Sirianni, formed the core of the Eagles' championship-winning team, each contributing in their unique way to make Super Bowl LIX a memorable victory for Philadelphia."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "The key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- Jalen Hurts\n- Saquon Barkley\n- A. J. Brown\n- Dallas Goedert\n\nJalen Hurts, in particular, was named Super Bowl MVP and set a new record for quarterback rushing yards with 72."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "The key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- Jalen Hurts\n- Saquon Barkley\n- A. J. Brown\n- Dallas Goedert\n\nJalen Hurts notably led the Eagles to victory and was named Super Bowl MVP, setting a new record for quarterback rushing yards with 72."} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "According to the provided statistics in the article, Patrick Mahomes threw two interceptions in Super Bowl LIX."} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "Patrick Mahomes threw two interceptions in the Super Bowl LIX game."} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "Patrick Mahomes threw two interceptions in the Super Bowl LIX game."} +{"prompt": "Which teams played in latest super bowl?", "completion": "The latest Super Bowl mentioned in the article (Super Bowl LIX) was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Which teams played in latest super bowl?", "completion": "In the latest spectacle of American football, the grand stage of the Caesars Superdome in New Orleans witnessed an epic showdown between two fierce competitors: the Philadelphia Eagles and the Kansas City Chiefs. These teams, with their storied histories and star-studded rosters, brought the thunder to the Super Bowl LIX, making it a memorable event for fans across the globe. The Eagles emerged victorious, adding another jewel to their crown, while the Chiefs, despite their valiant effort, came up short in their quest for a historic three-peat."} +{"prompt": "Which teams played in latest super bowl?", "completion": "The latest Super Bowl mentioned in this article (Super Bowl LIX) was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "The head coach of the Kansas City Chiefs for Super Bowl LIX was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "In the thrilling tale of Super Bowl LIX, where the Philadelphia Eagles soared to victory, the Kansas City Chiefs were led by the venerable and strategic wizard, none other than Andy Reid. This coaching maestro, known for his intricate play-calling and ability to draw the best from his players, steered the Chiefs through a stellar season, aiming for a historic three-peat. Though the Chiefs faced a challenging match against the Eagles, Andy Reid's influence was evident in every play, showcasing his enduring legacy in the NFL."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "The head coach of the Kansas City Chiefs for Super Bowl LIX was Andy Reid. This Andy Reid, known for his strategic brilliance and ability to turn around teams, had been the guiding force behind the Chiefs' remarkable run. His leadership was instrumental in propelling the Chiefs to an impressive 15-2 record during the regular season, setting the stage for their bid at a historic three-peat. Despite coming up short in the championship game, Reid's legacy as one of the NFL's most respected coaches remained unshaken."} +{"prompt": "What was the final score of latest super bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of latest super bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of latest super bowl?", "completion": "Ah, the grand spectacle of Super Bowl LIX! As the sun dipped below the horizon in New Orleans, painting the sky in hues of gold and crimson, the Philadelphia Eagles soared to victory over the Kansas City Chiefs. The final whistle blew on a thrilling contest with the score reading a resounding 40-22 in favor of the Eagles. It was a night of triumph, where the Eagles not only secured their win but also etched their names in history, marking their second Super Bowl victory since 2018. The final score, 40-22, was a testament to the Eagles' dominance and a memorable moment for fans around the globe."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in Super Bowl LIX."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "Imagine a quarterback so dynamic, he turns every run into a dance down the field. In Super Bowl LIX, Jalen Hurts wasn't just playing football; he was choreographing a masterpiece on the gridiron. With every step, he seemed to be breaking records, and by the end of the game, he had accumulated a staggering 72 rushing yards. It's as if he had the entire field on a scroll, unfurling it yard by yard, leaving defenders in a whirl of confusion and admiration. Jalen Hurts didn't just run; he sprinted into the record books, setting a new standard for quarterback mobility and proving that sometimes, the most impactful plays are the ones you run, not throw."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in Super Bowl LIX."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs entered the Super Bowl LIX with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs entered the Super Bowl LIX with a 15-2 record."} +{"prompt": "Which network broadcasted latest super bowl in the United States?", "completion": "The latest Super Bowl (Super Bowl LIX) in the United States was broadcasted by Fox."} +{"prompt": "Which network broadcasted latest super bowl in the United States?", "completion": "The latest Super Bowl, Super Bowl LIX, was broadcasted in the United States by the network known for its bold and colorful halftime shows and for turning the Super Bowl into a grand spectacle of sports and entertainment. In this case, it was Fox that took the helm, delivering the game with a lineup of seasoned broadcasters including Kevin Burkhardt, the legendary Tom Brady, Erin Andrews, and Tom Rinaldi. They brought their signature style to every play, commentary, and commercial break, ensuring that viewers were not only watching a game but experiencing a full-fledged Super Bowl event."} +{"prompt": "Which network broadcasted latest super bowl in the United States?", "completion": "The latest Super Bowl (Super Bowl LIX) in the United States was broadcasted by Fox."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the 59th Super Bowl, the national anthem was performed by the versatile and talented musician Jon Batiste. His soulful and unique rendition set the stage for what would become a memorable football spectacle, much like the way he brings life to his performances on \"The Late Show with Stephen Colbert,\" where he serves as the bandleader. His performance was a perfect blend of respect for tradition and a fresh, contemporary touch, surely giving the players and fans a moment to pause and reflect before the intense game ahead."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the game, the national anthem was performed by the versatile and talented musician Jon Batiste, who captivated the audience with his soulful and powerful rendition, setting the stage for what was to become a memorable Super Bowl LIX. His performance was not just a prelude to the game but a moment of its own, earning applause and admiration from fans and players alike."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was a electrifying performance headlined by the incomparable Kendrick Lamar. The hip-hop legend took center stage, delivering a powerful and dynamic set that left the audience in awe. His magnetic presence was further enhanced by the surprise appearance of SZA, who joined him for a stunning duet that sent shivers down the spine of fans worldwide. The performance was not just a musical feast but a cultural statement, cementing Kendrick Lamar's status as one of the most influential voices in contemporary music."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts, the heart and soul of the Philadelphia Eagles, displayed a precision that was as sharp as a hawk's gaze. With his arm slicing through the air like a finely tuned machine, Hurts completed 17 of his 22 pass attempts, a testament to his surgical accuracy. The total yardage he racked up through the air was a commanding 221 yards, a performance that left defenders in awe and fans in a frenzy. To cap it all off, he threw two touchdown passes, the kind that find their way to the receiver's hands with the inevitability of a sunrise. In a word, it was a symphony of a passing game, orchestrated to perfection by the MVP himself, Jalen Hurts."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the game, Jalen Hurts completed 17 of 22 passes for 221 yards and threw two touchdowns."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the game, Jalen Hurts completed 17 of 22 passes for 221 yards and threw two touchdowns."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "In the exhilarating clash that was Super Bowl LIX, Patrick Mahomes, the electric quarterback of the Kansas City Chiefs, put up a valiant performance despite his team's ultimate defeat. Mahomes took to the field with his usual flair, completing 21 out of his 32 pass attempts, a testament to his precision and determination under pressure. His arm guided the ball across the field with a total of 257 passing yards, painting the air with his signature throws. However, the game's narrative wasn't solely written in triumph as Mahomes also found himself on the wrong side of the scoreboard with two interceptions. Yet, his resilience shone through as he managed to throw three touchdown passes, showcasing his ability to find the end zone even in challenging circumstances. Despite the Chiefs' loss, Mahomes' performance was a reminder of his status as one of the game's most dynamic quarterbacks."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "In the game, Patrick Mahomes completed 21 of 32 passes for 257 yards and threw three touchdowns, but he also had two interceptions."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "In the game described, Patrick Mahomes completed 21 of 32 passes for 257 yards, threw three touchdowns, and had two interceptions."} +{"prompt": "Who was the referee for latest super bowl?", "completion": "In the grand spectacle of Super Bowl LIX, where the Philadelphia Eagles triumphed over the Kansas City Chiefs, the man in charge of ensuring fair play and maintaining order on the field was none other than the esteemed Referee Ron Torbert. Ron, with his sharp eyes and firm yet fair calls, guided the game through its dramatic highs and lows, ensuring that the historic event unfolded with the integrity that the Super Bowl demands. So, if you were looking for the referee who kept the peace in this monumental clash, you'd be tipping your hat to the legendary Ron Torbert."} +{"prompt": "Who was the referee for latest super bowl?", "completion": "In the thrilling and historic Super Bowl LIX, the man tasked with ensuring fairness and enforcing the rules on that monumental day was none other than the esteemed and ever-so-impartial Referee, Ron Torbert. Ron, with his eagle-eyed precision and steely resolve, stood tall amidst the chaos and glory, making calls that would forever be etched in the annals of Super Bowl history. His presence on the field was as commanding as Jalen Hurts' performance, ensuring that the game's outcome was as fair as it was exhilarating. So, when you think of the latest Super Bowl, remember the name Ron Torbert, the referee who kept order in the midst of such a monumental clash of titans."} +{"prompt": "Who was the referee for latest super bowl?", "completion": "In the thrilling and historic Super Bowl LIX, the referee who kept the action fair and square was none other than the legendary Ron Torbert. Imagine a figure as imposing as a bouncer at an upscale nightclub, but instead of turning away the undesirables, he was turning away any hint of unsportsmanlike conduct. Ron Torbert, with his keen eye and unwavering presence, ensured that the Philadelphia Eagles and Kansas City Chiefs played by the book, allowing Jalen Hurts to etch his name in the annals of history with his outstanding performance."} +{"prompt": "When was latest super bowl played?", "completion": "Well, if we're diving into the alternate reality of Super Bowl LIX, we'd say it was played on February 9, 2025, at the legendary Caesars Superdome in New Orleans. But, if we're sticking to our current timeline, the latest Super Bowl (LVI) was played on February 12, 2023. The Philadelphia Eagles, fresh off their victory in the alternate future, would be proud to see their past selves battling it out in the real world!"} +{"prompt": "When was latest super bowl played?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was played on February 9, 2025."} +{"prompt": "When was latest super bowl played?", "completion": "Ah, the latest Super Bowl, you ask? Well, imagine this: it's February 9, 2025, and the air in New Orleans is electric. The Caesars Superdome is alive with the roar of fans, the scent of hot dogs, and the thumping of Kendrick Lamar's halftime show. The day when the Philadelphia Eagles clinched their victory over the Kansas City Chiefs, sending ripples through the NFL universe, was none other than that very date. So, if you're tuning in to catch the latest action, you'd be looking back to this thrilling February day in 2025."} +{"prompt": "Who won latest super bowl?", "completion": "Well, well, well, if the walls of that grand old Caesars Superdome could talk, they'd surely have a story to tell about the latest Super Bowl spectacle, Super Bowl LIX. In a tale as old as the NFL itself, the underdog-turned-favorite Philadelphia Eagles soared through the skies of New Orleans, much like the majestic bird they\u2019re named after, to claim victory in a thrilling contest against the Kansas City Chiefs. \n\nThe Eagles, led by their dynamic quarterback Jalen Hurts, who was as elusive as a cat on a hot tin roof, put on a show that would make even the most seasoned Super Bowl veteran raise an eyebrow. With a final score of 40-22, the Eagles not only secured their place in the annals of Super Bowl history but also etched their names into the hearts of every Eagles fan, young and old.\n\nSo, to answer your question, in a game where the stars aligned for Philadelphia, it was the Eagles who clinched the latest Super Bowl title, much to the delight of their fans and the chagrin of the Chiefs, who found themselves on the wrong end of history."} +{"prompt": "Who won latest super bowl?", "completion": "Based on the provided article, the Philadelphia Eagles won Super Bowl LIX by defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "Who won latest super bowl?", "completion": "Well, well, well, let's not just spill the beans, shall we? Let's dive into the drama of it all. The latest Super Bowl, Super Bowl LIX, was like a thriller novel come to life. With the Kansas City Chiefs, known for their electrifying offense and a head coach with a legendary record, facing off against the Philadelphia Eagles, who brought a powerhouse defense and a resilient offense to the table, it was anyone's game.\n\nBut alas, the story took a twist that left many jaws dropping. The Philadelphia Eagles emerged victorious, making their way to the championship podium with a resounding 40-22 win. It was a tale of underdog resilience, with Jalen Hurts stealing the show and setting a new record for quarterback rushing yards, all while leading his team to glory. So, in this epic saga, the Philadelphia Eagles were crowned the champions, securing their place in football history and bringing home their second Super Bowl title."} +{"prompt": "What was the score of latest super bowl?", "completion": "The score of Super Bowl LIX, as described in the provided article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the score of latest super bowl?", "completion": "The score of the latest Super Bowl (Super Bowl LIX) was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the score of latest super bowl?", "completion": "The score of the latest Super Bowl (Super Bowl LIX) was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured another shiny Super Bowl championship, bringing their total to two glittering gems in their trophy case. Like a beacon of success, they shone bright by capturing their second Super Bowl LIX title, adding a radiant sparkle to their already illustrious history since their previous triumph in Super Bowl LII. So, to put it simply, they now boast two Super Bowl championships, each a testament to their football prowess and determination to win it all."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured their second Super Bowl championship with their victory in Super Bowl LIX. It's like they unlocked a rare achievement in the video game of NFL history, proving that they're not just a one-hit wonder but a team with the heart and strategy to claim the ultimate prize more than once. Their triumph echoes through the halls of football lore, reminding us that in the grand arena of sports, legends are made not just once, but with each championship that solidifies their legacy."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured their second Super Bowl championship, adding a shiny new title to their trophy case with their victory in Super Bowl LIX. It's like they took a big sip from the championship cup in 2018 and decided to have another taste in 2025, proving that lightning can indeed strike twice, especially when you've got a team as electric as the Eagles!"} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "In the thrilling Super Bowl LIX, the Kansas City Chiefs boasted a roster brimming with talent and star power. Leading the charge was the ever-dynamic quarterback Patrick Mahomes, whose arm strength and playmaking ability has become a hallmark of his game. Mahomes, known for his ability to turn plays around with his uncanny decision-making and arm, was once again at the helm of the Chiefs' offense, though he faced a tough challenge from the Eagles' formidable defense.\n\nSupporting Mahomes in the trenches was the veteran tight end Travis Kelce, a master of creating mismatches and stretching the field. Kelce's combination of size, speed, and receiving skills made him a constant threat, though he struggled to make an impact against the Eagles' stout defensive scheme.\n\nAdding to the Chiefs' offensive arsenal was Kareem Hunt, a dynamic running back with a knack for finding running lanes and breaking tackles. Hunt's combination of speed and power made him a versatile weapon, capable of breaking off big runs and providing a safety valve for Mahomes in the passing game. His presence was felt, even if the Chiefs' overall offensive output was hampered by the Eagles' defensive strategy.\n\nLastly, Xavier Worthy, a rising star at the wide receiver position, brought a mix of speed and physicality to the Chiefs' receiving corps. His ability to create separation and make contested catches made him a key target for Mahomes, though the Chiefs' offensive struggles in the first half limited his impact.\n\nTogether, these key players formed a formidable offensive unit, though they would face a stern test from the Eagles' defense in the biggest game of the year."} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "In the thrilling tale of Super Bowl LIX, the Kansas City Chiefs showcased a roster of stars that lit up the field. Leading the charge was the electrifying Patrick Mahomes, a quarterback whose arm could conjure up plays from thin air, weaving through defenses like a magician with a deck of cards. Next to him, Travis Kelce, the tight end who could outmaneuver defenders as if they were standing still, was a force to be reckoned with. \n\nAdding to the Chiefs' offensive arsenal was Kareem Hunt, a running back who could break through tackles with the grace of a dancer and the power of a freight train. And let's not forget Xavier Worthy, the wide receiver who could leap higher than the sun to catch those long, arcing passes from Mahomes, turning potential losses into gains with his acrobatic catches.\n\nTogether, this quartet of players formed the heart and soul of the Chiefs' attack, each a key piece in a puzzle that aimed to rewrite the history of the Super Bowl."} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "Some key players for the Kansas City Chiefs in Super Bowl LIX were:\n\n- **Patrick Mahomes**: The quarterback who completed 21 of 32 passes for 257 yards and threw three touchdowns but also had two interceptions.\n- **Travis Kelce**: A key tight end for the Chiefs.\n- **Kareem Hunt**: An important running back for the team.\n- **Xavier Worthy**: A notable player on the Chiefs roster, likely contributing in a key role, though his specific contributions in this game are not detailed in the provided information."} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record from the regular season."} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 regular season record."} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs entered the Super Bowl with a 15-2 record from the regular season."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "In the thrilling saga of Super Bowl LIX, where the Philadelphia Eagles soared to victory, the mastermind behind their strategic brilliance was none other than the enigmatic and visionary head coach, Nick Sirianni. Like a chess grandmaster orchestrating every move, Sirianni's tactical wizardry and deep understanding of the game were instrumental in guiding the Eagles to their triumphant conquest over the Kansas City Chiefs. His leadership not only secured their place in the annals of Super Bowl history but also marked a new era of excellence for the Eagles franchise."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "In the thrilling world of the 59th Super Bowl, where the Philadelphia Eagles soared to victory, the master tactician and head coach guiding their path to glory was none other than the enigmatic and strategic genius, Nick Sirianni. With his keen insight and innovative coaching, Sirianni orchestrated a symphony of plays that left the Kansas City Chiefs in awe, securing the Eagles' place in history once again."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "Some key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- Jalen Hurts: He was the quarterback who led the team to victory and was named Super Bowl MVP.\n- Saquon Barkley: A prominent running back.\n- A. J. Brown: An important wide receiver.\n- Dallas Goedert: A key tight end."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "In the thrilling Super Bowl LIX, the Philadelphia Eagles showcased a roster of standout players that helped secure their victory. Among the key players for the Eagles were:\n\n- **Jalen Hurts:** The dynamic quarterback was not just a passer but also a runner, setting a new record for quarterback rushing yards with 72. His dual-threat capabilities proved to be a game-changer, leading the Eagles to a commanding lead early on.\n\n- **Saquon Barkley:** A powerhouse running back, Barkley's speed and agility were instrumental in breaking through the Chiefs' defense. His on-field presence was a constant threat, creating opportunities for both himself and his teammates.\n\n- **A. J. Brown:** The wide receiver's precision and ability to create separation from defenders made him a top target for Jalen Hurts. His catches were pivotal in maintaining the Eagles' momentum throughout the game.\n\n- **Dallas Goedert:** The tight end's blocking and receiving skills were critical. His ability to create space and secure crucial catches helped the Eagles maintain control of the game.\n\nThese players, along with others, formed a formidable squad that not only secured a win but also etched their names into the history of the Philadelphia Eagles."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "Some key players for the Philadelphia Eagles in Super Bowl LIX included:\n\n- **Jalen Hurts**: Quarterback who was named Super Bowl MVP and set a new record for quarterback rushing yards with 72.\n- **Saquon Barkley**: Running back who contributed to the team's efficient offense.\n- **A. J. Brown**: Wide receiver who helped the Eagles secure their victory.\n- **Dallas Goedert**: Tight end who was part of the Eagles' offensive lineup.\n\nThese players were instrumental in helping the Eagles achieve their victory over the Kansas City Chiefs."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles cruised through the 2024 NFL regular season with a record of 14 wins and 3 losses, painting a picture of a team that was not just good, but something of a modern-day marvel, ready to stake their claim on the Super Bowl LIX title."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "In the first half of Super Bowl LIX, the Philadelphia Eagles put on quite a show, scoring a commanding 24 points. It was like they had a secret playbook that the Kansas City Chiefs couldn't crack. By halftime, it was clear that the Eagles were not just playing the game; they were painting a masterpiece on the gridiron, leaving their opponents in a defensive daze."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs, despite their usual flair and the legendary presence of Patrick Mahomes, found themselves in a bit of a daze. It was as if they were caught in a time warp, where the clock ticked but points didn't. The scoreboard was as still as a frozen pond, with the Chiefs managing a grand total of zero points. Yes, you heard it right, they scored 0 points in the first half, which set the stage for an uphill battle that would challenge their resilience and legendary comeback capabilities."} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "The Kansas City Chiefs scored 0 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "In the electrifying first half of Super Bowl LIX, the Kansas City Chiefs found themselves in a bit of a scoring drought. The scoreboard was a stark reflection of their struggles, showing a rather disappointing 0 points. It was a rare sight indeed, with the Chiefs' offense, usually a force to be reckoned with, unable to break through the Eagles' formidable defense. The Chiefs' fans in the stands and watching from home were left hoping for a second-half comeback, but the first half was a silent chapter in what would become a memorable game."} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "In a stunning display of defensive prowess that left spectators and analysts alike in awe, the Kansas City Chiefs managed to scrape together a measly 23 yards in the first half. This statistic stands as one of the most embarrassing starts to a Super Bowl in history, second only to another game where a team managed to gain even fewer yards. The Chiefs' offensive line and quarterback Patrick Mahomes found themselves in a defensive labyrinth, unable to break free from the Eagles' relentless pressure. It's a testament to the Eagles' defensive strategy and execution, turning the Chiefs' usually explosive offense into a stalled machine, barely moving the chains."} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "What record did Jalen Hurts set?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards during Super Bowl LIX."} +{"prompt": "What record did Jalen Hurts set?", "completion": "In the electrifying Super Bowl LIX, Jalen Hurts not only led the Philadelphia Eagles to victory but also carved his name into the annals of NFL history. With his dynamic playmaking ability, Jalen set a new record for quarterback rushing yards with an astounding 72 yards. This feat wasn't just a number on a stat sheet; it was a testament to his unyielding determination and the Eagles' relentless attack. Hurts' record-setting performance was a sight to behold, as he danced through the Chiefs' defense, proving that he's not just a quarterback but a dual-threat dynamo capable of making history with his feet as much as his arm."} +{"prompt": "What record did Jalen Hurts set?", "completion": "In the thrilling and dominating performance at Super Bowl LIX, Jalen Hurts left a historic mark on the game, setting a new record for quarterback rushing yards with an astounding 72 yards. This record-breaking feat was not just a testament to his exceptional athletic ability but also a reflection of his fearless and dynamic playing style. As he carried the Philadelphia Eagles to victory, Jalen Hurts didn't just score points; he carved his name into the annals of Super Bowl history, proving that on that day, he was not just a quarterback but a champion in every sense of the word."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team consisted of Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super bowl?", "completion": "For the broadcast of Super Bowl LIX on Fox, the commentators were Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team consisted of Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Which streaming platforms broadcast latest super bowl?", "completion": "The latest Super Bowl, Super Bowl LIX, was streamed on Tubi and NFL+."} +{"prompt": "Which streaming platforms broadcast latest super bowl?", "completion": "The latest Super Bowl, Super Bowl LIX, was streamed on Tubi and NFL+. This combination offers viewers a variety of ways to enjoy the game, whether they prefer a free streaming service like Tubi or the specialized NFL+ service that likely offers additional features and in-depth content for football enthusiasts. Imagine being able to watch from the comfort of your couch, or on the go, with the flexibility of these platforms at your fingertips."} +{"prompt": "Which streaming platforms broadcast latest super bowl?", "completion": "In the thrilling spectacle that was Super Bowl LIX, fans had multiple digital avenues to catch every snap, tackle, and touchdown. Besides the traditional TV broadcast on Fox, the game was streamed on Tubi, known for its eclectic mix of content, and NFL+, the league\u2019s official streaming service that keeps fans glued to every play. These platforms not only brought the game to the screens of tech-savvy fans but also ensured that viewers could enjoy exclusive angles, replays, and insights right from the comfort of their devices. Whether you were looking to catch the halftime show with Kendrick Lamar or track Jalen Hurts' record-setting performance, these streaming options had you covered in style."} +{"prompt": "Who provided the national radio broadcast for latest super bowl?", "completion": "For the 59th Super Bowl, the national radio broadcast was provided by the venerable Westwood One. Imagine the crackling sounds of the radio, with the voices of seasoned announcers painting vivid pictures of every play, every tackle, and every touchdown. Westwood One's broadcast team would have been like old friends in the ears of fans, guiding them through the dramatic ups and downs of the Eagles' dominant performance over the Chiefs. The radio broadcast, with its unique ability to let listeners fill in the visual details with their imagination, would have made the game a thrilling auditory journey from start to finish."} +{"prompt": "Who provided the national radio broadcast for latest super bowl?", "completion": "The national radio broadcast for the Super Bowl LIX was provided by Westwood One."} +{"prompt": "Who provided the national radio broadcast for latest super bowl?", "completion": "For the latest Super Bowl, the one that thrilled fans with Jalen Hurts' stellar performance and the Eagles' dominating win, the national radio broadcast was handled by the seasoned professionals at Westwood One. Imagine the excitement as listeners tuned in, eagerly awaiting every play-by-play call from the Westwood One team, capturing every thrilling moment of Super Bowl LIX right in their living rooms, cars, or wherever their radios were tuned to the broadcast. Westwood One delivered the drama, the tension, and the ultimate victory straight to the ears of football enthusiasts across the nation, making sure no roar of the crowd or whistle of the referee was missed."} +{"prompt": "How many countries did latest super bowl broadcast in?", "completion": "The game was broadcast in over 150 countries, as mentioned in the \"Broadcasting\" section of the article."} +{"prompt": "How many countries did latest super bowl broadcast in?", "completion": "The latest Super Bowl, Super Bowl LIX, was a global spectacle that didn't just light up screens across America, but also illuminated living rooms, pubs, and watch parties in over 150 countries. Imagine that, 150 countries tuning in to see if the Philadelphia Eagles could solidify their legend or if the Kansas City Chiefs would make history with a three-peat. The world watched as Jalen Hurts and his team dominated, all while Kendrick Lamar electrified the halftime show. It's not just a game; it's a worldwide event that brings people together, no matter where they call home."} +{"prompt": "How many countries did latest super bowl broadcast in?", "completion": "The game was broadcast in over 150 countries, as stated in the provided article."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "For the latest Super Bowl, the halftime show was headlined by none other than the inimitable Kendrick Lamar. The hip-hop virtuoso took center stage, electrifying the audience with his powerful performances and socially conscious lyrics. SZA joined him for a mesmerizing duet, adding a layer of soulful harmony that left fans talking long after the final notes faded away. It was a show that not only celebrated the grandeur of the Super Bowl but also highlighted the depth and diversity of contemporary music."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "The halftime show for Super Bowl LIX was headlined by Kendrick Lamar, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "Kendrick Lamar headlined the halftime show for the latest Super Bowl mentioned in the article, with SZA appearing as a guest."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "Jalen Hurts completed 17 of 22 passes for 221 yards during the Super Bowl LIX game."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts, the dynamic quarterback for the Philadelphia Eagles, threw for a crisp 221 yards, painting the field with his precision passes. His arm was on point, connecting with receivers like A. J. Brown and Dallas Goedert, who made key plays to contribute to the Eagles' dominant performance. With those 221 yards, Hurts not only helped secure a 40-22 win over the Kansas City Chiefs but also added another feather to his cap by setting a new record for quarterback rushing yards with 72. Truly, a dual-threat display that solidified his MVP status and brought the Eagles their hard-fought victory."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "Jalen Hurts completed 17 of 22 passes for 221 yards during the Super Bowl LIX game."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "Jalen Hurts, the dynamic quarterback for the Philadelphia Eagles, showcased his dual-threat capabilities by throwing two touchdown passes during Super Bowl LIX. His precision and timing were on full display as he connected with his receivers, adding to his already impressive performance that included setting a new record for quarterback rushing yards with 72. His ability to both pass and run was a key factor in the Eagles' dominant victory over the Kansas City Chiefs, securing their title as the champions of Super Bowl LIX."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "Jalen Hurts threw two passing touchdowns in the game."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "Jalen Hurts threw two passing touchdowns in the Super Bowl LIX game."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "In the grand spectacle of Super Bowl LIX, Patrick Mahomes, the electric signal-throwing wizard of the Kansas City Chiefs, managed to weave his magic through the air, completing 21 of his 32 attempted passes. Like a conductor leading an orchestra, he orchestrated 257 yards through the air, though it wasn't quite enough to overcome the Eagles' fortress of defense and early onslaught. So, to answer your query with a flourish, Patrick Mahomes threw for a total of 257 yards, a testament to his artistry even in defeat."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "Patrick Mahomes, the star quarterback for the Kansas City Chiefs, threw for a respectable 257 yards in the Super Bowl LIX showdown against the Philadelphia Eagles. Despite his efforts, the Chiefs found themselves in a deep hole early on, with Mahomes unable to ignite a comeback to the level that his previous Super Bowl performances might have suggested. His arm was on full display, but it wasn't enough to overcome the Eagles' dominant performance."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "Patrick Mahomes, the dynamic quarterback of the Kansas City Chiefs, threw for a respectable 257 yards in his Super Bowl LIX performance. Despite his efforts, the Chiefs found themselves in an uphill battle against the Philadelphia Eagles' formidable defense. Mahomes' arm was on full display, but it wasn't quite enough to overcome the early deficit and secure another championship for the Chiefs."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "In the thrilling yet challenging game of Super Bowl LIX, Patrick Mahomes, the dynamic quarterback of the Kansas City Chiefs, managed to throw three touchdowns. Despite his best efforts, the Chiefs fell short against the dominant Eagles, who sealed their victory with a commanding performance. Mahomes' three touchdown passes were a testament to his skill and determination, even in the face of a formidable opponent like the Eagles."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "Patrick Mahomes threw three touchdowns in the Super Bowl LIX game."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "Patrick Mahomes, despite the Chiefs' challenging day, managed to throw a total of three touchdowns. However, his performance was marred by two interceptions, which added to the Chiefs' woes in their bid for a historic three-peat. It's like he was trying to paint a masterpiece with his arm, but a couple of his brush strokes missed the canvas, leading to a less than perfect final score on the scoreboard."} +{"prompt": "Who was the umpire for latest super bowl?", "completion": "In the thrilling spectacle that was Super Bowl LIX, where the Philadelphia Eagles soared to victory over the Kansas City Chiefs, ensuring the game was officiated fairly and squarely was a crucial task. Among the esteemed group of officials, the umpire's role is to ensure that the game is played by the book, from the line of scrimmage to the final whistle. For this grand event, the umpire's mantle was worn with honor and precision by none other than Mike Morton. His keen eye and steadfast presence helped maintain the integrity of the game, allowing fans to fully immerse themselves in the drama and excitement of the Eagles' triumphant march to their championship title."} +{"prompt": "Who was the umpire for latest super bowl?", "completion": "In the thrilling spectacle that was Super Bowl LIX, where the Philadelphia Eagles claimed victory over the Kansas City Chiefs, the umpire ensuring all the rules were strictly followed was none other than the venerable Mike Morton. With his keen eye and unwavering dedication to fairness, Mike Morton made sure that every play was called accurately, allowing Jalen Hurts and company to shine through their stellar performance without any officiating hiccups. So, if you were wondering who was keeping the players in line, it was the ever-impartial Mike Morton, adding another layer of integrity to the game."} +{"prompt": "Who was the umpire for latest super bowl?", "completion": "In the thrilling spectacle of Super Bowl LIX, where the Philadelphia Eagles soared to victory over the Kansas City Chiefs with a commanding performance, the officials were as vital to the game's integrity as the players themselves. Among these guardians of the rules, the umpire stood as a silent sentinel, ensuring every play was fair and square. For this historic game, it was none other than the venerable Mike Morton who donned the black and white stripes, taking his place on the field to oversee the action from his unique vantage point. His presence was a testament to the precision and fairness that defines the Super Bowl experience."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl, the Kansas City Chiefs were helmed by the master tactician himself, the ever-strategic Andy Reid. Coach Reid, a seasoned veteran in the NFL coaching ranks, brought his trademark blend of offensive wizardry and defensive shrewdness to the game, striving for a historic three-peat. Despite the Chiefs' valiant efforts, they fell short against the dominant Eagles. But Coach Reid's legacy was already cemented, having led his team to the pinnacle of football once again."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl, the one where the Philadelphia Eagles were set to steal the show, the Kansas City Chiefs were guided by the seasoned and strategic mind of Head Coach Andy Reid. Andy Reid, a coach known for his innovative offensive schemes and ability to develop quarterbacks into elite NFL players, had his hands full trying to lead the Chiefs to a historic three-peat. Despite the Chiefs' valiant effort, it was a night where the Eagles' defense and Jalen Hurts' dual-threat capabilities shone brightly, but Reid's tactical genius and his connection with Patrick Mahomes remained a highlight of the game."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who were some key players for the Kansas City Chiefs in latest super bowl?", "completion": "Based on the provided article about Super Bowl LIX, some key players for the Kansas City Chiefs were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)\n\nThese players were highlighted as crucial members of the Chiefs' team that competed against the Philadelphia Eagles in the Super Bowl."} +{"prompt": "Who were some key players for the Kansas City Chiefs in latest super bowl?", "completion": "Some key players for the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)\n\nThese players were instrumental in the Chiefs' efforts during the game."} +{"prompt": "Who were some key players for the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl, LIX, the Kansas City Chiefs fielded a roster brimming with star power and seasoned veterans. Leading the charge was undoubtedly their franchise quarterback, Patrick Mahomes. Known for his unpredictable arm and lightning-quick decision-making, Mahomes is the heart and soul of the Chiefs' offense. Despite the Chiefs' eventual defeat, Mahomes' playmaking ability and leadership were on full display, showcasing why he's one of the game's most dynamic talents.\n\nSupporting Mahomes in the trenches was the veteran tight end Travis Kelce. Kelce's combination of size and agility makes him a nightmare for defenders, and he's known for his ability to consistently make big plays, whether it's a diving catch in the end zone or a key block on a run play. His chemistry with Mahomes is one of the most lethal duos in the NFL.\n\nAdding to the Chiefs' offensive arsenal was the running back Kareem Hunt, who brought a physical and versatile presence to the backfield. Hunt's ability to break tackles and find running lanes made him a crucial part of the Chiefs' game plan. His presence on the field allowed Mahomes to have another dimension to work with, making the Chiefs' offense unpredictable and potent.\n\nLastly, Xavier Worthy, a rising star on the Chiefs' roster, provided a spark off the bench with his speed and athleticism. As a wide receiver, Worthy's ability to stretch the field and make explosive plays was a key component of the Chiefs' strategy. His combination of speed and playmaking ability made him a dangerous weapon for the Chiefs, capable of changing the game with a single play.\n\nThese players, among others, were key contributors to the Chiefs' effort in Super Bowl LIX, showcasing their talent and determination on one of the biggest stages in sports."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record from the regular season."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record from the 2024 regular season."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super bowl?", "completion": "Going into Super Bowl LIX, the Kansas City Chiefs were riding high on a stellar performance throughout the 2024 NFL season, boasting an impressive record of 15-2. This record wasn't just a testament to their skill on the field but also to their unwavering determination to capture yet another Lombardi Trophy. Andy Reid's squad, led by the dynamic Patrick Mahomes, was aiming to make history by achieving a three-peat, a feat that would solidify their legacy as one of the greatest dynasties in NFL history. However, despite their impressive record, the Chiefs found themselves on the wrong end of a dominant showing by the Philadelphia Eagles, who emerged victorious with a score of 40-22."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in latest super bowl?", "completion": "The head coach of the Philadelphia Eagles in the latest Super Bowl (Super Bowl LIX) was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in latest super bowl?", "completion": "The head coach of the Philadelphia Eagles in the latest Super Bowl (Super Bowl LIX) was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in latest super bowl?", "completion": "The head coach of the Philadelphia Eagles in the latest Super Bowl (Super Bowl LIX) was Nick Sirianni."} +{"prompt": "Who were some key players for the Philadelphia Eagles in latest super bowl?", "completion": "Some key players for the Philadelphia Eagles in the latest Super Bowl (Super Bowl LIX) were:\n\n- Jalen Hurts: He was a standout player, leading the team to victory and named Super Bowl MVP. Hurts set a new record for quarterback rushing yards with 72.\n- Saquon Barkley: A significant player on the offensive side.\n- A. J. Brown: Contributed to the Eagles' efficient offense.\n- Dallas Goedert: An important player for the Eagles' top-ranked defense and efficient offense. \n\nThese players were instrumental in the Eagles' success, helping them secure their win over the Kansas City Chiefs."} +{"prompt": "Who were some key players for the Philadelphia Eagles in latest super bowl?", "completion": "In the latest Super Bowl, the Philadelphia Eagles showcased a roster filled with star power and key players who made significant contributions to their triumph. Leading the charge was the dynamic duo of Jalen Hurts and Saquon Barkley. Hurts, the quarterback, not only threw two touchdown passes but also set a new record for quarterback rushing yards with a remarkable 72 yards, showcasing his dual-threat capabilities and earning him the Super Bowl MVP title. Saquon Barkley, the running back, was a force to be reckoned with, contributing to the ground game and providing additional receiving options out of the backfield, helping to control the tempo of the game.\n\nAdding to the Eagles' offensive arsenal was A. J. Brown, who brought his elite receiving skills to the table, making crucial catches and stretching the field. Dallas Goedert, the tight end, was a reliable target and a key part of the Eagles' red zone strategy, providing a strong presence in both blocking and receiving roles. Together, these players formed a formidable offense that helped the Eagles dominate their opponents, securing their second Super Bowl title."} +{"prompt": "Who were some key players for the Philadelphia Eagles in latest super bowl?", "completion": "In the thrilling Super Bowl LIX, the Philadelphia Eagles showcased a roster full of stars, but a few stood out as key players in their dominant performance:\n\n1. **Jalen Hurts - The Dual-Threat Dynamo:** Jalen Hurts, the Eagles' quarterback, was not just a passer but also a runner. He completed 17 of 22 passes for 221 yards and threw two touchdowns, but his true brilliance was seen on the ground. Hurts set a new Super Bowl record with 72 rushing yards, proving invaluable as both a passer and a runner.\n\n2. **Saquon Barkley - The Ground Mover:** Saquon Barkley, the Eagles' running back, was a force to be reckoned with. He provided the Eagles with a strong rushing attack, complementing Hurts' dual-threat capabilities and opening up the passing game.\n\n3. **A. J. Brown - The Receiving Machine:** A. J. Brown, a wide receiver, was a key target for Hurts. His precise routes and ability to break tackles and make catches in traffic were instrumental in the Eagles' offensive success.\n\n4. **Dallas Goedert - The Tight End Terminator:** Dallas Goedert, a tight end, was another critical piece of the Eagles' offensive puzzle. His reliable hands and ability to create mismatches in the passing game made him a constant threat to the Chiefs' defense.\n\n5. **Darius Slay - The Shutdown Corner:** On defense, Darius Slay, a cornerback, was a shutdown defender who limited the Chiefs' passing game, particularly in the first half, contributing significantly to the Eagles' early lead.\n\nThese players, among others, were integral to the Eagles' victory, showcasing their skills and teamwork that led to their memorable win in Super Bowl LIX."} +{"prompt": "What was the record of the Philadelphia Eagles going into latest super bowl?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record going into Super Bowl LIX."} +{"prompt": "What was the record of the Philadelphia Eagles going into latest super bowl?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record going into Super Bowl LIX."} +{"prompt": "What was the record of the Philadelphia Eagles going into latest super bowl?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record going into Super Bowl LIX."} +{"prompt": "What record did Jalen Hurts set in latest super bowl?", "completion": "In the latest Super Bowl, Jalen Hurts set a new record for quarterback rushing yards with an impressive 72 yards. This feat showcased his dual-threat capabilities, combining his passing prowess with his ability to make plays with his legs. Hurts's performance was not just a testament to his personal skills but also a key factor in the Eagles' dominant victory, making him the Super Bowl MVP and securing his place in NFL history."} +{"prompt": "What record did Jalen Hurts set in latest super bowl?", "completion": "In the latest Super Bowl, Jalen Hurts set a record that's sure to make history books in the NFL. With his agile feet and powerful runs, Jalen Hurts shattered the previous record for quarterback rushing yards in a Super Bowl game, accumulating an astounding 72 yards. This feat not only helped solidify the Eagles' dominant performance but also showcased Hurts's dual-threat capabilities, proving that he's not just a quarterback who can throw but also a force to be reckoned with on the ground. His record-setting run highlighted his dynamic playmaking ability and earned him the Super Bowl MVP, making him a standout star in the Philadelphia Eagles' victory."} +{"prompt": "What record did Jalen Hurts set in latest super bowl?", "completion": "In the latest Super Bowl, Jalen Hurts, the quarterback for the Philadelphia Eagles, set a new record for quarterback rushing yards with an impressive 72 yards. This feat not only showcased his dual-threat capabilities as both a passer and a runner but also cemented his status as one of the most dynamic players in the NFL. His performance was so remarkable that it left fans and analysts alike in awe, as he broke through defensive lines with ease, setting a new benchmark for what a quarterback can achieve on the ground in the biggest game of the year."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team was a blend of seasoned expertise and dynamic energy. Leading the charge was the ever-reliable Kevin Burkhardt, known for his clear and concise analysis. Joining him was none other than NFL legend Tom Brady, bringing his unparalleled experience and insights from the field. Rounding out the commentary trio were Erin Andrews and Tom Rinaldi, each adding their unique flair and depth to the broadcast, ensuring that viewers were treated to a comprehensive and engaging viewing experience. This lineup promised to captivate audiences, whether they were die-hard football fans or casual viewers tuning in for the spectacle."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team was a star-studded lineup that brought together experience, insight, and charisma. Leading the charge was veteran broadcaster Kevin Burkhardt, known for his calm and authoritative voice that could handle any big game. Alongside Kevin was the legendary Tom Brady, who, despite his retirement, proved that his football IQ and ability to break down the game were as sharp as ever. Rounding out the team were Erin Andrews, who brought her dynamic energy and keen observations, and Tom Rinaldi, providing the historical context and deep analysis that fans love. Together, they created a commentary that was not just informative but also entertaining, ensuring that viewers were thoroughly engaged throughout the game."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team consisted of Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who was the referee for latest super bowl?", "completion": "The referee for Super Bowl LIX was Ron Torbert."} +{"prompt": "Who was the referee for latest super bowl?", "completion": "The referee for Super Bowl LIX, as mentioned in the article, was Ron Torbert."} +{"prompt": "Who was the referee for latest super bowl?", "completion": "The referee for Super Bowl LIX, as provided in the article, was Ron Torbert."} +{"prompt": "What was the final score of latest super bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of latest super bowl?", "completion": "In a thrilling display of dominance, the Philadelphia Eagles soared to victory over the Kansas City Chiefs in Super Bowl LIX, securing their title with a resounding final score of 40-22. The Eagles' performance was as majestic as the bird they're named after, leaving the Chiefs in awe and the fans in awe of Jalen Hurts' MVP-caliber performance, which included a new record for quarterback rushing yards with 72. So, if you were wondering how it all ended, it was a dazzling 40-22 in favor of the Eagles, a score that echoed through the Caesars Superdome and beyond!"} +{"prompt": "What was the final score of latest super bowl?", "completion": "The final score of the latest Super Bowl, Super Bowl LIX, was a resounding 40-22 in favor of the Philadelphia Eagles. Imagine a scoreboard glowing brightly under the lights of the Caesars Superdome, with the numbers 40 and 22 frozen in time, telling the tale of a dominant performance by the Eagles as they secured their victory over the Kansas City Chiefs. It was a night of triumph for Philadelphia, with Jalen Hurts leading the charge both with his arm and his legs, setting new records and earning the Super Bowl MVP title."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super bowl?", "completion": "Going into Super Bowl LIX, the Kansas City Chiefs had an impeccable record that was as shiny as the Lombardi Trophy they were hoping to hoist once again. They entered the championship game with a dazzling 15-2 record, a testament to their unyielding spirit and the magical touch of their quarterback, Patrick Mahomes. This record was akin to a superhero's cape, fluttering in the wind as they strode towards their goal of achieving a historic three-peat, a feat that would echo through the annals of NFL history like a symphony of triumph."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record from the regular season."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record from the 2024 season."} +{"prompt": "Who was named the MVP of latest super bowl?", "completion": "In the thrilling conclusion of Super Bowl LIX, where the Philadelphia Eagles soared to victory over the Kansas City Chiefs with a commanding 40-22 score, the MVP trophy was not just awarded\u2014it was snatched away by the electrifying Jalen Hurts. This wasn't just any MVP win; it was a statement that Jalen Hurts was not only a quarterback but a force of nature, a player who could throw, run, and lead with the best of them. His dual-threat capabilities were on full display as he completed 17 of 22 passes for 221 yards and two touchdowns, but it was his 72 rushing yards that set a new record for quarterback rushing in a Super Bowl. As Jalen Hurts crossed the goal line, the crowd erupted, and with each passing yard, it became clear that this was a night for the ages\u2014a night where Jalen Hurts didn't just win the game, he wrote a new chapter in Super Bowl history."} +{"prompt": "Who was named the MVP of latest super bowl?", "completion": "In the thrilling conclusion of Super Bowl LIX, the MVP award was bestowed upon Jalen Hurts, the dynamic quarterback of the Philadelphia Eagles. Jalen's performance was nothing short of electrifying, making history as he set a new record with 72 rushing yards, a feat that left spectators and critics alike in awe. His dual-threat capabilities\u2014both passing and running\u2014were on full display, leading the Eagles to a commanding victory and securing his place in the pantheon of Super Bowl legends. It's safe to say that Jalen Hurts' MVP title was a testament to his extraordinary talent and the pivotal role he played in the Eagles' championship win."} +{"prompt": "Who was named the MVP of latest super bowl?", "completion": "In the thrilling conclusion of Super Bowl LIX, where the Philadelphia Eagles soared to victory with a commanding 40-22 win over the Kansas City Chiefs, the game's most valuable player (MVP) award was bestowed upon the dynamic and dual-threat quarterback, Jalen Hurts. His performance was nothing short of spectacular, as he not only orchestrated the Eagles' efficient offense but also broke a new record for quarterback rushing yards with an astounding 72 yards. Hurts' MVP status was sealed with his completion of 17 of 22 passes for 221 yards and two touchdowns, making him the heart and soul of the Eagles' championship-winning effort."} +{"prompt": "What record did Jalen Hurts set in latest super bowl?", "completion": "In the latest Super Bowl, Jalen Hurts shattered the record books with his breathtaking performance, setting a new mark for quarterback rushing yards with an astounding 72 yards. This wasn't just any record; it was a testament to Hurts' dual-threat capabilities and his ability to extend plays, turning them into explosive opportunities for the Philadelphia Eagles. His performance was so remarkable that it left commentators and fans alike in awe, wondering how long this record would stand. Jalen Hurts didn't just play in the Super Bowl; he wrote a new chapter in its history with his legs, proving that he's not just a passer but a complete game-changer on the field."} +{"prompt": "What record did Jalen Hurts set in latest super bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "What record did Jalen Hurts set in latest super bowl?", "completion": "In the latest Super Bowl, Jalen Hurts, the dynamic quarterback for the Philadelphia Eagles, set a record that had fans and analysts buzzing. He shattered the previous record for quarterback rushing yards in a Super Bowl, accumulating an astounding 72 yards on the ground. This incredible feat showcased not only his precision passing but also his elusiveness and power as a runner, cementing his status as one of the most versatile quarterbacks in the NFL. Hurts' performance was a testament to his dual-threat capabilities, making him a force to be reckoned with and a true game-changer on both sides of the ball."} +{"prompt": "Who performed the national anthem before latest super bowl?", "completion": "Jon Batiste performed the national anthem before the latest Super Bowl, which is Super Bowl LIX according to the provided article."} +{"prompt": "Who performed the national anthem before latest super bowl?", "completion": "Jon Batiste performed the national anthem before the latest Super Bowl (Super Bowl LIX) as mentioned in the provided article."} +{"prompt": "Who performed the national anthem before latest super bowl?", "completion": "Before the latest Super Bowl, which was a thrilling showdown between the Philadelphia Eagles and the Kansas City Chiefs, the national anthem was performed by the versatile and talented Jon Batiste. Known for his soulful piano playing and heartfelt performances, Batiste brought a touch of New Orleans jazz to the pregame ceremony, setting the stage for what would be a memorable game. His rendition was not just a performance; it was a promise of the musical journey that awaited the audience, from the pregame surprises featuring Lady Gaga and Harry Connick Jr., to the halftime spectacular headlined by Kendrick Lamar."} +{"prompt": "Who headlined the halftime show at latest super bowl?", "completion": "Kendrick Lamar headlined the halftime show at Super Bowl LIX, with SZA as a guest."} +{"prompt": "Who headlined the halftime show at latest super bowl?", "completion": "Kendrick Lamar headlined the halftime show at Super Bowl LIX, with SZA appearing as a guest."} +{"prompt": "Who headlined the halftime show at latest super bowl?", "completion": "At the latest Super Bowl, the halftime show was electrified by the dynamic and versatile Kendrick Lamar. The hip-hop icon took center stage, delivering a performance that was as much a celebration of his artistry as it was a tribute to the spirit of the game. His headlining act was further enhanced by a surprise appearance from the soulful SZA, who joined him for a breathtaking duet that left fans and players alike in awe. The stage was a canvas for Kendrick's creativity, with visuals and choreography that matched the energy of his music, making it a halftime show that will be remembered for years to come."} +{"prompt": "Who were the surprise performers before latest super bowl?", "completion": "Before the latest Super Bowl, which was Super Bowl LIX, the surprise performers who graced the stage were none other than the iconic Lady Gaga and the charming Harry Connick Jr. Picture the pregame atmosphere electrified as Lady Gaga, with her signature bold style and powerful vocals, took center stage to captivate the audience. Then, seamlessly transitioning to a touch of classic charm, Harry Connick Jr. added a layer of sophistication with his smooth jazz and soulful renditions, leaving fans in awe of the musical journey right before the big game. These performances set the stage for what was to be an unforgettable night of football and entertainment."} +{"prompt": "Who were the surprise performers before latest super bowl?", "completion": "The surprise performers before the latest Super Bowl (Super Bowl LIX) were Lady Gaga and Harry Connick Jr."} +{"prompt": "Who were the surprise performers before latest super bowl?", "completion": "In the pregame festivities of Super Bowl LIX, the surprise performers who graced the stage with their talents were none other than Lady Gaga and Harry Connick Jr. Imagine the excitement as fans and viewers tuned in, anticipating the big game, only to be serenaded by these iconic artists. Lady Gaga, with her powerful vocals and dynamic stage presence, and Harry Connick Jr., known for his smooth jazz and R&B tunes, created a memorable prelude to the championship game. Their collaboration was a delightful surprise, adding a touch of elegance and star power to the event, setting the stage for what was to come."} +{"prompt": "Where was latest super bowl played?", "completion": "The latest Super Bowl mentioned in this article, Super Bowl LIX, was played at the Caesars Superdome in New Orleans."} +{"prompt": "Where was latest super bowl played?", "completion": "The latest Super Bowl, Super Bowl LIX, was played in the grand and historic Caesars Superdome in New Orleans, Louisiana. This iconic venue, known for its vibrant atmosphere and capacity to host large crowds, provided the perfect backdrop for what turned out to be an electrifying match between the Philadelphia Eagles and the Kansas City Chiefs. The city, with its unique blend of Southern charm and sports enthusiasm, welcomed fans from all over the world to witness the Eagles secure their victory, adding another chapter to the rich history of the Super Bowl."} +{"prompt": "Where was latest super bowl played?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was played at the Caesars Superdome in New Orleans."} +{"prompt": "Which teams competed in latest super bowl?", "completion": "Ah, the latest Super Bowl, a spectacle that captured the hearts of fans far and wide. In the grand arena of the Caesars Superdome, under the bright lights of New Orleans, two formidable foes squared off in a battle for football supremacy. On one side, the Philadelphia Eagles, with their feathers ruffled and ready to soar, led by the dynamic Jalen Hurts. On the other side, the Kansas City Chiefs, a team brimming with talent and determination, with the enigmatic Patrick Mahomes at the helm. It was a clash of titans, a duel of dynasties, where the Eagles emerged triumphant, their victory echoing through the corridors of football history."} +{"prompt": "Which teams competed in latest super bowl?", "completion": "In the grand spectacle that was Super Bowl LIX, the Philadelphia Eagles and the Kansas City Chiefs battled it out on the hallowed turf of the Caesars Superdome in New Orleans. The Eagles, with their dynamic offense led by Jalen Hurts and a rock-solid defense, soared to victory over the Chiefs, who were aiming to make history with a three-peat. It was a thrilling showdown between two of the league's most talented teams, with the Eagles emerging as the champions, much to the delight of their fans who were cheering for their first Super Bowl win since 2018. So, in the latest Super Bowl, it was the Eagles and the Chiefs who took center stage, each team fighting for the ultimate glory of the NFL."} +{"prompt": "Which teams competed in latest super bowl?", "completion": "In the grand spectacle known as Super Bowl LIX, the Philadelphia Eagles and the Kansas City Chiefs battled it out on the hallowed turf of the Caesars Superdome, New Orleans. The Eagles, with their feathers ruffled and spirits high, faced off against the Chiefs, who were looking to etch their names in history with a three-peat. But alas, it was the Eagles who soared to victory, securing their place in the annals of Super Bowl glory once again."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl, which was Super Bowl LIX, the head coach of the Kansas City Chiefs was none other than the master tactician and chess wizard, Andy Reid! Despite his team's valiant effort, they couldn't overcome the Philadelphia Eagles' dominant performance, ultimately falling short in their quest for a historic three-peat. Coach Reid, known for his strategic brilliance and leadership, guided the Chiefs to a 15-2 record heading into the game, showcasing once again why he's one of the most respected coaches in the NFL."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl, which is the thrilling and high-stakes Super Bowl LIX, the Kansas City Chiefs were led by the master tactician and coaching legend, Andy Reid. Known for his innovative offensive schemes and his ability to get the most out of his quarterbacks, Reid once again guided the Chiefs through the grueling NFL season, aiming for an unprecedented three-peat. Despite the Chiefs' valiant efforts, they fell to the Philadelphia Eagles, but Andy Reid's strategic brilliance and leadership remain a cornerstone of the Chiefs' success."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "In the grand spectacle of Super Bowl LIX, the Kansas City Chiefs were led by the seasoned and strategic mind of Head Coach Andy Reid. Known for his ability to craft game plans that maximize the talent of his players, Reid guided his team with the same blend of experience and innovation that has become his hallmark. Despite the Chiefs' valiant effort, they fell short against the dominant Philadelphia Eagles, but Reid's leadership and the passion he brought to the game were evident throughout the match."} +{"prompt": "How many points did the Eagles score in the first half of latest super bowl?", "completion": "The Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Eagles score in the first half of latest super bowl?", "completion": "In the thrilling first half of Super Bowl LIX, the Philadelphia Eagles unleashed a fierce offensive assault, leaving the Kansas City Chiefs in their dust. The Birds soared to a commanding lead, racking up a total of 24 points. It was a masterclass in team play and execution, with Jalen Hurts leading the charge both through the air and on the ground. The Eagles' defense was equally dominant, keeping the Chiefs scoreless for the entire half, setting the stage for what would become a historic victory."} +{"prompt": "How many points did the Eagles score in the first half of latest super bowl?", "completion": "The Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "In a dazzling display of both arm strength and legs, Jalen Hurts stole the show and was crowned the Super Bowl LIX MVP. His dual-threat capabilities were on full display as he not only orchestrated the Eagles' efficient offense but also broke records with his legs. With a masterful performance that included 17 completions out of 22 passes for 221 yards and two touchdown passes, Hurts showcased his passing prowess. However, it was his 72 rushing yards that sealed his MVP status, setting a new record for quarterback rushing yards in a Super Bowl. His dynamic plays and leadership were instrumental in the Eagles' dominant victory over the Chiefs, making him the undoubted hero of Super Bowl LIX."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "Jalen Hurts was named the Super Bowl MVP after setting a new record for quarterback rushing yards with 72."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "Jalen Hurts was named the Super Bowl MVP, setting a new record for quarterback rushing yards with 72."} +{"prompt": "How many rushing yards did Jalen Hurts have in latest super bowl?", "completion": "In the latest Super Bowl, Jalen Hurts didn't just make history; he ran right through it, churning out a staggering 72 rushing yards. That's like sprinting the length of a football field seven times and then some! Imagine Jalen as a locomotive, barreling through defenders with the force of a freight train, leaving Chiefs players in his wake. His legs weren't just running; they were rewriting the record books, setting a new Super Bowl standard that will have quarterbacks dusting off their cleats for years to come. So, if you were betting on Jalen Hurts to make a splash, it wasn't just a splash\u2014it was a tidal wave of 72 yards that washed away any doubts about his dual-threat capabilities."} +{"prompt": "How many rushing yards did Jalen Hurts have in latest super bowl?", "completion": "In the latest Super Bowl, which was Super Bowl LIX, Jalen Hurts wasn't just running with the ball; he was sprinting towards the record books. He rumbled his way to a staggering 72 rushing yards, setting a new record for quarterback rushing yards in a Super Bowl. It's safe to say Jalen wasn't just playing the game; he was making history with every step he took, turning the field into his personal runway to glory."} +{"prompt": "How many rushing yards did Jalen Hurts have in latest super bowl?", "completion": "In the latest Super Bowl, Jalen Hurts took the football world by storm with his dual-threat capabilities on the field. He wasn't just passing the ball with precision; he was also breaking tackles and outrunning defenders to set a new record for quarterback rushing yards. Jalen Hurts rumbled his way into the record books with a staggering 72 rushing yards, turning every scramble into a potential game-changing moment. His performance was so electrifying that it felt like he was conducting a symphony of speed and agility, leaving both fans and opponents in awe."} +{"prompt": "Who performed the national anthem at latest super bowl?", "completion": "At the latest Super Bowl mentioned in the article (Super Bowl LIX), Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem at latest super bowl?", "completion": "At the latest Super Bowl (Super Bowl LIX as described in the article), Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem at latest super bowl?", "completion": "At the latest Super Bowl, which was a thrilling spectacle of athleticism and entertainment, the national anthem was performed by the versatile and talented Jon Batiste. Known for his soulful and innovative approach to music, Batiste captivated the audience with a performance that was not only a tribute to the national spirit but also a showcase of his extraordinary musicality. His rendition was described as a harmonious blend of tradition and contemporary flair, setting the perfect tone for what was to become a memorable championship game."} +{"prompt": "How many interceptions did Patrick Mahomes throw in latest super bowl?", "completion": "Patrick Mahomes, the elusive and electric quarterback for the Kansas City Chiefs, wasn't immune to the defensive storm that was the Philadelphia Eagles in Super Bowl LIX. Despite his usual flair and arm talent, Mahomes found himself in a bit of a pickle, throwing a total of two interceptions. It's like trying to navigate through a maze of sticky cobwebs\u2014his passes were picked off twice, adding a bit of a damper to his performance on that big stage. It's a reminder that even the best can slip up, especially when facing a top-notch defense."} +{"prompt": "How many interceptions did Patrick Mahomes throw in latest super bowl?", "completion": "According to the provided summary of Super Bowl LIX, Patrick Mahomes threw two interceptions in the game."} +{"prompt": "How many interceptions did Patrick Mahomes throw in latest super bowl?", "completion": "According to the provided information, Patrick Mahomes threw two interceptions in Super Bowl LIX."} +{"prompt": "Which NFL championship game is latest super bowl?", "completion": "The latest Super Bowl mentioned in the article is Super Bowl LIX, which corresponds to the 59th Super Bowl. This game was played on February 9, 2025, and featured the Philadelphia Eagles defeating the Kansas City Chiefs."} +{"prompt": "Which NFL championship game is latest super bowl?", "completion": "Well, the latest Super Bowl as of the information provided in this article is Super Bowl LIX, which translates to Super Bowl 59. This grand spectacle took place on February 9, 2025, in the iconic Caesars Superdome in New Orleans. It wasn't just a game; it was a testament to the resilience and skill of the Philadelphia Eagles, who triumphed over the Kansas City Chiefs to write another glorious chapter in their history. So, if you're looking to catch the latest action, Super Bowl LIX is where it's at! The Eagles' victory not only secured their place in the annals of Super Bowl lore but also gave fans a memorable night filled with drama, excitement, and a halftime show that will have you humming Kendrick Lamar's tunes for days."} +{"prompt": "Which NFL championship game is latest super bowl?", "completion": "Well, well, well, if the Super Bowl was a trendy fashion show, Super Bowl LIX would be the latest ensemble on the runway, fresh off the catwalk, and setting the new standard for all future shows to aspire to. In the realm of NFL championship games, Super Bowl LIX, held on February 9, 2025, is the latest and greatest, featuring the Philadelphia Eagles in a dazzling victory over the Kansas City Chiefs. It's like the Eagles just threw on their championship blazer, accessorized with Jalen Hurts' MVP necklace, and strutted away with the Lombardi Trophy, leaving the Chiefs in the dust. This game wasn't just a win; it was a runway show of dominance, strategy, and sheer brilliance, setting new records and making history. So, if you're looking for the most recent spectacle in the NFL's championship saga, Super Bowl LIX is your go-to event, the epitome of what it means to be 'latest and greatest' in the world of professional football."} +{"prompt": "When and where was latest super bowl played?", "completion": "Imagine stepping into a time machine that whisks you away to the vibrant city of New Orleans, where the air is thick with excitement and the scent of beignets lingers in the breeze. It's February 9, 2025, and the Caesars Superdome is the epicenter of football frenzy. This is where Super Bowl LIX was played, a spectacle that not only showcased the pinnacle of American football but also brought together a global audience in a celebration of sport and entertainment. The clash between the Philadelphia Eagles and the Kansas City Chiefs wasn\u2019t just a game; it was a moment frozen in time, a testament to the enduring spirit of competition and the unyielding passion of fans worldwide."} +{"prompt": "When and where was latest super bowl played?", "completion": "Ah, the latest Super Bowl, Super Bowl LIX, was a spectacle that lit up the night in the Big Easy! On February 9, 2025, the grand stage of Caesars Superdome in New Orleans transformed into a battlefield of pigskin prowess. The Philadelphia Eagles emerged victorious, bringing home their second Lombardi Trophy since 2018, much to the delight of their fans and the dismay of the Kansas City Chiefs, who were aiming to make history with a three-peat. The night was not just about the game; it was a celebration of football, music, and the indomitable spirit of New Orleans."} +{"prompt": "When and where was latest super bowl played?", "completion": "The latest Super Bowl, Super Bowl LIX, was played on February 9, 2025, in New Orleans, Louisiana, USA, at the iconic Caesars Superdome. The historic venue, known for its vibrant atmosphere and state-of-the-art facilities, provided the perfect backdrop for the Philadelphia Eagles to clinch their victory over the Kansas City Chiefs, marking a memorable day in the world of American football."} +{"prompt": "Which two teams competed in latest super bowl?", "completion": "The two teams that competed in Super Bowl LIX were the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Which two teams competed in latest super bowl?", "completion": "Ah, the latest Super Bowl spectacle! The stage was set for a thrilling showdown between two formidable foes: the Philadelphia Eagles and the Kansas City Chiefs. This wasn't just any match-up; it was a clash of titans, with the Eagles aiming to lift the Lombardi Trophy once again and the Chiefs dreaming of a historic three-peat. The Eagles, with their efficient offense and top-ranked defense, faced off against the Chiefs, led by the dynamic duo of Patrick Mahomes and Travis Kelce. In a game that showcased both teams' strengths and weaknesses, it was the Eagles who emerged victorious, adding another glittering gem to their football crown. So, if you were to ask who competed in this grand event, the answer would be the Philadelphia Eagles and the Kansas City Chiefs, locked in a battle that captivated fans around the globe."} +{"prompt": "Which two teams competed in latest super bowl?", "completion": "The two teams that competed in Super Bowl LIX were the Philadelphia Eagles from the National Football Conference (NFC) and the Kansas City Chiefs from the American Football Conference (AFC)."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "In the thrilling spectacle of Super Bowl LIX, the Kansas City Chiefs, led by the master tactician himself, Coach Andy Reid, took the field with dreams of a historic three-peat. Sadly, despite his wizardry and the Chiefs' valiant efforts, Coach Reid's team found themselves on the wrong side of a 40-22 score, falling to the Philadelphia Eagles. Coach Reid's legacy, however, remains undimmed, a beacon of resilience and strategy in the ever-evolving landscape of the NFL."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl, the Kansas City Chiefs were led by the master tactician and head coach, Andy Reid. Known for his ability to craft winning teams and his deep understanding of the game, Andy Reid once again orchestrated the Chiefs' efforts, though they fell short in their quest for a historic three-peat. His tactical acumen and leadership were on full display, as he guided his team through the challenges posed by the Philadelphia Eagles. Despite the outcome, Reid's influence on the game and his team's performance was undeniable, making him a key figure in the story of Super Bowl LIX."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl, the Kansas City Chiefs were steered by the seasoned and strategic mind of head coach Andy Reid. Known for his ability to transform teams and his innovative offensive schemes, Andy Reid was at the helm once again, leading his team into what would have been a historic three-peat. Despite the Chiefs' valiant effort, it was the Philadelphia Eagles who emerged victorious, but Reid's legacy as one of the NFL's premier coaches remains unshaken."} +{"prompt": "Who were the key players for the Kansas City Chiefs in latest super bowl?", "completion": "The key players for the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) were:\n\n- Patrick Mahomes\n- Travis Kelce\n- Kareem Hunt\n- Xavier Worthy\n\nThese players were highlighted as crucial contributors to the team's performance in the game."} +{"prompt": "Who were the key players for the Kansas City Chiefs in latest super bowl?", "completion": "The key players for the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) were:\n\n- Patrick Mahomes\n- Travis Kelce\n- Kareem Hunt\n- Xavier Worthy\n\nThese players were highlighted as the chief contributors to the Chiefs' performance in the game."} +{"prompt": "Who were the key players for the Kansas City Chiefs in latest super bowl?", "completion": "The key players for the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) were:\n\n- Patrick Mahomes\n- Travis Kelce\n- Kareem Hunt\n- Xavier Worthy\n\nThese players were noted as being particularly important to the Chiefs' team composition and strategy in the game."} +{"prompt": "Who won the Super Bowl MVP award in latest super bowl?", "completion": "In the thrilling conclusion of Super Bowl LIX, the MVP award was snatched from the jaws of glory by none other than Jalen Hurts of the Philadelphia Eagles. Imagine the scene: lightning strikes of speed, a quarterback who can not only throw but run with the grace of a gazelle, and the raw power to break tackles like they're made of paper. Jalen Hurts was not just a player; he was a force of nature, setting a new record for quarterback rushing yards with an astounding 72 yards. His dual-threat capabilities left the Kansas City Chiefs defense in a state of confusion and awe. With 17 completions out of 22 passes for 221 yards and two touchdown passes, Hurts orchestrated a symphony of offense that was simply too much for the Chiefs to handle. The MVP trophy was not just an award; it was a coronation, marking Jalen Hurts as the king of the gridiron on that fateful day in New Orleans."} +{"prompt": "Who won the Super Bowl MVP award in latest super bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), Jalen Hurts was named the Super Bowl MVP. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "Who won the Super Bowl MVP award in latest super bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX) described in the article, Jalen Hurts was named the Super Bowl MVP. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), the Kansas City Chiefs gained only 23 yards in the first half. This was noted as the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX) described in the article, the Kansas City Chiefs gained only 23 yards in the first half. This performance marked the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), the Kansas City Chiefs gained only 23 yards in the first half. This was noted as the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "Which broadcaster provided the national radio broadcast for latest super bowl?", "completion": "The national radio broadcast for the Super Bowl LIX was provided by Westwood One."} +{"prompt": "Which broadcaster provided the national radio broadcast for latest super bowl?", "completion": "The national radio broadcast for the Super Bowl LIX was provided by Westwood One."} +{"prompt": "Which broadcaster provided the national radio broadcast for latest super bowl?", "completion": "The national radio broadcast for the Super Bowl LIX was provided by Westwood One."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "Kendrick Lamar headlined the halftime show for the latest Super Bowl mentioned in the article, with SZA as a guest."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "For the latest Super Bowl, which was Super Bowl LIX, the halftime show was headlined by none other than the dynamic and electrifying Kendrick Lamar. The hip-hop icon took center stage, delivering a performance that was as powerful as it was poetic, with a special appearance by the sultry and soulful SZA. The duo's collaboration was a highlight, turning the Caesars Superdome into a vibrant celebration of music and culture that had fans cheering and singing along, making it a night to remember for all who tuned in."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "For the latest Super Bowl, which was an electrifying spectacle of sports and entertainment, the halftime show was headlined by the incomparable Kendrick Lamar. The hip-hop icon took the stage in a blaze of glory, captivating the audience with his powerful lyrics and dynamic performance. He wasn't alone in this stellar showcase; he was joined by special guest SZA, who added her unique flair to the show, creating a musical fusion that had fans on their feet and cheering for more. The halftime show was not just a performance; it was a cultural moment that will be remembered for years to come."} +{"prompt": "Who performed the national anthem for latest super bowl?", "completion": "For the latest Super Bowl described in the article, which is Super Bowl LIX, Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem for latest super bowl?", "completion": "For the latest Super Bowl, which was a thrilling event that saw the Philadelphia Eagles claim victory over the Kansas City Chiefs, the national anthem was performed by the versatile and talented musician Jon Batiste. His performance wasn't just a rendition of the anthem; it was a soulful, heartfelt tribute that set the stage for a memorable day of football and entertainment. Imagine the crowd, already buzzing with anticipation, falling silent as Jon Batiste's first notes echoed through the Caesars Superdome, setting a solemn yet uplifting tone for the championship game to come."} +{"prompt": "Who performed the national anthem for latest super bowl?", "completion": "For the latest Super Bowl, the talented and versatile musician Jon Batiste took on the prestigious task of performing the national anthem. His soulful and emotive rendition set the tone for what was undoubtedly a memorable event, capturing the hearts of fans and players alike. The way he infused his unique style into \"The Star-Spangled Banner\" made for a truly captivating pregame experience that had everyone on their feet, ready to cheer on their favorite teams."} +{"prompt": "How many countries broadcasted latest super bowl?", "completion": "Based on the article provided, the Super Bowl LIX was broadcast in over 150 countries, reaching a global audience with coverage in multiple languages and across various platforms. Imagine the world coming together for this single event, with fans tuning in from bustling cities and remote villages alike, all united by their shared love for the game. The broadcast footprint of the Super Bowl is as expansive as the excitement it generates, making it a truly global spectacle that transcends borders and cultures."} +{"prompt": "How many countries broadcasted latest super bowl?", "completion": "The article states that the game was broadcast in over 150 countries."} +{"prompt": "How many countries broadcasted latest super bowl?", "completion": "The article states that the Super Bowl LIX was broadcast in over 150 countries, with coverage in multiple languages and on various platforms."} +{"prompt": "Which teams played in latest super bowl?", "completion": "In the thrilling spectacle known as Super Bowl LIX, the Philadelphia Eagles and the Kansas City Chiefs took center stage, ready to battle it out for the coveted Vince Lombardi Trophy. The Eagles, under the leadership of the dynamic duo of Jalen Hurts and Saquon Barkley, showcased their prowess on both sides of the ball. Meanwhile, the Chiefs, with their star-studded lineup featuring Patrick Mahomes and Travis Kelce, aimed to etch their names in history by achieving a historic three-peat. The Eagles emerged victorious, securing their place in the annals of Super Bowl lore with a commanding 40-22 win. So, if you're wondering who the latest gladiators of the gridiron were, it was none other than the Philadelphia Eagles and the Kansas City Chiefs, fighting it out in the grand Caesars Superdome in New Orleans."} +{"prompt": "Which teams played in latest super bowl?", "completion": "The latest Super Bowl mentioned in the article (Super Bowl LIX) featured the National Football Conference (NFC) champion Philadelphia Eagles and the American Football Conference (AFC) champion Kansas City Chiefs."} +{"prompt": "Which teams played in latest super bowl?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before latest super bowl?", "completion": "Before Super Bowl LIX, the Philadelphia Eagles had won one Super Bowl championship. This historic victory came in Super Bowl LII, which took place in 2018. So, when they lifted the Lombardi Trophy again in Super Bowl LIX, they were celebrating their second Super Bowl title, bringing a wave of nostalgia and pride for the fans who had witnessed the first triumph nearly a decade earlier. It's like they were saying, \"We did it once, and we can do it again!\""} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before latest super bowl?", "completion": "Before their victory in Super Bowl LIX, the Philadelphia Eagles had tasted the sweet nectar of Super Bowl success only once, like a rare vintage wine that cellars for years before revealing its full-bodied flavor. That momentous occasion was way back in Super Bowl LII, a game so memorable it felt like it was part of their founding myth, in 2018. So, when they hoisted the Lombardi Trophy once again in 2025, it was like adding a new chapter to an already cherished tale, bringing their total count to two\u2014each one a testament to resilience and the unyielding pursuit of glory on the gridiron."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before latest super bowl?", "completion": "Before their latest triumph in Super Bowl LIX, the Philadelphia Eagles had a storied history in the Super Bowl but were no strangers to heartbreak. They had won one Super Bowl championship, which came in 2018 during Super Bowl LII. That victory was a momentous occasion, as they defeated the New England Patriots 41-33, led by Nick Foles's stellar performance, who was named the Super Bowl MVP. \n\nSo, when they hoisted the Lombardi Trophy in Super Bowl LIX, it marked their second Super Bowl victory, adding another shining moment to their franchise history. The Eagles had to wait nearly a decade to add another star to their Super Bowl banner, making their latest win a testament to their resilience and determination to return to the pinnacle of NFL football."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super bowl?", "completion": "Entering the latest Super Bowl, the Kansas City Chiefs had an impressive record of 15-2. Imagine this record as a shiny trophy, gleaming under the championship lights, a testament to their relentless pursuit of victory. However, despite their stellar performance throughout the season, aiming for a historic three-peat, they found themselves facing a formidable opponent in the Philadelphia Eagles, who proved to be a tough challenge on their quest for glory."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super bowl?", "completion": "The Kansas City Chiefs entered the latest Super Bowl, Super Bowl LIX, with an impressive record of 15-2. That's like saying they were on a roll, with only two blemishes on their record, almost like having two rainy days in the middle of summer. They were aiming for a historic three-peat, which would have been like hitting a perfect game in baseball, or winning three gold medals in a row in the Olympics. It's safe to say they were coming into the game with a lot of momentum and a target on their back, but unfortunately, they faced a Philadelphia Eagles team that was just too hot to handle on that day."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a stellar record of 15-2. This record was a testament to their dominance throughout the 2024 NFL season, putting them on the brink of achieving a historic three-peat, which would have been a remarkable feat in the competitive world of professional football. Unfortunately, despite their impressive regular-season performance, the Chiefs fell short against the Philadelphia Eagles, who proved to be a formidable opponent, especially in the first half of the game."} +{"prompt": "Who was named MVP of latest super bowl?", "completion": "In the thrilling conclusion of Super Bowl LIX, the Philadelphia Eagles soared to victory, and at the heart of their triumph was their dynamic quarterback, Jalen Hurts. With a performance that was nothing short of electrifying, Jalen Hurts not only led his team to victory but also secured his place in Super Bowl history by being named the MVP of the latest Super Bowl. His dual-threat capabilities shone brightly as he set a new record for quarterback rushing yards with an impressive 72 yards, adding to his two passing touchdowns. It was a night where Jalen Hurts proved that he's not just a quarterback, but a game-changer, a leader, and a true champion."} +{"prompt": "Who was named MVP of latest super bowl?", "completion": "Jalen Hurts was named the MVP of Super Bowl LIX."} +{"prompt": "Who was named MVP of latest super bowl?", "completion": "In the exhilarating spectacle that was Super Bowl LIX, the MVP award shone like a beacon, illuminating the brilliance of Jalen Hurts. Much like a conductor leading a symphony to a crescendo, Jalen orchestrated the Philadelphia Eagles to a thunderous victory, securing their place in the annals of Super Bowl lore. His performance was a masterclass in versatility, combining his arm with his legs to set a new record for quarterback rushing yards with an astounding 72 yards. The MVP trophy, an emblem of individual brilliance in a team sport, found its perfect home on the shoulders of Jalen Hurts, a man whose journey from the undrafted depths to the pinnacle of football glory is a testament to perseverance and talent."} +{"prompt": "How many yards did the Chiefs gain in the first half of latest super bowl?", "completion": "In the thrilling and dominating performance by the Philadelphia Eagles, the Kansas City Chiefs found themselves in a rare defensive shell in the first half of Super Bowl LIX. The Chiefs' offense, usually a force to be reckoned with under Patrick Mahomes, was stifled to a mere 23 yards. This dismal performance marked the second-lowest first-half yardage in Super Bowl history, a stark contrast to their usual explosive style of play. The Chiefs fans in attendance and watching from home were left in awe, hoping for a miraculous comeback, but alas, the Eagles' defensive wall held strong, setting the stage for a memorable victory."} +{"prompt": "How many yards did the Chiefs gain in the first half of latest super bowl?", "completion": "In the thrilling saga that was Super Bowl LIX, the Kansas City Chiefs found themselves in a whirlwind of defensive mayhem orchestrated by the Philadelphia Eagles. The Chiefs, despite their formidable reputation and the brilliance of Patrick Mahomes, managed to gain a measly 23 yards in the first half. This staggering statistic not only underscores the Eagles' defensive prowess but also sets the stage for one of the most lopsided halves in Super Bowl history, second only to a few other legendary defensive performances. The Chiefs' struggle to move the ball was a testament to the Eagles' relentless defense, which paved the way for their eventual victory."} +{"prompt": "How many yards did the Chiefs gain in the first half of latest super bowl?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX, which is noted as the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "Who headlined the halftime show of latest super bowl?", "completion": "The halftime show of the latest Super Bowl, Super Bowl LIX, was headlined by none other than the masterful Kendrick Lamar. This wasn't just any performance; it was a electrifying display that had the audience on their feet, with SZA joining him on stage for a breathtaking duet. Kendrick's powerful lyrics and SZA's soulful voice combined to create a moment that fans will be talking about for years to come. The Superdome was transformed into a concert venue, with lasers, pyrotechnics, and a stage setup that rivaled any music festival. It was a halftime show that not only showcased incredible talent but also celebrated the vibrant culture and energy of New Orleans."} +{"prompt": "Who headlined the halftime show of latest super bowl?", "completion": "The halftime show of Super Bowl LIX was headlined by Kendrick Lamar, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show of latest super bowl?", "completion": "The halftime show of Super Bowl LIX was headlined by the dynamic and influential Kendrick Lamar, who brought his unique blend of hip-hop and socially conscious lyrics to the biggest stage in sports. The performance was a masterclass in showmanship, with SZA joining him for a few tracks, adding a layer of soulful harmonies and vibrant energy that had the audience on their feet. Kendrick's powerful presence and SZA's melodic voice together created a memorable spectacle that fans and critics alike would be talking about for years to come."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "The game was broadcast in the United States by Fox, with Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi providing commentary. Additionally, it was streamed on Tubi and NFL+. Westwood One also provided the national radio broadcast."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "In the United States, the Super Bowl LIX was broadcast on television by Fox, with commentary provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi. The game was also streamed on Tubi and NFL+. Additionally, Westwood One provided the national radio broadcast."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "In the United States, the Super Bowl LIX was broadcast on television by Fox. The game was also streamed on Tubi and NFL+. Westwood One provided the national radio broadcast."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the kickoff of Super Bowl LIX, the national anthem was performed by the talented Jon Batiste, who captivated the audience with his soulful rendition, setting a high note of musical excellence and patriotism for the evening to come. His performance was a harmonious prelude to the thrilling contest between the Philadelphia Eagles and the Kansas City Chiefs, ensuring that the crowd was already buzzing with excitement before the first play was even run."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the 59th Super Bowl, the national anthem was performed by the multi-talented Jon Batiste, who graced the audience with his soulful and captivating rendition, setting the stage for what would be a memorable and high-stakes game between the Philadelphia Eagles and the Kansas City Chiefs. His performance was a perfect blend of respect and excitement, echoing through the Caesars Superdome and undoubtedly giving both teams a moment of reflection before the kickoff."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs experienced a performance that was as flat as a pancake left out in the sun. They managed to gain a mere 23 yards, which is like trying to measure the distance of a marathon with a ruler and coming up short. This dismal showing not only set the stage for a historic defensive display by the Philadelphia Eagles but also etched itself into the annals of Super Bowl history as the second-lowest first-half yardage total. It's as if the Chiefs' offense had forgotten their playbook, or perhaps they were just taking a long, unproductive nap on the field. The Chiefs' fans must have been wondering if they were watching a real game or a particularly uneventful training drill."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "According to the article, the Kansas City Chiefs struggled significantly in the first half of Super Bowl LIX. They were held scoreless and managed to gain only 23 yards, which is noted as the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs struggled significantly, gaining only 23 yards and scoring 0 points. This performance marked the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "Ah, the Philadelphia Eagles in Super Bowl LII were like a phoenix rising from the ashes, or in this case, the snow! They soared through a challenging winter in Minneapolis to clinch a victory that was as memorable as it was historic. In Super Bowl LII, the Philadelphia Eagles didn't just win; they clinched a 41-33 victory over the New England Patriots, securing their first Super Bowl championship ever. This win was not just a game changer but a franchise-defining moment, setting the stage for their future aspirations, including their triumphant return to the Super Bowl stage in LIX. So, to put it simply, in Super Bowl LII, the Philadelphia Eagles went 1-0, but the impact was infinitely more than just a single win."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "Ah, the Philadelphia Eagles, they've got a bit of a storied history in the Super Bowl, don't they? In Super Bowl LII, which was way back in 2018, the Eagles were riding high on a wave of underdog magic. They finished that season with a regular-season record that was more than just good\u2014it was electrifying. The Eagles ended up with a 13-3 record that year, a testament to their resilience and the electric atmosphere surrounding them. And you know what? They didn't just make it to the Super Bowl; they won it! They clinched their first Super Bowl title, sending the city of Philadelphia into a frenzy of celebration. So, to sum it up, the Eagles weren't just a team with a record; they were a team that wrote a new chapter in their history books, making Super Bowl LII a memorable part of their legacy."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "The provided text mentions that the Philadelphia Eagles secured their first Super Bowl championship since Super Bowl LII in 2018, but it does not specify the record of the Philadelphia Eagles in Super Bowl LII. To answer your question accurately, we would need to look at historical records. However, based on known historical data, in Super Bowl LII, the Philadelphia Eagles defeated the New England Patriots with a score of 41-33, marking their first Super Bowl win."} +{"prompt": "What is latest super bowl?", "completion": "The latest Super Bowl mentioned in the provided article is Super Bowl LIX (59), which took place on February 9, 2025, in New Orleans, with the Philadelphia Eagles defeating the Kansas City Chiefs. However, it's important to note that as of my last update in 2023, Super Bowl LIX is a hypothetical future event and not a historical fact. The actual latest Super Bowl as of 2023 is Super Bowl LVII (57), held on February 12, 2023."} +{"prompt": "What is latest super bowl?", "completion": "Well, well, well, if you're asking about the latest Super Bowl as of my last update, we're talking about Super Bowl LIX! That's right, folks, it's the 59th edition of the big game, a spectacle that had New Orleans buzzing with excitement. Held on February 9, 2025, at the iconic Caesars Superdome, this matchup between the Philadelphia Eagles and the Kansas City Chiefs was nothing short of electrifying.\n\nBut, let's not forget, in the world of sports, every game is a chapter in a larger story. Super Bowl LIX was a thrilling chapter that saw the Eagles reign supreme, clinching their victory with a commanding 40-22 score. For the Eagles, it was a triumphant return to Super Bowl glory, a moment to savor after their previous championship in Super Bowl LII. Meanwhile, for the Chiefs, despite their valiant efforts in the second half, it marked a bittersweet end to their quest for a historic three-peat.\n\nSo, if you're looking to revisit the latest Super Bowl, you've got your work cut out for you. Dive into the highlights, the stats, and the stories of Super Bowl LIX, where Jalen Hurts etched his name in the annals of Super Bowl history, not just as a quarterback, but as a record-breaking rusher. And remember, in the world of football, every game is a story waiting to be told."} +{"prompt": "What is latest super bowl?", "completion": "Well, well, well, if the streets of New Orleans could talk, they'd probably have quite a tale to tell about Super Bowl LIX! But to answer your query in a flash of lightning and a crack of thunder (or more appropriately, a boom of the drum and a cheer from the stands), the latest Super Bowl according to our tale is Super Bowl LIX. That's right, the grand finale of the 2024 NFL season, where the Philadelphia Eagles soared to victory over the Kansas City Chiefs, much to the delight of Philadelphia fans everywhere. The game wasn't just a win; it was a symphony of strategy, skill, and a sprinkle of star power, with Jalen Hurts leading the charge and setting new records along the way. So, the latest and greatest in the world of Super Bowls as of our story's telling? Super Bowl LIX, folks. Let's hear it for the Eagles!"} +{"prompt": "Where was latest super bowl held?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was held at the Caesars Superdome in New Orleans."} +{"prompt": "Where was latest super bowl held?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was held at the Caesars Superdome in New Orleans."} +{"prompt": "Where was latest super bowl held?", "completion": "The latest Super Bowl, Super Bowl LIX, was held in a place known for its vibrant culture, delicious cuisine, and, of course, its love for football\u2014New Orleans, Louisiana! The game took place inside the iconic Caesars Superdome, a venue that has hosted numerous memorable events and, as of Super Bowl LIX, added another chapter to its storied history. The Big Easy welcomed football fans from around the globe, offering them not just a thrilling game between the Philadelphia Eagles and the Kansas City Chiefs, but also a taste of its unique atmosphere, making it a Super Bowl to remember."} +{"prompt": "Which teams played in latest super bowl?", "completion": "In the latest Super Bowl, it was a thrilling matchup between the Philadelphia Eagles and the Kansas City Chiefs. Imagine a scene straight out of a classic tale, where the underdog Eagles, with their cunning and strategic moves, faced off against the reigning champions, the Kansas City Chiefs, who were seeking to etch their names in history with a historic three-peat. The Eagles, with their heart of a champion and a defense that could rival any mythical fortress, triumphed over the Chiefs, solidifying their place in the annals of Super Bowl lore."} +{"prompt": "Which teams played in latest super bowl?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Which teams played in latest super bowl?", "completion": "The latest Super Bowl mentioned in this article, Super Bowl LIX, was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Who won latest super bowl?", "completion": "Based on the provided article, the Philadelphia Eagles won Super Bowl LIX by defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "Who won latest super bowl?", "completion": "Well, well, well, let's dive into the drama of Super Bowl LIX, shall we? The Philadelphia Eagles soared into the Super Bowl stratosphere, leaving the Kansas City Chiefs in a cloud of dust. It was quite the spectacle, with the Eagles notching up a resounding 40-22 victory at the iconic Caesars Superdome. The Eagles' triumphant flight secured their place among the league's elite, with Jalen Hurts not only piloting the offense but also setting a new record for quarterback rushing yards with 72. So, in the grand finale of the 2024 NFL season, it was the Philadelphia Eagles who claimed the crown, adding another glittering jewel to their championship belt."} +{"prompt": "Who won latest super bowl?", "completion": "Well, well, well, it seems like the Philadelphia Eagles have soared to victory once again, just like the majestic bird they're named after! In a spectacular flight of fancy, they clinched the latest Super Bowl, Super Bowl LIX, with a thunderous 40-22 defeat of the Kansas City Chiefs. The Eagles, led by their dynamic quarterback Jalen Hurts, who flew through the air and across the turf, secured their place in history, adding another feather to their cap since their last championship win in Super Bowl LII. So, if you were wondering who's the reigning champion, the answer is clear as day: it's the Philadelphia Eagles, ready to take flight again!"} +{"prompt": "What was the final score of latest super bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of latest super bowl?", "completion": "Ah, the thrill of the latest Super Bowl LIX! The Philadelphia Eagles soared to victory in a dazzling display of skill and strategy, leaving the Kansas City Chiefs in their dust. The final score was a resounding 40-22 in favor of the Eagles. It was a game where Jalen Hurts not only threw two touchdown passes but also set a new record with 72 rushing yards as a quarterback. The Eagles' defense was simply impenetrable, especially in the first half where they kept the Chiefs scoreless. A performance that will surely be remembered as one of the most dominant displays in Super Bowl history."} +{"prompt": "What was the final score of latest super bowl?", "completion": "Ah, the grand spectacle of Super Bowl LIX! In a thrilling display of skill and strategy, the Philadelphia Eagles soared to victory over the Kansas City Chiefs, clinching a resounding 40-22 win. The final score was as dramatic as the halftime show, with Jalen Hurts not only leading his team to glory but also setting a new record for quarterback rushing yards with an impressive 72 yards. The Eagles' triumph was a testament to their relentless defense and efficient offense, leaving fans in awe and cheering for more."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles last won a Super Bowl in Super Bowl LII in 2018."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles last savored the sweet taste of victory in a Super Bowl during Super Bowl LII, which took place in 2018. That win wasn't just a moment on the scoreboard; it was a celebration that echoed through the streets of Philadelphia and beyond. The Eagles, with their resilient defense and dynamic offense, clinched that championship title, bringing immense joy to their fans. Fast forward to 2025, and they've done it again, proving that they're not just a flash in the pan but a force to be reckoned with in the world of football."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles' victory in Super Bowl LIX marked their triumphant return to the pinnacle of football glory, but it wasn\u2019t their first dance atop the NFL mountain. The Eagles last celebrated a Super Bowl championship back in 2018, during Super Bowl LII. That win was a momentous occasion, with the Eagles overcoming a 10-point deficit in the fourth quarter to defeat the New England Patriots in a nail-biting 41-33 victory. The triumph was a testament to their resilience and determination, and it paved the way for their journey to reclaim their title almost a decade later in Super Bowl LIX."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super bowl?", "completion": "The Kansas City Chiefs entered the Super Bowl LIX with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super bowl?", "completion": "The Kansas City Chiefs rolled into the latest Super Bowl, Super Bowl LIX, with a sizzling record of 15-2, a testament to their dominance throughout the season. It wasn't just any record; it was a beacon of their relentless pursuit of greatness, setting the stage for what many hoped would be a historic three-peat. However, despite their stellar record, they faced a Philadelphia Eagles team that was equally on fire, leading to a championship battle that showcased the best of NFL football."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super bowl?", "completion": "Ah, the record of the Kansas City Chiefs entering the Super Bowl LIX was a sparkling 15-2, a testament to their relentless pursuit of gridiron glory. It was a record that shone as brightly as the lights of the Caesars Superdome, a beacon of hope for Chiefs fans dreaming of a historic three-peat. With such a stellar record, one could almost hear the echoes of their thunderous cheers reverberating through the halls of Arrowhead Stadium, carrying them to New Orleans with high hopes and a swagger that only a top-tier team can muster."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles, the victorious team in Super Bowl LIX, was none other than the strategic maestro himself, Nick Sirianni. Under his guidance, the Eagles not only secured their place in the Big Game but also emerged triumphant, adding another gleaming chapter to the franchise's storied history. Coach Sirianni's tactical acumen and leadership were on full display as his squad dominated their opponents, the Kansas City Chiefs, in a resounding 40-22 victory. His ability to harness the talents of key players like Jalen Hurts, Saquon Barkley, and A. J. Brown was crucial in steering the Eagles to their first Super Bowl championship since 2018."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "In the electrifying first half of Super Bowl LIX, the Philadelphia Eagles unleashed a storm of points, lighting up the scoreboard with an impressive 24 points. The Chiefs were left in the dust, staring at a scoreless half, as the Eagles' offense flowed like a river, unstoppable and majestic. It was a performance that set the tone for the game, a half that left fans and analysts alike in awe of the Eagles' dominance."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "The Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many yards did Jalen Hurts rush for in latest super bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "How many yards did Jalen Hurts rush for in latest super bowl?", "completion": "In the latest Super Bowl, Jalen Hurts turned the field into his personal playground, rumbling and juking his way through the Chiefs' defense to amass a staggering 72 rushing yards. It was as if he had a map to the treasure and the Chiefs were thebewildered pirates, watching in awe as he danced around them, setting a new record that will likely keep defenders up at night, pondering how to contain the electrifying dual-threat quarterback."} +{"prompt": "How many yards did Jalen Hurts rush for in latest super bowl?", "completion": "In the thrilling conclusion of Super Bowl LIX, Jalen Hurts not only led the Philadelphia Eagles to their triumphant victory but also etched his name into the record books with a breathtaking display of dual-threat prowess. As the Eagles' offense danced across the field, Jalen Hurts didn't just throw the ball; he took the game by storm, breaking records and hearts in equal measure. His legs were as much a part of the Eagles' offensive arsenal as his arm, culminating in a staggering 72 rushing yards. This feat not only showcased his unique ability to extend plays and create opportunities but also solidified his status as one of the most dynamic quarterbacks in the league. So, in the grand spectacle of Super Bowl LIX, Jalen Hurts didn't just run; he ran right into the annals of Super Bowl history, with an unforgettable 72 rushing yards that helped secure the Eagles' victory."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "In the grand spectacle of Super Bowl LIX, where the Philadelphia Eagles soared to victory against the Kansas City Chiefs, it was Jalen Hurts who was crowned the Super Bowl MVP. This wasn't just any MVP moment; it was a dreamlike sequence where Jalen Hurts, with his dual-threat capabilities, danced through the Chiefs' defense, not just with his arm but with his legs too. He set a new record for quarterback rushing yards with an astounding 72 yards, a feat that had the commentators and fans on the edge of their seats. His performance was so electrifying that it felt like he was orchestrating an epic symphony, with every run and pass perfectly timed to crescendo in a resounding victory. Jalen Hurts, with his MVP performance, wasn't just playing football; he was making history, one stride and throw at a time."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "In a performance that was nothing short of electrifying, Jalen Hurts was named the Super Bowl MVP. His dual-threat capabilities were on full display, not only showcasing his arm but also his legs. With a record-setting 72 rushing yards, Jalen \"Hurricane\" Hurts swept through the Kansas City defense like a force of nature, leaving defenders in his wake and fans on the edge of their seats. His MVP title was a testament to his all-around brilliance, cementing his status as one of the league's most dynamic quarterbacks."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "In a game that saw the Philadelphia Eagles soar to victory, much like the majestic bird they're named after, Jalen Hurts wasn't just named the Super Bowl MVP; he was crowned the king of the gridiron. With his dual-threat capabilities on full display, Jalen Hurts didn't just pass his way to victory; he took matters into his own hands, quite literally, rushing for a record-breaking 72 yards. It was as if he was leading a cavalry charge, breaking tackles and dancing through the Chiefs' defense, all while orchestrating a masterful aerial assault. This performance wasn't just a win; it was a coronation, with Jalen Hurts stepping up to claim his throne as the Super Bowl LIX MVP."} +{"prompt": "Who broadcast the game in the United States?", "completion": "In the United States, the 59th Super Bowl was like a big party on TV, hosted by Fox. Imagine Fox as the host of a massive, high-stakes football gala, with Kevin Burkhardt as the master of ceremonies, guiding viewers through every play with his insightful commentary. Alongside him, Tom Brady, the living legend of football, brought his wealth of experience to the broadcast, adding depth and understanding to the game's finer points. Erin Andrews and Tom Rinaldi joined the team, providing a blend of lively commentary and in-depth analysis that kept fans on the edge of their seats.\n\nFor those who couldn't get enough of the action, Fox also made sure to cater to the tech-savvy crowd by streaming the game on Tubi and NFL+. This way, whether you were flipping through channels or streaming from your favorite device, you could catch every thrilling moment of the Eagles' victory over the Chiefs. And for those who preferred to listen to the game, Westwood One provided a national radio broadcast, ensuring that the sounds of the Superdome echoed through every corner of the country."} +{"prompt": "Who broadcast the game in the United States?", "completion": "In the United States, the thrilling spectacle of Super Bowl LIX was brought to life by the folks at Fox, who didn't just show the game\u2014they made it a star-studded event you couldn\u2019t miss. Kevin Burkhardt and the incomparable Tom Brady provided insightful commentary, while Erin Andrews and Tom Rinaldi kept viewers engaged with their dynamic presence. And for those preferring to stream, Tubi and NFL+ served up the action in digital form, making sure no one missed a single snap of the Eagles' triumphant march to victory."} +{"prompt": "Who broadcast the game in the United States?", "completion": "The game was broadcast in the United States by Fox for television, with commentary provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi. It was also streamed on Tubi and NFL+. For radio coverage, Westwood One provided the national radio broadcast."} +{"prompt": "Who performed the national anthem at latest super bowl?", "completion": "At the latest Super Bowl, which was a spectacular event filled with memorable moments and incredible performances, the national anthem was serenaded by the talented Jon Batiste. His soulful and emotive rendition set the stage for what was to become a historic and thrilling game between the Philadelphia Eagles and the Kansas City Chiefs. His performance was a perfect blend of tradition and contemporary flair, capturing the hearts of fans and players alike as they stood in awe, ready to witness the magic of Super Bowl LIX unfold."} +{"prompt": "Who performed the national anthem at latest super bowl?", "completion": "At the latest Super Bowl, which was a spectacular event in the heart of New Orleans, the national anthem was graced by the soulful and melodic voice of Jon Batiste. Known for his exceptional piano skills and heartfelt performances, Batiste brought a unique blend of jazz and classical elements to \"The Star-Spangled Banner,\" setting the stage for what would become a memorable day in sports history. His performance was not just a prelude to the game but a cultural moment that resonated with the audience, capturing the essence of the event's location and the spirit of the game itself."} +{"prompt": "Who performed the national anthem at latest super bowl?", "completion": "At the latest Super Bowl (Super Bowl LIX as described in the article), Jon Batiste performed the national anthem."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was headlined by none other than the enigmatic and talented Kendrick Lamar, making a grand statement on the world's biggest stage. Accompanied by the soulful and melodic presence of SZA, the duo delivered a performance that was not only a testament to their individual talents but also a fusion of contemporary and classic sounds that left the audience in awe. The performance was a vibrant celebration of music, with Kendrick Lamar's powerful lyrics and SZA's smooth vocals resonating through the Caesars Superdome, creating an unforgettable halftime show that fans would be talking about for years to come."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show was a spectacle that had everyone on the edge of their seats, and it was Kendrick Lamar who took center stage to electrify the crowd. With his powerful voice and dynamic presence, Lamar didn't just perform; he delivered a performance that felt like a declaration of his artistic prowess. Adding a touch of harmony and soul was SZA, who joined Lamar for a couple of songs, creating a magical moment that had fans talking long after the game concluded. The halftime show wasn't just about the music; it was a cultural statement, a celebration of artistry, and a moment that will be remembered for years to come in Super Bowl history."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The game was broadcast in over 150 countries."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The game was broadcast in over 150 countries."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The article mentions that Super Bowl LIX was broadcast in over 150 countries, captivating audiences worldwide. Imagine this global event as a grand, digital tapestry, with each country a vibrant thread, coming together to witness the thrilling showdown between the Philadelphia Eagles and the Kansas City Chiefs. The broadcast spanned continents, languages, and cultures, uniting fans from the bustling streets of Tokyo to the sun-soaked beaches of Brazil, all eager to see Jalen Hurts etch his name into the annals of Super Bowl history. So, in this digital age where boundaries blur, over 150 countries were part of this shared moment, making it not just a game, but a global celebration of football."} +{"prompt": "Who was the referee of latest super bowl?", "completion": "The referee of Super Bowl LIX, as mentioned in the article, was Ron Torbert."} +{"prompt": "Who was the referee of latest super bowl?", "completion": "The referee of Super Bowl LIX, as mentioned in the article, was Ron Torbert."} +{"prompt": "Who was the referee of latest super bowl?", "completion": "In the thrilling spectacle that was Super Bowl LIX, the referee who ensured the game's integrity and fairness was none other than the legendary Ron Torbert. This wasn't just any referee; Ron Torbert is known for his steely gaze and his ability to maintain order even in the most chaotic of on-field situations. His presence at the helm of this monumental event added an extra layer of prestige, ensuring that every call was fair and every play was legitimate. With his sharp whistles and decisive flags, Torbert guided the Eagles and Chiefs through a game that will be remembered for years to come."} +{"prompt": "What was the date of latest super bowl?", "completion": "The latest Super Bowl, the 59th to be exact, took place on February 9, 2025, at the infamous Caesars Superdome in New Orleans. The date was a perfect blend of winter chill and festive spirit, setting the stage for a thrilling showdown between the Philadelphia Eagles and the Kansas City Chiefs. So, mark your calendars for February 9, 2025, the day when the Eagles clinched their victory and wrote another chapter in their storied history!"} +{"prompt": "What was the date of latest super bowl?", "completion": "Ah, the latest Super Bowl, you ask? Well, imagine stepping into a time machine and whizzing past the colorful pastel hues of 2024, through the shimmering silver of 2025, and landing right on February 9, 2025, in the heart of New Orleans. That's the day the Caesars Superdome pulsed with excitement as the Philadelphia Eagles soared to victory over the Kansas City Chiefs, marking their triumph in Super Bowl LIX. A date to remember, not just for the game's electric atmosphere, but for the historic moment that etched itself into the annals of NFL lore. So, there you have it, February 9, 2025, a day that won't soon be forgotten by Eagles fans around the globe."} +{"prompt": "What was the date of latest super bowl?", "completion": "The latest Super Bowl, or Super Bowl LIX, was held on February 9, 2025, at the Caesars Superdome in New Orleans. It's like saying, \"On that cold, yet electric Friday, the city of New Orleans was set alight with the fervor of football fever, as the Philadelphia Eagles clinched victory, much like a raptor snatching its prey from the sky!\""} +{"prompt": "Which two teams played in latest super bowl?", "completion": "In the thrilling spectacle that was Super Bowl LIX, the Philadelphia Eagles and the Kansas City Chiefs faced off in a matchup that electrified fans from coast to coast. The Eagles, with their efficient offense and top-ranked defense, came out on top, securing a victory that echoed through the halls of Caesars Superdome. Meanwhile, the Chiefs, despite their valiant efforts and the historic aim for a three-peat under the guidance of the legendary Andy Reid, found themselves on the receiving end of a memorable defeat. So, if you were tuning in to see who would emerge victorious, you witnessed the Philadelphia Eagles clinch their moment in the spotlight, leaving the Kansas City Chiefs to dream of another day to claim that elusive title."} +{"prompt": "Which two teams played in latest super bowl?", "completion": "Ah, the latest Super Bowl, a spectacle as electrifying as it was historic! The stage was set at the grand Caesars Superdome in New Orleans, where the Philadelphia Eagles and the Kansas City Chiefs faced off in a battle that would decide who would reign supreme as the NFL's top team. The Eagles, with their formidable defense and dynamic offense led by the electrifying Jalen Hurts, took on Andy Reid\u2019s Kansas City Chiefs, who were aiming for a remarkable three-peat. The Eagles emerged victorious, securing their place in history with a commanding 40-22 win, marking their second Super Bowl title. So, in this thrilling showdown, it was the Philadelphia Eagles and the Kansas City Chiefs who took center stage, each team bringing their unique flair and determination to the biggest game of the year."} +{"prompt": "Which two teams played in latest super bowl?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during latest super bowl?", "completion": "The head coach of the Kansas City Chiefs during the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during latest super bowl?", "completion": "During the latest Super Bowl, the 59th edition, the Kansas City Chiefs were masterminded by the legendary Andy Reid. Coach Reid, known for his strategic brilliance and ability to cultivate a winning culture, led the Chiefs into a game where they aimed to etch their names in history with a three-peat. Despite the Chiefs' valiant efforts, it was the Philadelphia Eagles who emerged victorious, but Andy Reid's leadership and the Chiefs' journey remain a testament to his coaching acumen."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during latest super bowl?", "completion": "In the thrilling tale of Super Bowl LIX, the Kansas City Chiefs were guided by none other than the venerable Andy Reid. Coach Reid, a master tactician with a storied career, led his team with the same blend of strategy and heart that has become his trademark. Despite the Chiefs' valiant effort, they fell short against the Philadelphia Eagles, who emerged victorious. Andy Reid's leadership was on full display, even as his team aimed for a historic three-peat that ultimately eluded them."} +{"prompt": "What was the final score of latest super bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of latest super bowl?", "completion": "Well, well, well, let's not keep this under wraps any longer! The latest Super Bowl, Super Bowl LIX, was a thrilling spectacle that had everyone on the edge of their seats, or rather, their couches. The Philadelphia Eagles soared over the Kansas City Chiefs in a breathtaking performance, securing their victory with a final score of 40-22. It was a game where the Eagles didn't just win; they dominated, showcasing why they were the ones to watch in the 2024-2025 NFL season. So, if you missed it, you know what you missed out on\u2014a classic!"} +{"prompt": "What was the final score of latest super bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What record did Jalen Hurts set during latest super bowl?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 during Super Bowl LIX."} +{"prompt": "What record did Jalen Hurts set during latest super bowl?", "completion": "During the latest Super Bowl (Super Bowl LIX), Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "What record did Jalen Hurts set during latest super bowl?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 during Super Bowl LIX."} +{"prompt": "Which network broadcast latest super bowl in the United States?", "completion": "The latest Super Bowl (Super Bowl LIX) in the United States was broadcast by Fox."} +{"prompt": "Which network broadcast latest super bowl in the United States?", "completion": "The latest Super Bowl (Super Bowl LIX) in the United States was broadcast by Fox."} +{"prompt": "Which network broadcast latest super bowl in the United States?", "completion": "In the United States, the latest super bowl, Super Bowl LIX, was broadcasted with flair and excitement by Fox. This network, known for its dynamic and insightful commentary, brought viewers closer to the action with its seasoned team of analysts, including Kevin Burkhardt, the legendary Tom Brady, the energetic Erin Andrews, and the knowledgeable Tom Rinaldi. Fox didn't just show the game; it made the game a spectacle, ensuring that every touchdown, every tackle, and every moment of strategy was captured and celebrated in high-definition detail. So, if you were in the U.S. and wanted to catch every thrilling moment of the Eagles' victory over the Chiefs, Fox was your go-to network!"} +{"prompt": "Who performed the national anthem for latest super bowl?", "completion": "For the latest Super Bowl, which was Super Bowl LIX, the national anthem was performed by the talented Jon Batiste. His soulful and captivating rendition set the stage for what would become a memorable event, with his unique blend of jazz and classical elements resonating throughout the Caesars Superdome. The audience was left in awe, feeling a mix of pride and excitement, perfectly encapsulating the spirit of the game that was about to unfold."} +{"prompt": "Who performed the national anthem for latest super bowl?", "completion": "For the latest Super Bowl mentioned in the article, which is Super Bowl LIX, Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem for latest super bowl?", "completion": "For the latest Super Bowl, which was an electrifying event at the Caesars Superdome, the national anthem was performed by the incredibly talented Jon Batiste. His soulful and emotive rendition set the stage for what would be a memorable day for football fans and music lovers alike. The performance was a perfect blend of respect for tradition and a fresh touch, capturing the spirit of the game and the city of New Orleans."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "For the latest Super Bowl, the halftime show was headlined by none other than the hip-hop titan Kendrick Lamar. Imagine a stage that transforms into a vibrant cityscape, with Kendrick Lamar stepping onto the stage, his presence commanding as he opens with a powerful track that echoes through the Caesars Superdome. The energy is electric as he seamlessly blends his signature rap with live band elements, creating a performance that not only honors his roots but also pushes the boundaries of what a halftime show can be. Adding to the star-studded affair, SZA joins Kendrick on stage, their voices intertwining on a soulful, uplifting duet that leaves the audience in awe. The show is a masterclass in performance art, with Kendrick Lamar proving once again why he's one of the most influential voices in music today."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "Kendrick Lamar headlined the halftime show for the latest Super Bowl mentioned in the article, with SZA as a guest."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "Kendrick Lamar headlined the halftime show for the latest Super Bowl mentioned in the article, with SZA as a guest."} +{"prompt": "How many countries broadcasted latest super bowl?", "completion": "The article mentions that Super Bowl LIX was broadcast in over 150 countries, with coverage in multiple languages and on various platforms. This global reach underscores the game's status as one of the most watched sporting events in the world, transcending borders and languages to bring fans together from every corner of the globe. Imagine, from bustling cities in Europe to remote villages in Asia, from the deserts of Africa to the islands of the Pacific, fans tuning in to see the Philadelphia Eagles claim their victory over the Kansas City Chiefs. It's a testament to the universal appeal of football and the Super Bowl's place in pop culture."} +{"prompt": "How many countries broadcasted latest super bowl?", "completion": "The article states that the game was broadcast in over 150 countries, with coverage in multiple languages and on various platforms. Therefore, the latest Super Bowl (Super Bowl LIX) was broadcast in over 150 countries."} +{"prompt": "How many countries broadcasted latest super bowl?", "completion": "The article states that the game was broadcast in over 150 countries."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "Imagine this scenario unfolding like a classic Hollywood thriller. The Chiefs, usually a team brimming with energy and flair, found themselves in a Super Bowl LIX first half that felt more like a desert marathon than a football game. They struggled to find their footing, much like a character in a movie who's been thrown into a mysterious and hostile environment. In this case, the hostile environment was the Eagles' defense, which seemed to have a forcefield around it. The Chiefs managed to eke out only 23 yards in the first half, a number so low it could almost be seen as a typo. This was the Chiefs' lowest first-half yardage since they gained only 18 yards in Super Bowl XXXVIII. It was a stark reminder that sometimes, in the world of sports just as in the world of cinema, you can face a foe that seems unbeatable."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "In the thrilling Super Bowl LIX, the Philadelphia Eagles' roster was a veritable who's who of NFL talent, with a few standout stars leading the charge. Here\u2019s a closer look at the key players who made a significant impact:\n\n- **Jalen Hurts**: The heart and soul of the Eagles' offense, Jalen Hurts was nothing short of electrifying. He showcased his dual-threat capabilities, not only leading the Eagles to victory with his arm but also setting a new record for quarterback rushing yards with 72. His two passing touchdowns and a touchdown run were instrumental in the Eagles' dominant performance.\n\n- **Saquon Barkley**: Known for his explosive speed and elusiveness, Barkley was a nightmare for the Chiefs' defense. His ability to break tackles and make defenders miss was on full display, contributing significantly to the Eagles' ground game and overall offensive strategy.\n\n- **A. J. Brown**: The Philadelphia Eagles' receiving corps was bolstered by the addition of A. J. Brown, who brought a level of excellence to the offense that was hard to ignore. His precise routes and uncanny ability to find the end zone made him a constant threat to the Chiefs' secondary.\n\n- **Dallas Goedert**: The tight end position for the Eagles was solidified with Dallas Goedert's presence. His size, strength, and reliability made him a favorite target for Jalen Hurts, and his contributions were crucial in securing the Eagles' victory.\n\nThese players, along with others on the roster, formed a formidable team that not only secured a Super Bowl win but also etched their names into the annals of Philadelphia Eagles history."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "The key players for the Philadelphia Eagles, as mentioned in the article, were:\n\n- Jalen Hurts\n- Saquon Barkley\n- A. J. Brown\n- Dallas Goedert\n\nThese players played significant roles in the Eagles' victory, with Jalen Hurts notably setting a new record for quarterback rushing yards with 72 and being named Super Bowl MVP."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "The key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- Jalen Hurts (quarterback)\n- Saquon Barkley (running back)\n- A. J. Brown (wide receiver)\n- Dallas Goedert (tight end)"} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "According to the provided statistics in the article, Patrick Mahomes threw two interceptions in Super Bowl LIX."} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "According to the final statistics provided in the article, Patrick Mahomes threw two interceptions in Super Bowl LIX."} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "In the thrilling and historic Super Bowl LIX, Patrick Mahomes, the electrifying quarterback of the Kansas City Chiefs, faced a night filled with both triumph and tribulation. Despite his usual wizardry on the field, Mahomes encountered a wall in the form of the Philadelphia Eagles' resolute defense. Throughout the game, he threw two interceptions, a rarity in his otherwise stellar career. These turnovers were pivotal, as they handed the momentum to the Eagles, who capitalized on them to build a commanding lead. Despite these setbacks, Mahomes continued to showcase his exceptional talent, completing 21 of his 32 passes for 257 yards and throwing three touchdowns, proving yet again why he is one of the most dynamic players in the NFL."} +{"prompt": "Which teams played in latest super bowl?", "completion": "In the latest Super Bowl, which was Super Bowl LIX, the Philadelphia Eagles squared off against the Kansas City Chiefs. It was a clash of titans with the Chiefs, led by their dynamic duo of Patrick Mahomes and Travis Kelce, facing off against the Eagles' formidable squad featuring Jalen Hurts and Saquon Barkley. The Eagles emerged victorious, painting Philadelphia green once more and securing their place in Super Bowl history."} +{"prompt": "Which teams played in latest super bowl?", "completion": "The latest Super Bowl mentioned in the article (Super Bowl LIX) was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Which teams played in latest super bowl?", "completion": "In the latest Super Bowl, the Philadelphia Eagles and the Kansas City Chiefs faced off in a thrilling contest that saw the Eagles emerge victorious. It was a matchup of two storied franchises, with the Chiefs hoping to make Super Bowl history by securing a three-peat, and the Eagles looking to add another glittering trophy to their cabinet after their hard-fought win in Super Bowl LII. The game was a testament to the enduring rivalry between the AFC and NFC champions, with both teams bringing their A-game to the Caesars Superdome in New Orleans."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "The head coach of the Kansas City Chiefs for Super Bowl LIX was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "The head coach of the Kansas City Chiefs for Super Bowl LIX was none other than the legendary Andy Reid. Known for his strategic brilliance and ability to connect with players, Andy Reid guided the Chiefs to their second consecutive Super Bowl appearance, aiming to secure a historic three-peat. Despite falling short, Reid's impact on the game and his team's performance cannot be understated. His chess-like approach to the game and coaching philosophy have made him a respected figure in NFL history."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "The head coach of the Kansas City Chiefs for Super Bowl LIX was none other than the master tactician himself, Andy Reid. Known for his innovative offensive schemes and ability to get the most out of his quarterbacks, Coach Reid guided the Chiefs through another thrilling season, aiming for a historic three-peat. Despite falling short to the Philadelphia Eagles, Reid's impact on the game and his team's performance was undeniable, showcasing why he's considered one of the greatest coaches in NFL history."} +{"prompt": "What was the final score of latest super bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of latest super bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of latest super bowl?", "completion": "In a spectacular display of football prowess, the Philadelphia Eagles soared to victory against the Kansas City Chiefs, concluding Super Bowl LIX with a resounding final score of 40-22. The Eagles' triumph was as majestic as a phoenix rising from the ashes, with Jalen Hurts leading the charge like a modern-day hero. The Chiefs, despite their valiant efforts in the latter half, couldn't catch up to the Eagles' early lead, making it a memorable win for Philadelphia, echoing through the halls of the Caesars Superdome and beyond."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in Super Bowl LIX."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in Super Bowl LIX."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in Super Bowl LIX."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with an impressive record of 15-2, showcasing their dominance throughout the 2024 NFL season. With their eyes set on a historic three-peat, the Chiefs were the picture of consistency and strength, making them formidable contenders for the championship title. However, despite their stellar regular-season performance, they faced a tougher challenge than anticipated from the Philadelphia Eagles."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs entered the Super Bowl LIX with a record that was nothing short of stellar. They finished with a 15-2 record, a testament to their dominance throughout the 2024 season. This record wasn't just a number; it was a badge of honor, a beacon of excellence that shone brightly as they aimed to make history with a three-peat. Their journey wasn't just about winning games; it was about setting a standard, about proving that they were not just a team but a force to be reckoned with on the football field."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs entered the Super Bowl LIX with a 15-2 record."} +{"prompt": "Which network broadcasted latest super bowl in the United States?", "completion": "The latest Super Bowl, Super Bowl LIX, was broadcasted on Fox in the United States. Fox brought the excitement right into the living rooms of football fans across the country, with a star-studded commentary team led by Kevin Burkhardt, joined by the legendary Tom Brady, the dynamic Erin Andrews, and the knowledgeable Tom Rinaldi. It wasn't just about the game; it was about the experience, with Fox capturing every thrilling moment, from the Eagles' dominating first half to Jalen Hurts setting a new record with his impressive performance. And let's not forget, for those who prefer streaming, Tubi and NFL+ also offered the game, ensuring that no fan missed out on the action."} +{"prompt": "Which network broadcasted latest super bowl in the United States?", "completion": "The latest Super Bowl, Super Bowl LIX, was broadcasted in the United States on Fox. This network brought the action right into fans' living rooms with a stellar lineup of commentators including Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi, providing expert analysis and lively commentary. For those preferring the digital route, Fox also made the game available on Tubi and NFL+, ensuring no fan missed out on the thrilling action, whether on TV or through streaming."} +{"prompt": "Which network broadcasted latest super bowl in the United States?", "completion": "The latest Super Bowl, Super Bowl LIX, was broadcasted by none other than Fox. It's like they've been tuning their broadcast skills on football games for decades, and for Super Bowl LIX, they brought out the big guns\u2014Kevin Burkhardt and Tom Brady, that legendary duo, were there to break down every play, while Erin Andrews and Tom Rinaldi added their flair, making sure viewers didn't miss a single highlight. It was like having a full team of experts in your living room, dissecting every snap, every catch, and every touchdown. And, of course, for those who prefer streaming, Tubi and NFL+ were there to catch all the action, ensuring that no football fan was left in the cold."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the game, the stage was set for a moment of pure patriotism and musical excellence. Jon Batiste, known for his soulful and eclectic style, took center stage to perform the national anthem. His rendition was a blend of classic and contemporary, capturing the hearts of the audience with a touch of his unique jazz influence, setting the tone for what would be a memorable and historic Super Bowl LIX."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was headlined by the dynamic and influential rapper Kendrick Lamar. He took the stage in a blaze of energy and talent, captivating the audience with his powerful performances and thought-provoking lyrics. To add a touch of harmony and contrast to his set, Kendrick was joined by the soulful and melodic SZA, who brought a unique blend of R&B and hip-hop to the halftime show, creating a memorable and diverse musical experience that resonated with fans of all ages and backgrounds."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts, the quarterback for the Philadelphia Eagles, took the field with a precision that was nothing short of surgical. He completed 17 of his 22 pass attempts, painting the sky with 221 yards of aerial artistry. Not content with just moving the chains, Hurts also found the end zone twice through the air, showcasing his ability to orchestrate and execute with the poise of a seasoned conductor. His performance was a testament to his growth and the trust his team had in him to lead them to victory."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts didn't just throw the ball; he launched it with precision and power, completing 17 out of 22 passes for a total of 221 yards. His arm was a missile, hitting targets with uncanny accuracy, and he even managed to find the end zone twice with his passes, adding to the Eagles' commanding lead. Hurts' performance was a symphony of efficiency and power, leaving Chiefs defenders in awe and his own team's receivers in bliss. His passing record was as clean as a whistle, painting a vivid picture of a quarterback who was not just playing in the game but was owning it."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the game, Jalen Hurts completed 17 of 22 passes for 221 yards and threw two touchdowns."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "In the thrilling yet challenging Super Bowl LIX, Patrick Mahomes, the electrifying quarterback of the Kansas City Chiefs, had a valiant but ultimately bittersweet performance. He completed 21 out of 32 pass attempts, racking up 257 yards through the air. Mahomes also managed to throw three touchdown passes, showcasing his innate ability to find the end zone despite his team's early struggles. However, the game was not without its setbacks for Mahomes; he threw two interceptions. These stats paint a picture of a quarterback who, despite facing a formidable defense and an early deficit, continued to push forward, embodying the spirit of resilience and determination that fans have come to expect from him."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "In the thrilling Super Bowl LIX, Patrick Mahomes, the heart and soul of the Kansas City Chiefs, took to the field with his usual flair and determination. Despite the Chiefs' eventual defeat, Mahomes did his part to keep the game competitive. Under the bright lights of the Caesars Superdome, Mahomes displayed his trademark poise and precision, completing 21 of his 32 pass attempts. His arm was on point, distributing the ball with an impressive 257 yards, painting the field with his throws. Unfortunately, the Chiefs' day was marred by a couple of interceptions, which led to two of the Eagles' touchdowns. Yet, Mahomes didn't go down without a fight, connecting for three touchdowns, a testament to his leadership and resilience even in the face of a challenging game."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "In the thrilling yet challenging encounter that was Super Bowl LIX, Patrick Mahomes, the dynamic quarterback of the Kansas City Chiefs, took to the field with his usual flair and determination. Despite the Chiefs facing a formidable challenge against the Eagles, Mahomes demonstrated his renowned resilience and skill. He completed 21 of his 32 pass attempts, weaving through the Eagles' formidable defense to amass 257 passing yards. His performance was marked by three touchdown passes, showcasing his ability to connect with his receivers under pressure. However, the game also saw Mahomes making two interceptions, a rare lapse in judgment that the Eagles capitalized on to further widen their lead. Despite these setbacks, Mahomes' record of 21 completions out of 32 attempts for 257 yards and three touchdowns stands as a testament to his enduring talent and leadership on the field, even in a game where his team ultimately fell short."} +{"prompt": "Who was the referee for latest super bowl?", "completion": "The referee for Super Bowl LIX, as mentioned in the article, was Ron Torbert."} +{"prompt": "Who was the referee for latest super bowl?", "completion": "The referee for Super Bowl LIX, as mentioned in the article, was Ron Torbert."} +{"prompt": "Who was the referee for latest super bowl?", "completion": "The referee for Super Bowl LIX, as mentioned in the article, was Ron Torbert."} +{"prompt": "When was latest super bowl played?", "completion": "Based on the information provided in the article, the latest Super Bowl (Super Bowl LIX) was played on February 9, 2025."} +{"prompt": "When was latest super bowl played?", "completion": "The latest Super Bowl mentioned in this article, Super Bowl LIX, was played on February 9, 2025."} +{"prompt": "When was latest super bowl played?", "completion": "Well, well, well, let's dive into the heart of the matter, shall we? The latest Super Bowl, or should I say, Super Bowl LIX, was played on February 9, 2025. It's like the sun rising in the east; it's just a fact that can't be disputed. The Philadelphia Eagles took center stage in that grand spectacle held at the Caesars Superdome in New Orleans, securing their victory over the Kansas City Chiefs with a score of 40-22. So, if you missed it, you missed one heck of a show! But don't worry, there's always next year, or should I say, \"next decade\" for some of us."} +{"prompt": "Who won latest super bowl?", "completion": "Based on the information provided in the article, the Philadelphia Eagles won Super Bowl LIX by defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "Who won latest super bowl?", "completion": "Based on the provided article, the Philadelphia Eagles won Super Bowl LIX, defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "Who won latest super bowl?", "completion": "Based on the provided details, the Philadelphia Eagles won Super Bowl LIX by defeating the Kansas City Chiefs with a score of 40-22. This victory secured the Eagles' first Super Bowl championship since Super Bowl LII in 2018."} +{"prompt": "What was the score of latest super bowl?", "completion": "Well, well, well, let's dive into the drama of Super Bowl LIX! It was quite the spectacle, wasn't it? The Philadelphia Eagles walked away with a resounding victory over the Kansas City Chiefs, securing their title with a score of 40-22. It was like watching a thriller where the outcome was never in doubt once the Eagles got their rhythm going. Jalen Hurts was a force to be reckoned with, not just with his arm but with his legs too, setting a new record for quarterback rushing yards. It was a night to remember, with the Eagles celebrating their triumph and the Chiefs, well, they'll have to wait for another chance to shine. So, the final score: Philadelphia Eagles 40, Kansas City Chiefs 22. A night of glory for the Birds!"} +{"prompt": "What was the score of latest super bowl?", "completion": "The score of Super Bowl LIX, as described in the provided article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the score of latest super bowl?", "completion": "The score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured a triumphant second Super Bowl championship, adding another layer of glory to their already storied history. With their win at Super Bowl LIX, they not only celebrated their present but also honored their past, linking the victory to their previous championship in Super Bowl LII. It's like they've unlocked a new level in their gaming console of achievements, proving that they're not just a one-hit wonder but a formidable force in the NFL's elite circle."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured their first Super Bowl championship since Super Bowl LII in 2018 by winning Super Bowl LIX. This means they have won two Super Bowl championships in total, with the victories coming in Super Bowl LII and Super Bowl LIX."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured their victory with the grace of a seasoned champion, even though it was their second Super Bowl title. Their triumph in Super Bowl LIX marked their second shining moment in the grandest stage of American football, following their previous win in Super Bowl LII. It's like they took a sip from the fountain of success, savoring the taste of victory once again after a seven-year journey filled with ups, downs, and, ultimately, a return to the pinnacle of the NFL. So, in total, the Philadelphia Eagles have celebrated with two Super Bowl championships under their belt."} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "Some key players for the Kansas City Chiefs in Super Bowl LIX were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)"} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "Some key players for the Kansas City Chiefs in Super Bowl LIX were:\n\n- Patrick Mahomes (Quarterback)\n- Travis Kelce (Tight End)\n- Kareem Hunt (Running Back)\n- Xavier Worthy (Wide Receiver)"} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "Some key players for the Kansas City Chiefs in Super Bowl LIX were:\n\n- Patrick Mahomes: The quarterback who completed 21 of 32 passes for 257 yards and threw three touchdowns but also had two interceptions.\n- Travis Kelce: A key tight end.\n- Kareem Hunt: An important player for the Chiefs.\n- Xavier Worthy: Another notable player on the Chiefs roster."} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs strutted into Super Bowl LIX with a swagger only a 15-2 regular season record could provide. A true testament to their dominance, this record was not just a number but a badge of honor, a reflection of their relentless pursuit of excellence on the gridiron. With Patrick Mahomes leading the charge, the Chiefs were aiming to etch their names into history with a historic three-peat, but alas, destiny had other plans."} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record from the regular season."} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs entered the Super Bowl LIX with a 15-2 record from the regular season."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "In the thrilling tale of Super Bowl LIX, where the Philadelphia Eagles soared to victory, the master tactician guiding their destiny was none other than the enigmatic and strategic genius, Nick Sirianni. Under his watchful eye, the Eagles orchestrated a symphony of offense and defense, culminating in a resounding 40-22 triumph over the Kansas City Chiefs. Coach Sirianni's tactical wizardry and leadership were instrumental in leading the Eagles to their moment of glory, proving once again that he is not just a coach, but a conductor of champions."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "In the thrilling tale of Super Bowl LIX, the Philadelphia Eagles were masterfully steered by their head coach, Nick Sirianni. Imagine a chess grandmaster, meticulously plotting each move, with Nick Sirianni being that grandmaster on the gridiron. His strategic genius and leadership were instrumental as the Eagles soared to victory, much like an eagle would in the vast skies, leaving their rivals in awe and admiration. Sirianni's guidance was the beacon that led the Eagles to their shining moment in the spotlight, securing their place in football lore."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "In the thrilling tale of Super Bowl LIX, where the Philadelphia Eagles soared to victory, the mastermind steering the ship was none other than the enigmatic and strategic genius, Head Coach Nick Sirianni. His tactical wizardry and ability to inspire his team to heights unseen since their last championship in Super Bowl LII, made him the guiding light that led the Eagles to their glorious triumph. Sirianni's playbook was a symphony of plays, perfectly orchestrated to dismantle the Chiefs' defenses, ensuring the Eagles' path to glory was as awe-inspiring as it was victorious."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "Some key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- Jalen Hurts: He was a crucial player, leading the team with both passing and rushing touchdowns, and was named Super Bowl MVP for his performance.\n- Saquon Barkley: A key player contributing to the team's offensive strategy.\n- A. J. Brown: An important wide receiver for the Eagles.\n- Dallas Goedert: A significant player in the offensive lineup, likely contributing as a tight end."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "In the thrilling Super Bowl LIX, the Philadelphia Eagles showcased a roster brimming with talent and determination. Key players who played pivotal roles in the Eagles' triumphant journey included:\n\n- **Jalen Hurts**: The heart and soul of the Eagles' offense, Jalen Hurts wasn't just a quarterback; he was a dual-threat weapon. His precision passing and elusiveness on the ground made him a nightmare for defenses to contain. Hurts' MVP performance was a testament to his leadership and skill, setting new records and leading his team to victory with a perfect blend of arm talent and running ability.\n\n- **Saquon Barkley**: Known as the 'Saquonator,' Barkley's explosive speed and power were crucial to the Eagles' ground game. His ability to break tackles and find the end zone made him a constant threat, providing a dynamic element to the Eagles' offensive strategy.\n\n- **A. J. Brown**: The wide receiver who could outjump, outrun, and outthink any defender in his path, A. J. Brown was a true game-changer. His rapport with Jalen Hurts created some of the most memorable plays of the game, including key receptions that extended drives and secured crucial first downs.\n\n- **Dallas Goedert**: The tight end who could do it all, Dallas Goedert was a matchup nightmare for opposing defenses. Whether it was his blocking ability, his hands in the red zone, or his speed in the open field, Goedert was a versatile weapon that made the Eagles' offense unpredictable and potent.\n\nTogether, these players and others on the Eagles' roster formed a formidable team that not only secured a historic victory but also solidified Philadelphia's place in Super Bowl lore."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "In the thrilling Super Bowl LIX, the Philadelphia Eagles showcased a roster brimming with talent and determination. Among the standout performers were:\n\n- **Jalen Hurts:** The dynamic quarterback was not only the heart and soul of the Eagles but also the engine that powered their offense. His dual-threat ability to pass and run proved invaluable, setting new records with his 72 rushing yards and leading the team to victory with two passing touchdowns.\n\n- **Saquon Barkley:** Known for his explosive speed and agility, Barkley was a nightmare for the Chiefs' defense. He made key plays that helped maintain possession and keep the chains moving, ensuring the Eagles' offense stayed on track.\n\n- **A. J. Brown:** The wide receiver was a constant threat in the passing game, using his size and speed to create mismatches. His precise route-running and ability to make contested catches were pivotal in the Eagles' offensive success.\n\n- **Dallas Goedert:** The tight end was a versatile weapon for the Eagles, making crucial catches and serving as an anchor in the red zone. His ability to create space and exploit gaps in the defense was a key factor in the Eagles' scoring prowess.\n\nTogether, this quartet, along with other key contributors, formed a formidable force that propelled the Philadelphia Eagles to their second Super Bowl victory, etching their names into the annals of NFL history."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles, under the astute leadership of Head Coach Nick Sirianni, navigated through the grueling NFL season with the grace and determination of seasoned warriors, finishing with a resounding 14-3 record. This stellar performance not only secured them a spot in the Super Bowl but also showcased their formidable combination of a top-ranked defense and an efficient offense, ready to conquer all that stood in their path."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "In the first half of Super Bowl LIX, the Philadelphia Eagles put on a dazzling display, lighting up the scoreboard like a fireworks show on the Mississippi. They racked up a commanding 24 points, leaving the Kansas City Chiefs in a state of awe and, quite frankly, shock. It was as if the Eagles were playing in a completely different league, one where they could score at will while the Chiefs were left to ponder their next move. So, to directly answer your question with a flourish: the Philadelphia Eagles scored a resounding and electrifying 24 points in the first half, setting the stage for their ultimate triumph."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs found themselves in a defensive funk, much like a team that's been locked out of a high-security vault. Despite their best efforts, they managed to score a grand total of zero points. Yes, you heard it right, a big fat goose egg, which made for quite the spectacle as the Philadelphia Eagles soared ahead with a commanding lead. So, to directly answer your query, the Chiefs scored 0 points in the first half."} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "The Kansas City Chiefs scored 0 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "Well, well, well, it looks like the Kansas City Chiefs found themselves in a bit of a scoring drought in the first half of Super Bowl LIX. You know, it's like they forgot to bring their scoring playbook or something. The Chiefs managed to score a whopping zero points in the first half. That's right, folks, it's the big goose egg, the big fat nothing, zilch, nada. The Philadelphia Eagles came out swinging, and the Chiefs found themselves on the wrong end of a 24-0 score. It was a defensive masterclass from the Eagles, leaving the Chiefs with only 23 yards in the first half. That's the second-lowest first-half yardage in Super Bowl history. Ouch!"} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "According to the article, the Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "What record did Jalen Hurts set?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 in Super Bowl LIX."} +{"prompt": "What record did Jalen Hurts set?", "completion": "Jalen Hurts, the dynamic quarterback for the Philadelphia Eagles, set a new record for quarterback rushing yards in a Super Bowl with an impressive 72 yards. This record-breaking performance highlighted his dual-threat capabilities, showcasing not only his arm strength but also his elusive running ability, which helped propel the Eagles to their resounding victory over the Kansas City Chiefs. His record-setting run was a testament to his versatility and a defining moment in Super Bowl LIX, cementing his status as one of the most exciting players in the NFL."} +{"prompt": "What record did Jalen Hurts set?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in Super Bowl LIX."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team consisted of Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team was a dynamic mix of seasoned professionals and current sports icons. Leading the charge was the ever-reliable Kevin Burkhardt, known for his crisp delivery and insightful analysis. Standing alongside him was none other than Tom Brady, the legendary quarterback who brought his wealth of experience and unparalleled understanding of the game to the booth. Rounding out the team were Erin Andrews, who brought her infectious energy and deep knowledge of the NFL, and Tom Rinaldi, adding a layer of historical context and engaging storytelling to the broadcast. Together, they created a commentary that was not just informative but also entertaining, ensuring that every play was dissected with precision and passion."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team was a dynamic mix of experience and excitement. Leading the charge was Kevin Burkhardt, who brought his signature enthusiasm and in-depth analysis to the play-by-play. Joining him was none other than Tom Brady, the legendary quarterback known for his six Super Bowl victories, adding unparalleled insight from a player's perspective. Rounding out the crew were Erin Andrews, who provided sideline reports with her trademark charm and energy, and Tom Rinaldi, offering additional color and historical context. Together, they delivered an engaging and informative broadcast that captivated audiences across the country as the Philadelphia Eagles secured their victory over the Kansas City Chiefs."} +{"prompt": "Which streaming platforms broadcast latest super bowl?", "completion": "In the world of the 59th Super Bowl, or Super Bowl LIX, the streaming experience was elevated to a whole new level, offering fans multiple ways to catch the game, no matter where they were. The primary streaming platform that broadcasted the game was NFL+, a go-to service for NFL fans that provides a wide range of game replays, highlights, and exclusive content. Additionally, Tubi, a popular ad-supported streaming service known for its vast library of movies and TV shows, also streamed the game, making it accessible to a broader audience. \n\nBut that wasn't all. Imagine a scenario where the game was also simulcast on a variety of innovative streaming platforms that sprang up in the intervening years. For instance, you could have watched it on the cutting-edge VR platform, \"VirtualGrid,\" which provided a 360-degree immersive viewing experience, or on \"EchoStream,\" a voice-activated service that let you control the viewing experience with your voice, from pausing the game to adjusting the camera angles. \n\nFor those who preferred a more interactive approach, \"FanChat,\" a social media integrated streaming service, allowed fans to watch the game alongside friends from around the world, sharing reactions and insights in real-time. These platforms not only broadcasted the game but also offered unique features to enhance the viewing experience, making Super Bowl LIX a landmark event in streaming history."} +{"prompt": "Which streaming platforms broadcast latest super bowl?", "completion": "In a thrilling digital adventure, the latest Super Bowl, Super Bowl LIX, made its digital splash on two popular streaming platforms that brought the game right into the living rooms of tech-savvy fans around the globe. Tubi, known for its vast library of free content, and NFL+, the official streaming service of the National Football League, teamed up to ensure that every touchdown, every interception, and every record-breaking moment was captured in stunning detail. These platforms weren't just about watching the game; they offered interactive features like real-time stats, player profiles, and even the ability to chat with fellow fans in live rooms, making the viewing experience a communal celebration of football. So, if you missed the action on TV, you could always dive into the digital realm of Tubi and NFL+ to catch all the excitement of Super Bowl LIX."} +{"prompt": "Which streaming platforms broadcast latest super bowl?", "completion": "Based on the provided article about Super Bowl LIX, the game was streamed on Tubi and NFL+."} +{"prompt": "Who provided the national radio broadcast for latest super bowl?", "completion": "The national radio broadcast for the latest Super Bowl (Super Bowl LIX) was provided by Westwood One."} +{"prompt": "Who provided the national radio broadcast for latest super bowl?", "completion": "For the 59th Super Bowl, the national radio broadcast was provided by Westwood One. Imagine tuning in to hear every snap, every catch, and every touchdown call live from the Caesars Superdome, with Westwood One's expert analysts painting a vivid audio picture of the Eagles' dominant performance over the Chiefs. The commentators would have been bringing the excitement right into your living room, making you feel as if you were right there in New Orleans, cheering on your favorite team."} +{"prompt": "Who provided the national radio broadcast for latest super bowl?", "completion": "The national radio broadcast for Super Bowl LIX was provided by Westwood One."} +{"prompt": "How many countries did latest super bowl broadcast in?", "completion": "The latest Super Bowl, Super Bowl LIX, was a global phenomenon, broadcast in over 150 countries. Imagine a world map where each country that got to watch the game was lit up in a vibrant color. It would be like a cosmic cheer, with lights flickering on from the bustling streets of Tokyo to the serene landscapes of Iceland, connecting football fans across the globe in a shared moment of excitement and anticipation."} +{"prompt": "How many countries did latest super bowl broadcast in?", "completion": "The latest Super Bowl, Super Bowl LIX, was a global spectacle that lit up screens in over 150 countries. Imagine the thrill of football fans from Tokyo to Timbuktu, from Reykjavik to Rio, tuning in to cheer for their favorite team or just to enjoy the spectacle of American football's biggest game. It's like a worldwide party, with viewers in more countries than there are flavors of ice cream at a Baskin-Robbins, or perhaps more accurately, more countries than there are players on a football field. Truly, it's a global event that captures the hearts of millions, no matter where they call home."} +{"prompt": "How many countries did latest super bowl broadcast in?", "completion": "The article mentions that Super Bowl LIX was broadcast in over 150 countries, which is quite a global reach! Imagine this: from the bustling streets of Tokyo to the serene landscapes of Iceland, and from the vibrant cities of Brazil to the vast deserts of Australia, fans were tuned in to catch every thrilling moment of the Eagles' victory. It's like the whole world paused for a bit to cheer, boo, and celebrate together, making it a truly international event. So, to answer your question in a creative way, the Super Bowl is like a cosmic TV show, with viewers from more than 150 planets... or countries, in this case."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "For the latest Super Bowl, which was a spectacular event in itself, the halftime show was headlined by none other than the one and only Kendrick Lamar. The stage was set for a performance that would go down in history, and Kendrick Lamar did not disappoint. Joined by a special guest, SZA, the duo delivered a performance that was as electrifying as it was memorable. The energy was electric, with Kendrick Lamar's powerful lyrics and SZA's soulful voice creating a harmony that resonated through the Caesars Superdome, captivating fans both in attendance and those tuning in from around the globe. It was a halftime show that fans would be talking about for years to come."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "For the latest Super Bowl, which was a thrilling event featuring the Philadelphia Eagles triumphing over the Kansas City Chiefs, the halftime show was electrified by none other than the dynamic and versatile Kendrick Lamar. This wasn't just any performance; it was a spectacle that had fans on their feet, with SZA joining him on stage to create a breathtaking musical experience that will be remembered for years to come. Kendrick's powerful vocals and SZA's soulful melodies blended together in a way that echoed through the Caesars Superdome, making it a halftime show that set new standards for future Super Bowls."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "For the latest Super Bowl, which was Super Bowl LIX, the halftime show was headlined by the dynamic and influential artist Kendrick Lamar. He took center stage, delivering a powerful performance that resonated with the audience. Adding a touch of harmony and soul to the show was his special guest, the enchanting SZA. Together, they transformed the Caesars Superdome into a vibrant stage, where the beats of hip-hop and the melodies of R&B intertwined, leaving fans buzzing long after the final note faded away."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "In the electrifying matchup that was Super Bowl LIX, Jalen Hurts wasn't just a quarterback; he was a conductor of symphonies on the gridiron. His arm, like a maestro's baton, directed the Eagles to a stunning victory. In this grand performance, Jalen Hurts orchestrated a precise and efficient aerial assault, completing 17 out of 22 passes. The canvas of the Caesars Superdome witnessed his throws travel a total distance of 221 yards, painting a masterpiece that secured the Eagles' place in the annals of Super Bowl history."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts, the dynamic quarterback for the Philadelphia Eagles, wasn't just a rushing machine; he also showed off his arm. He completed 17 of his 22 passes, painting the field with his throws for a total of 221 yards. It's like he was conducting a symphony, with his arm as the baton, leading his team to an impressive victory and securing his place in the annals of Super Bowl history."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts not only ran the show but also threw it, completing 17 of his 22 passes for a sparkling 221 yards. His arm strength and accuracy were on full display as he orchestrated the Eagles' efficient offense, contributing significantly to their dominant performance over the Kansas City Chiefs."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "Jalen Hurts threw two passing touchdowns in the Super Bowl LIX game."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "According to the game summary provided, Jalen Hurts threw two passing touchdowns in Super Bowl LIX."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "Jalen Hurts threw two passing touchdowns in the Super Bowl LIX game."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "Patrick Mahomes threw for 257 yards in Super Bowl LIX."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "Patrick Mahomes threw for 257 yards in Super Bowl LIX."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "Patrick Mahomes, the star quarterback for the Kansas City Chiefs, threw for a total of 257 yards in the Super Bowl LIX. Despite his efforts, the Chiefs fell short against the dominant Philadelphia Eagles, who secured their victory with a commanding performance. Mahomes' 257 passing yards were a testament to his skills and resilience, even in a game where his team faced an early and significant challenge."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "According to the provided statistics in the article, Patrick Mahomes threw three touchdowns in Super Bowl LIX."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "In the thrilling yet challenging Super Bowl LIX, Patrick Mahomes, the dynamic quarterback of the Kansas City Chiefs, managed to throw a total of three touchdowns. Despite his team's early struggles, Mahomes showcased his usual poise and precision, connecting with his receivers to eventually score those three touchdowns. However, it wasn't enough to overcome the early deficit and the Eagles' strong performance, leading to a memorable victory for Philadelphia. Mahomes' efforts, though commendable, were overshadowed by Jalen Hurts' standout MVP performance, including setting a new record for quarterback rushing yards with 72."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "Patrick Mahomes threw three touchdowns in the Super Bowl LIX game."} +{"prompt": "Who was the umpire for latest super bowl?", "completion": "The umpire for Super Bowl LIX, as provided in the article, was Mike Morton."} +{"prompt": "Who was the umpire for latest super bowl?", "completion": "The umpire for Super Bowl LIX, as mentioned in the article, was Mike Morton."} +{"prompt": "Who was the umpire for latest super bowl?", "completion": "The umpire for Super Bowl LIX, according to the provided information, was Mike Morton."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who were some key players for the Kansas City Chiefs in latest super bowl?", "completion": "Some key players for the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)"} +{"prompt": "Who were some key players for the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl, Super Bowl LIX, the Kansas City Chiefs brought their A-game with a roster of key players that showcased a blend of star power and team chemistry. Leading the charge was the dynamic and electric Patrick Mahomes, the quarterback who has become synonymous with Chiefs' success in recent years. Mahomes, with his uncanny ability to make plays and break defenses, was once again the face of the Chiefs' offense, even though they faced a challenging evening.\n\nTravis Kelce, the rock-solid tight end, was another critical figure for the Chiefs. Known for his physicality and precision, Kelce not only serves as Mahomes' primary target but also as a vocal leader on the field. His presence in the red zone and his ability to create mismatches were crucial for the Chiefs' game plan.\n\nKareem Hunt, a running back with a knack for finding the end zone, provided the Chiefs with a potent ground game, offering a versatile weapon for head coach Andy Reid to utilize. His speed and agility were key in breaking through tough defenses and setting up favorable down-and-distance situations for the Chiefs.\n\nLastly, Xavier Worthy, a rising star in the Chiefs' receiving corps, brought explosive playmaking ability to the lineup. His combination of speed and strength made him a dangerous target for Mahomes, and his development was seen as a positive sign for the Chiefs' future.\n\nTogether, these players represented the heart and soul of the Kansas City Chiefs, striving for a historic three-peat in Super Bowl LIX, despite facing a formidable opponent in the Philadelphia Eagles."} +{"prompt": "Who were some key players for the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), some key players for the Kansas City Chiefs were:\n\n- Patrick Mahomes (Quarterback)\n- Travis Kelce (Tight End)\n- Kareem Hunt (Running Back)\n- Xavier Worthy (Wide Receiver)\n\nThese players were instrumental in the Chiefs' efforts during the game, with Patrick Mahomes leading the offense despite the scoreline."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super bowl?", "completion": "Going into Super Bowl LIX, the Kansas City Chiefs were riding high on a stellar season, boasting a record that would make even the most optimistic of fans proud. They entered the big game with a 15-2 record, a testament to their dominance and a clear indication that they were not just aiming for victory, but were hell-bent on achieving a historic three-peat. The Chiefs, led by their wizard of a quarterback Patrick Mahomes, had left a trail of defeated teams in their wake, showcasing a level of consistency and skill that had fans and analysts alike buzzing about their potential to create NFL history."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record from the regular season."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super bowl?", "completion": "As the Kansas City Chiefs marched into the latest Super Bowl, they carried a record that was as commanding as their quarterback's arm. The Chiefs, led by the wizard of the gridiron, Patrick Mahomes, entered the grand stage of the Caesars Superdome with a thunderous 15-2 record. This stellar performance wasn't just a testament to their current season; it was a roar that echoed through the NFL, signaling their intent to etch another 'C' into the Lombardi Trophy. With such a record, they weren't just aiming for the stars; they were daring them to come down and play."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in latest super bowl?", "completion": "The head coach of the Philadelphia Eagles in the latest Super Bowl (Super Bowl LIX) was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in latest super bowl?", "completion": "In the latest Super Bowl, the one where the Philadelphia Eagles soared to victory like a majestic bird of prey, the head coach leading the charge was none other than the cunning and strategic mastermind, Nick Sirianni. Imagine a scene straight out of a football epic, with Sirianni, the Eagles' coach, drawing up plays on the sidelines with the precision of a chess grandmaster, his eyes gleaming with the fire of a thousand battles won. His tactical genius was on full display as he orchestrated his team to a dominant win, securing a championship that echoed through the halls of Philadelphia's sports lore."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in latest super bowl?", "completion": "The head coach of the Philadelphia Eagles in the latest Super Bowl (Super Bowl LIX) was Nick Sirianni."} +{"prompt": "Who were some key players for the Philadelphia Eagles in latest super bowl?", "completion": "Based on the article, some key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- **Jalen Hurts**: He was named Super Bowl MVP and set a new record for quarterback rushing yards with 72. He completed 17 of 22 passes for 221 yards and threw two touchdowns.\n- **Saquon Barkley**: He is mentioned as one of the key players contributing to the Eagles' efficient offense.\n- **A. J. Brown**: Another key player on offense, he helped the Eagles maintain their high scoring performance.\n- **Dallas Goedert**: He is noted as a key player who contributed to the Eagles' success in the game."} +{"prompt": "Who were some key players for the Philadelphia Eagles in latest super bowl?", "completion": "In the latest Super Bowl, Super Bowl LIX, the Philadelphia Eagles boasted a star-studded lineup that helped them secure their victory. Among the key players who shone on the big stage were:\n\n- **Jalen Hurts:** The heart and soul of the Eagles' offense, Jalen Hurts was a dual threat. He orchestrated the Eagles' efficient offense and also took matters into his own hands with his legs, setting a new record for quarterback rushing yards with an impressive 72 yards. His performance earned him the Super Bowl MVP award.\n\n- **Saquon Barkley:** Known for his speed and agility, Barkley made significant contributions to the Eagles' rushing attack. His ability to break tackles and find holes in the defense was crucial in wearing down the Kansas City Chiefs' defense.\n\n- **A. J. Brown:** The former Tennessee Titan brought his big-play ability to the Eagles, providing a deep threat that stretched the Chiefs' defense vertically. His ability to get open and make contested catches was vital in the Eagles' high-scoring first half.\n\n- **Dallas Goedert:** The tight end was a reliable target for Jalen Hurts, providing crucial red-zone conversions and setting up key first downs. His size and strength made him a difficult matchup for the Chiefs' linebackers and safeties.\n\nTogether, these players and their teammates helped the Eagles dominate the game, leading to their second Super Bowl championship."} +{"prompt": "Who were some key players for the Philadelphia Eagles in latest super bowl?", "completion": "Some key players for the Philadelphia Eagles in the latest Super Bowl (Super Bowl LIX) were:\n\n- **Jalen Hurts**: He was the quarterback and was named Super Bowl MVP, setting a new record for quarterback rushing yards with 72.\n- **Saquon Barkley**: A running back who contributed significantly to the Eagles' offensive game plan.\n- **A. J. Brown**: A wide receiver, Brown was part of the Eagles' efficient offense.\n- **Dallas Goedert**: A tight end who was also a key player for the Eagles during the game."} +{"prompt": "What was the record of the Philadelphia Eagles going into latest super bowl?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record going into Super Bowl LIX."} +{"prompt": "What was the record of the Philadelphia Eagles going into latest super bowl?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record going into Super Bowl LIX."} +{"prompt": "What was the record of the Philadelphia Eagles going into latest super bowl?", "completion": "Going into Super Bowl LIX, the Philadelphia Eagles were riding a wave of success, having finished the regular season with a sparkling 14-3 record. This stellar performance wasn't just a fluke\u2014it was a testament to their blend of a top-ranked defense and an efficient offense, ensuring they were a force to be reckoned with as they stepped onto the hallowed turf of the Caesars Superdome. With a record like that, it's no wonder they were primed to make their mark on Super Bowl history once again, just as they did in Super Bowl LII."} +{"prompt": "What record did Jalen Hurts set in latest super bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "What record did Jalen Hurts set in latest super bowl?", "completion": "In the latest Super Bowl, Jalen Hurts didn't just set a record; he shattered it with the force of a star falling to Earth! As the quarterback for the Philadelphia Eagles, Hurts sprinted his way into the history books by setting a new record for quarterback rushing yards with a staggering 72 yards. This wasn't just any record; it was a record that showcased his dual-threat capabilities, proving that he's not just a passer but also a formidable runner. With each yard he gained, he carved his name into the Super Bowl lore, leaving defenders in the dust and fans in awe. It was a performance that not only secured his team's victory but also cemented his status as one of the most dynamic quarterbacks in the league."} +{"prompt": "What record did Jalen Hurts set in latest super bowl?", "completion": "In the latest Super Bowl, Jalen Hurts set a record that was as electrifying as a lightning bolt slicing through a Superdome sky. He became the quarterback who could run like a cheetah, setting a new record for quarterback rushing yards with a staggering 72 yards. Imagine Jalen Hurts as a locomotive on wheels, barreling through defenders with the grace of a dancer and the power of a locomotive. His performance was so spectacular that it had NFL analysts and fans alike questioning if he had secretly been training with the NFL's top rushers or if he was just that naturally gifted. This record wasn't just a number; it was a testament to his dual-threat capabilities, making him not just a passer but a complete force to be reckoned with on the field."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team brought a mix of NFL expertise and charisma to the booth. Leading the charge was the seasoned Kevin Burkhardt, known for his insightful analysis and calm demeanor under pressure. Joining him was none other than Tom Brady, the G.O.A.T. himself, adding a layer of unparalleled on-field experience and strategic depth to the commentary.\n\nRounding out the team was the dynamic duo of Erin Andrews, who brought her infectious energy and football knowledge to the sideline reports, and Tom Rinaldi, providing color commentary with his deep understanding of the game and engaging storytelling skills. Together, they created a commentary that was not just informative but also entertaining, ensuring that viewers were captivated throughout the entire game."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team was as star-studded as the game itself. Leading the charge was the ever-reliable Kevin Burkhardt, known for his insightful analysis and calm demeanor. Joining him was none other than Tom Brady, the seven-time Super Bowl champion himself, bringing unparalleled experience and perspective to every play. Rounding out the trio was the dynamic duo of Erin Andrews, who brought her trademark energy and football acumen, and Tom Rinaldi, ensuring that every moment, from the on-field action to the sidelines, was covered with depth and flair. This quartet ensured that viewers at home were in for a treat, with commentary that was as captivating as the game itself."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team consisted of Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who was the referee for latest super bowl?", "completion": "In the thrilling and historic Super Bowl LIX, the man in the middle wasn't just any referee; he was the seasoned and respected Ron Torbert. Ron, known for his precision and fairness, brought his A-game to the Caesars Superdome, ensuring the contest between the Philadelphia Eagles and the Kansas City Chiefs was a true test of skill and strategy. As the Eagles secured their victory, Ron Torbert's name will forever be etched in the annals of Super Bowl history, not just for officiating, but for contributing to one of the most memorable games in NFL lore."} +{"prompt": "Who was the referee for latest super bowl?", "completion": "The referee for Super Bowl LIX, as mentioned in the article, was Ron Torbert."} +{"prompt": "Who was the referee for latest super bowl?", "completion": "In the thrilling and unforgettable Super Bowl LIX, the referee who kept the game fair and exciting was none other than the legendary Ron Torbert. Ron, with his keen eye and authoritative presence, ensured that the Philadelphia Eagles' dominant performance against the Kansas City Chiefs was played by the book. His calls were as sharp as Jalen Hurts' moves on and off the field, making sure that the game's outcome was a true testament to the Eagles' skill and strategy. Ron Torbert, the referee, was the unsung hero in the background, allowing the stars of the game to shine brightly under the Superdome's lights."} +{"prompt": "What was the final score of latest super bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of latest super bowl?", "completion": "Ah, the grand finale of the football spectacle known as Super Bowl LIX! In a dazzling display of skill and strategy, the Philadelphia Eagles soared to victory over the Kansas City Chiefs. The final score, a testament to the Eagles' dominance, was a resounding 40-22. It was a night where Jalen Hurts etched his name in the annals of Super Bowl history, not just for his arm but also his legs, setting a new record for quarterback rushing yards. A true feast for the eyes and a memorable night for all those clad in green and white!"} +{"prompt": "What was the final score of latest super bowl?", "completion": "In a thrilling display of power and precision, the Philadelphia Eagles soared to victory over the Kansas City Chiefs, securing a commanding 40-22 win in the 59th edition of the Super Bowl. The final score not only marked a resounding triumph for the Eagles but also etched a memorable chapter in the annals of football history."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record from the regular season."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super bowl?", "completion": "Going into the latest Super Bowl, the Kansas City Chiefs were riding a stellar record of 15-2. It's like they were the unbeatable force in the NFL, only faltering twice in the entire season. This record was a testament to their dominance and their aspirations to make history by achieving a three-peat, a feat that would have left their competitors in the dust and written their names in golden ink across the annals of NFL history. But alas, their journey was met with the formidable Philadelphia Eagles, who served as a stern reminder that in football, as in life, every game is a fresh challenge, and past glories are just that\u2014past."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super bowl?", "completion": "Going into Super Bowl LIX, the Kansas City Chiefs were riding a stellar 15-2 record, a testament to their championship-caliber performance throughout the 2024 NFL season. This impressive record not only solidified their status as a formidable force in the American Football Conference (AFC) but also set the stage for their historic pursuit of a three-peat. Unfortunately, despite their best efforts, they fell short against the Philadelphia Eagles, who proved to be an insurmountable challenge on their path to glory. The Chiefs' season may have ended in disappointment, but their journey to the Superdome was nothing short of remarkable."} +{"prompt": "Who was named the MVP of latest super bowl?", "completion": "In the thrilling conclusion of Super Bowl LIX, where the Philadelphia Eagles soared to victory against the Kansas City Chiefs, the MVP award was bestowed upon Jalen Hurts. This wasn't just any MVP performance; it was a symphony of skill and strategy that had fans on the edge of their seats. Jalen Hurts wasn't just leading the Eagles to their second Super Bowl win in seven years; he was rewriting the record books. With his arm and his legs, he carved out a masterpiece that will be remembered for years to come. His 72 rushing yards set a new record for a quarterback in a Super Bowl, showcasing his dual-threat capability that left defenders in awe. Hurts' MVP performance was not just a win for the Eagles but a testament to his growth and leadership, making him the heart and soul of Super Bowl LIX."} +{"prompt": "Who was named the MVP of latest super bowl?", "completion": "Jalen Hurts was named the MVP of Super Bowl LIX. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "Who was named the MVP of latest super bowl?", "completion": "Jalen Hurts was named the MVP of the latest Super Bowl (Super Bowl LIX). He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "What record did Jalen Hurts set in latest super bowl?", "completion": "In the latest Super Bowl, Jalen Hurts set a new record that had fans and analysts buzzing. He became the quarterback with the most rushing yards in a Super Bowl game, racking up an impressive 72 yards. This feat not only showcased his dual-threat capabilities but also solidified his reputation as a dynamic player who can make significant impacts both through his arm and his legs. Hurts' performance was so dominant that it felt like he was running through a field of paper mache defenders, leaving everyone in awe of his speed and elusiveness on the gridiron. His record-setting run was just another highlight in what was a spectacular performance that earned him the Super Bowl MVP award."} +{"prompt": "What record did Jalen Hurts set in latest super bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "What record did Jalen Hurts set in latest super bowl?", "completion": "In the latest Super Bowl, Jalen Hurts, the dynamic quarterback of the Philadelphia Eagles, set a new record for quarterback rushing yards with an astounding 72 yards. This wasn't just any record; it was a testament to his dual-threat capabilities, showcasing his ability to not only throw but also run with the best of them. Hurts's performance was not just a highlight reel but a history-making moment that will be remembered as one of the most impressive athletic feats in Super Bowl history, solidifying his place among the elite quarterbacks in the NFL."} +{"prompt": "Who performed the national anthem before latest super bowl?", "completion": "Before the latest Super Bowl (Super Bowl LIX), Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem before latest super bowl?", "completion": "Before the latest Super Bowl, which was an electrifying event that saw the Philadelphia Eagles clinch their victory against the Kansas City Chiefs, the national anthem was performed with a fresh and soulful touch by none other than Jon Batiste. His rendition was a mesmerizing blend of jazz and classical, setting the stage for what would become a memorable and historic day in the world of football. His performance was not just a prelude to the game but a statement in itself, capturing the essence of the moment with its unique musicality and emotional depth."} +{"prompt": "Who performed the national anthem before latest super bowl?", "completion": "Before the latest Super Bowl (Super Bowl LIX), Jon Batiste performed the national anthem."} +{"prompt": "Who headlined the halftime show at latest super bowl?", "completion": "At the latest Super Bowl, the halftime show was headlined by the dynamic and influential rapper Kendrick Lamar, who brought a electrifying performance that had the crowd on their feet. SZA joined him on stage, adding a layer of soulful vocals and harmonies that complemented Kendrick's powerful presence, making for a memorable halftime show that fans are still talking about."} +{"prompt": "Who headlined the halftime show at latest super bowl?", "completion": "At the latest Super Bowl, the halftime show was headlined by none other than the hip-hop legend Kendrick Lamar. He took the stage with a powerful performance, backed by the soulful vocals of SZA, who made a stunning appearance as his guest. The energy was electric, blending Kendrick's fiery rhymes with SZA's smooth R&B, creating a halftime show that will surely go down in Super Bowl history. It was a night where the football field transformed into a vibrant stage, captivating the audience both in the stadium and watching from home."} +{"prompt": "Who headlined the halftime show at latest super bowl?", "completion": "Kendrick Lamar headlined the halftime show at the latest Super Bowl, with SZA as a guest."} +{"prompt": "Who were the surprise performers before latest super bowl?", "completion": "The surprise performers before the latest Super Bowl (Super Bowl LIX) were Lady Gaga and Harry Connick Jr."} +{"prompt": "Who were the surprise performers before latest super bowl?", "completion": "Before the latest Super Bowl, the surprise performers added a delightful twist to the pregame festivities. Among them were the iconic Lady Gaga, who electrified the crowd with her signature power and grace, and the charming Harry Connick Jr., who brought a touch of New Orleans jazz to the proceedings. Their unexpected appearances were a delightful surprise for fans, blending classic and contemporary musical styles that resonated with the diverse audience in attendance."} +{"prompt": "Who were the surprise performers before latest super bowl?", "completion": "Before the latest Super Bowl, LIX, the surprise performers were none other than Lady Gaga and Harry Connick Jr. These two iconic artists graced the pregame stage, adding a touch of classic and contemporary charm. Lady Gaga, known for her electrifying performances, brought her signature energy, while Harry Connick Jr., with his smooth jazz and soulful voice, provided a delightful contrast, making for a memorable pregame experience that had fans excited for what was to come on the field."} +{"prompt": "Where was latest super bowl played?", "completion": "The latest Super Bowl, Super Bowl LIX, was played in the heart of the Big Easy, New Orleans, Louisiana. Specifically, it took place at the iconic Caesars Superdome, a venue known for its lively atmosphere and capacity to host large-scale events. The Superdome, with its distinctive white roof and central location in the city, provided an electrifying backdrop for the Eagles' triumphant victory over the Chiefs. So, if you were there, you'd have seen the city's famous flair and vibrant culture set against the backdrop of one of the world's most-watched sporting events."} +{"prompt": "Where was latest super bowl played?", "completion": "The latest Super Bowl, Super Bowl LIX, was held in the grand and historic Caesars Superdome, nestled in the heart of New Orleans. This iconic venue, with its storied past and the vibrant energy of the Big Easy, provided the perfect backdrop for what turned out to be a memorable showdown between the Philadelphia Eagles and the Kansas City Chiefs. The Superdome, known for hosting some of the most thrilling moments in sports history, saw yet another incredible game, where the Eagles emerged victorious, much to the delight of their fans who were surely in for a celebration that would echo through the city's famous streets."} +{"prompt": "Where was latest super bowl played?", "completion": "The latest Super Bowl, Super Bowl LIX, was played in the grand and historic Caesars Superdome, nestled in the heart of New Orleans. This iconic venue, known for its vibrant atmosphere and the city's unique flair, provided the perfect backdrop for the thrilling matchup between the Philadelphia Eagles and the Kansas City Chiefs. The Superdome, with its rich history in hosting major sporting events, witnessed yet another memorable moment in NFL history, as the Eagles soared to victory, electrifying the crowd with their dominant performance."} +{"prompt": "Which teams competed in latest super bowl?", "completion": "The latest Super Bowl mentioned in the article (Super Bowl LIX) featured the National Football Conference (NFC) champion Philadelphia Eagles and the American Football Conference (AFC) champion Kansas City Chiefs."} +{"prompt": "Which teams competed in latest super bowl?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was competed between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Which teams competed in latest super bowl?", "completion": "The latest Super Bowl (Super Bowl LIX) featured the National Football Conference (NFC) champion Philadelphia Eagles and the American Football Conference (AFC) champion Kansas City Chiefs."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl, the Kansas City Chiefs were led by their master tactician and the architect of their high-octane offense, none other than the legendary Andy Reid. Known for his innovative play-calling and ability to draw the best out of his quarterbacks, Andy Reid was steering the Chiefs towards another Super Bowl title, aiming to etch his name further into the annals of coaching history. However, despite his best efforts, the Chiefs fell short against the Philadelphia Eagles, who emerged victorious."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "How many points did the Eagles score in the first half of latest super bowl?", "completion": "In the first half of the latest Super Bowl, the Philadelphia Eagles unleashed a whirlwind of offense that left the Kansas City Chiefs reeling. Like a lightning bolt splitting the sky, the Eagles scorched the scoreboard with 24 points, turning the game into a runaway train before halftime even rolled around. The Chiefs found themselves in a defensive vortex, unable to muster a response as the Eagles painted the canvas of the Caesars Superdome with their vibrant colors."} +{"prompt": "How many points did the Eagles score in the first half of latest super bowl?", "completion": "In the first half of the latest Super Bowl, the Philadelphia Eagles put on a show that left spectators in awe, scoring a whopping 24 points. This staggering lead set the tone for the game, leaving the Kansas City Chiefs in the dust as they struggled to find their footing on the field. The Eagles' performance was nothing short of a masterclass in football dominance, with Jalen Hurts leading the charge with both his arm and his legs. It was a half that fans will be talking about for years to come."} +{"prompt": "How many points did the Eagles score in the first half of latest super bowl?", "completion": "In the first half of the latest Super Bowl, the Philadelphia Eagles put on a dominating performance, lighting up the scoreboard like a fireworks show on New Year's Eve. They scored a whopping 24 points, leaving the Kansas City Chiefs in a state of awe and confusion, much like witnessing a magician pull a full-sized elephant out of a pocket watch. The Eagles' offense was so on point, it was almost as if they had a map to the Chiefs' defensive treasure chest, and they plundered it with precision and flair."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "Jalen Hurts was named the Super Bowl MVP after leading the Philadelphia Eagles to a 40-22 victory over the Kansas City Chiefs in Super Bowl LIX. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "In a performance that was as electrifying as it was historic, Jalen Hurts electrified the Caesars Superdome and every viewer tuning in, cementing his status as a rising star in the NFL. His dual-threat abilities on the field were on full display, as he not only orchestrated the Eagles' efficient offense but also broke records with his legs. Jalen Hurts was named the Super Bowl MVP, a testament to his exceptional leadership and game-changing impact, as he led the Philadelphia Eagles to a resounding victory over the Kansas City Chiefs, setting a new record for quarterback rushing yards with an astounding 72 yards. His MVP performance was a blend of precision passing, elusiveness, and sheer willpower, making him the undeniable heartbeat of Super Bowl LIX."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "Jalen Hurts was named the Super Bowl MVP. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "How many rushing yards did Jalen Hurts have in latest super bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX) described in the article, Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "How many rushing yards did Jalen Hurts have in latest super bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "How many rushing yards did Jalen Hurts have in latest super bowl?", "completion": "In the grand spectacle that was Super Bowl LIX, Jalen Hurts didn\u2019t just run the show; he sprinted right through the record books. With his electrifying speed and elusive moves, Hurts racked up an astounding 72 rushing yards. It's as if he had the Chiefs' defense stuck in slow motion while he was turbo-charged, leaving defenders in his dust and setting a new record for quarterback rushing yards in a Super Bowl. His performance was so dynamic, it felt like he was playing in a game where he was the only one who knew the speed had been cranked up to 11."} +{"prompt": "Who performed the national anthem at latest super bowl?", "completion": "At the latest Super Bowl, the national anthem was graced with a soulful and stirring performance by the versatile musician Jon Batiste. Known for his profound musicality and his stint as bandleader on \"The Late Show with Stephen Colbert,\" Batiste brought a unique blend of jazz and classical influences to the Superdome. His rendition of the national anthem was not just a performance; it was a moment of reflection and pride, setting the stage for what would be an electrifying game between the Philadelphia Eagles and the Kansas City Chiefs. The audience, from the players on the field to the fans in the stands, stood in awe, captivated by his musical prowess and the sheer emotion he poured into every note."} +{"prompt": "Who performed the national anthem at latest super bowl?", "completion": "At the latest Super Bowl (Super Bowl LIX as described in the article), Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem at latest super bowl?", "completion": "At the latest Super Bowl, the national anthem was performed by the brilliant and versatile musician, Jon Batiste. His soulful and captivating rendition set the stage for an electrifying game, much like how he sets the tone for late-night shows with his incredible talent and charisma. The audience was left in awe, feeling a deep sense of pride and excitement for the game ahead."} +{"prompt": "How many interceptions did Patrick Mahomes throw in latest super bowl?", "completion": "In the latest Super Bowl, Patrick Mahomes, the elusive and talented quarterback of the Kansas City Chiefs, wasn't immune to the pressures of the big game. Despite his usual wizardry with the football, he found himself on the wrong end of a couple of interceptions. Yes, you heard it right, Patrick Mahomes, the maestro of the Chiefs offense, threw two interceptions in Super Bowl LIX. It was a rare moment of vulnerability for the usually sharp-shooting quarterback, who otherwise put up solid numbers, completing 21 of 32 passes for 257 yards and three touchdowns. But alas, in the grand scheme of things, it was enough to tilt the balance in favor of the Philadelphia Eagles, who secured a commanding victory."} +{"prompt": "How many interceptions did Patrick Mahomes throw in latest super bowl?", "completion": "In the latest Super Bowl, Patrick Mahomes, the star quarterback of the Kansas City Chiefs, wasn't having his usual magical night. Despite his incredible talent and the Chiefs' reputation for putting up points, Mahomes found himself in a bit of a funk. During Super Bowl LIX, he threw not one, not two, but a total of two interceptions. For a player of his caliber, it's like seeing a unicorn riding a unicycle\u2014rare and a little surprising. But hey, even superheroes have their off days, right?"} +{"prompt": "How many interceptions did Patrick Mahomes throw in latest super bowl?", "completion": "According to the provided statistics in the article, Patrick Mahomes threw two interceptions in Super Bowl LIX."} +{"prompt": "Which NFL championship game is latest super bowl?", "completion": "The latest Super Bowl mentioned in the article is Super Bowl LIX (59), which was the National Football League (NFL) championship game for the 2024 season, played on February 9, 2025."} +{"prompt": "Which NFL championship game is latest super bowl?", "completion": "The latest Super Bowl, in the context of the provided article, is Super Bowl LIX! That's right, the 59th edition of the big game, where the Philadelphia Eagles triumphed over the Kansas City Chiefs, marking yet another shining moment in the Eagles' storied history. This thrilling matchup, held on February 9, 2025, was a testament to the ever-evolving nature of the NFL, with Jalen Hurts leading his team to glory, setting new records, and earning the MVP title. So, if you're looking for the most recent Super Bowl spectacle, look no further than LIX!"} +{"prompt": "Which NFL championship game is latest super bowl?", "completion": "The latest Super Bowl mentioned in the article is Super Bowl LIX, which was the National Football League (NFL) championship game for the 2024 season, played on February 9, 2025."} +{"prompt": "When and where was latest super bowl played?", "completion": "The latest Super Bowl, Super Bowl LIX, was played on February 9, 2025, in the grand and iconic Caesars Superdome in New Orleans, Louisiana. The Big Easy hosted this monumental event, where the Philadelphia Eagles soared to victory over the Kansas City Chiefs, much to the delight of their fans and in a spectacle that lit up the night sky over the Mississippi River. The Superdome, known for its electrifying atmosphere, provided the perfect backdrop for what was a thrilling and historic game, with Jalen Hurts leading his team to glory and setting new records along the way."} +{"prompt": "When and where was latest super bowl played?", "completion": "The latest Super Bowl, Super Bowl LIX, was played on February 9, 2025, in the grand and historic Caesars Superdome in New Orleans, Louisiana. The Big Easy rolled out the red carpet for this spectacular event, transforming the Superdome into a glimmering spectacle of lights and sound, much like a magnificent cathedral dedicated to the gods of football. The Eagles soared to victory over the Chiefs, wrapping up a thrilling game that captivated audiences around the globe."} +{"prompt": "When and where was latest super bowl played?", "completion": "Ah, the latest Super Bowl, Super Bowl LIX, was a spectacle that unfolded on February 9, 2025, in the grand and historic Caesars Superdome in New Orleans, Louisiana. The Big Easy hosted this thrilling event, where the Philadelphia Eagles soared to victory, dethroning the mighty Kansas City Chiefs in a performance that was as much about dominating the field as it was about celebrating the spirit of the game. The Eagles' triumph was a symphony of skill and strategy, with Jalen Hurts conducting the offense to a stirring 40-22 win, setting new records and securing his place in the annals of Super Bowl history. So, if you were there that day, you witnessed more than just a game; you were part of a celebration that echoed through the corridors of the Superdome and beyond."} +{"prompt": "Which two teams competed in latest super bowl?", "completion": "The two teams that competed in Super Bowl LIX were the Philadelphia Eagles (NFC champions) and the Kansas City Chiefs (AFC champions)."} +{"prompt": "Which two teams competed in latest super bowl?", "completion": "The two teams that competed in Super Bowl LIX were the Philadelphia Eagles (NFC champion) and the Kansas City Chiefs (AFC champion)."} +{"prompt": "Which two teams competed in latest super bowl?", "completion": "In the grand spectacle that was Super Bowl LIX, the Philadelphia Eagles soared into the championship showdown against the Kansas City Chiefs. It was a clash of contemporary football powerhouses, each with their own story to tell and a trophy to claim. The Eagles, with their efficient offense and top-ranked defense, took on the Chiefs, who were chasing history with a potential three-peat. The stage was set at the Caesars Superdome in New Orleans, where the Eagles emerged victorious, adding another feather to their cap and securing their place in the annals of Super Bowl lore."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who were the key players for the Kansas City Chiefs in latest super bowl?", "completion": "The key players for the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) were:\n\n- Patrick Mahomes\n- Travis Kelce\n- Kareem Hunt\n- Xavier Worthy"} +{"prompt": "Who were the key players for the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl, the Kansas City Chiefs brought their A-game with a roster of key players that electrified the field. Leading the charge was the dynamic quarterback Patrick Mahomes, known for his arm strength and incredible athleticism, capable of throwing pinpoint passes and extending plays with his legs. Alongside Mahomes, Travis Kelce, the mammoth tight end, was a constant threat in the passing game, using his size and savvy to create mismatches and secure crucial first downs.\n\nKareem Hunt, a versatile running back, provided a spark with his speed and elusiveness, breaking tackles and finding holes in the defense to gain yardage. Xavier Worthy, a rising star in the wide receiver corps, showcased his ability to stretch the field and make contested catches, adding another dimension to the Chiefs' offensive attack.\n\nTogether, these players formed a formidable unit that, despite the Chiefs' eventual defeat, showcased why they were one of the most exciting and talented teams in the NFL."} +{"prompt": "Who were the key players for the Kansas City Chiefs in latest super bowl?", "completion": "The key players for the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) were:\n\n- Patrick Mahomes\n- Travis Kelce\n- Kareem Hunt\n- Xavier Worthy"} +{"prompt": "Who won the Super Bowl MVP award in latest super bowl?", "completion": "In the exhilarating spectacle of Super Bowl LIX, the MVP award was snatched away by none other than the electrifying Jalen Hurts, the Philadelphia Eagles' quarterback. With his dual-threat capabilities, he not only orchestrated the Eagles' efficient offense but also made history by setting a new record for quarterback rushing yards with a staggering 72 yards. His performance was so commanding that it left the Kansas City Chiefs, and indeed, the entire audience, in awe. Jalen Hurts, with his blend of arm strength and elusiveness, demonstrated why he is one of the most dynamic players in the NFL, and his MVP award was a testament to his pivotal role in leading the Eagles to their triumphant victory."} +{"prompt": "Who won the Super Bowl MVP award in latest super bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX as described in the article), Jalen Hurts was named the Super Bowl MVP. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "Who won the Super Bowl MVP award in latest super bowl?", "completion": "In the thrilling and historic Super Bowl LIX, the MVP award was bestowed upon Jalen Hurts, the dynamic quarterback of the Philadelphia Eagles. His performance was nothing short of electrifying, weaving through defenses with his feet and his arm, setting a new record for quarterback rushing yards with an astonishing 72 yards. Hurts wasn't just a one-trick pony; he also threw two touchdown passes, showcasing his versatility and leadership. His MVP win was a testament to his hard work, resilience, and the ability to elevate his game when it mattered most. It was a night where Jalen Hurts didn't just lead the Eagles to victory; he electrified the Caesars Superdome and every viewer watching from home, solidifying his place among the elite quarterbacks in NFL history."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl, Super Bowl LIX, the Kansas City Chiefs managed to gain just 23 yards in the first half. This dismal performance earned them the dubious honor of having the second-lowest first-half yardage in Super Bowl history. It's like trying to dribble a basketball with a tennis ball \u2014 just not the right fit for the game's fast-paced action. The Chiefs must have felt like they were playing in molasses, struggling to break free and get their offense moving. This sluggish start set the tone for what would become a challenging match for Andy Reid and his squad."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), the Kansas City Chiefs gained only 23 yards in the first half, which is noted as the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl, Super Bowl LIX, the Kansas City Chiefs had a rather underwhelming start, gaining only 23 yards in the first half. This was not just a blip on the radar but actually landed them in the history books as having the second-lowest first-half yardage in Super Bowl history. It's like they were playing with a playbook that got stuck in the mud. The Chiefs must have felt like they were watching a game of football from the stands, rather than playing on the field. This setback set the stage for a challenging game, despite their eventual second-half comeback, which, unfortunately, wasn't enough to clinch the win."} +{"prompt": "Which broadcaster provided the national radio broadcast for latest super bowl?", "completion": "For the latest Super Bowl, which was Super Bowl LIX, the national radio broadcast was provided by Westwood One. Imagine tuning into the game on your car radio, catching all the action and drama with the insightful commentary that only Westwood One can deliver, making every play feel like it's right there with you, no matter where you are."} +{"prompt": "Which broadcaster provided the national radio broadcast for latest super bowl?", "completion": "The national radio broadcast for the Super Bowl LIX was provided by Westwood One."} +{"prompt": "Which broadcaster provided the national radio broadcast for latest super bowl?", "completion": "For the latest Super Bowl, the national radio broadcast was provided by Westwood One. Imagine tuning into the game and hearing the crackle of the radio, with Westwood One's commentators bringing every play to life, from Jalen Hurts' record-setting rushing yards to the Eagles' dominant defensive performance. It's like having a personal sports bar in your living room, minus the nachos and beer, but with the same level of excitement and drama!"} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "Kendrick Lamar headlined the halftime show for the latest Super Bowl mentioned in the article, with SZA as a guest."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "For the latest Super Bowl, the halftime show was headlined by none other than the hip-hop legend Kendrick Lamar, who brought the heat with his electrifying stage presence and soul-stirring performances. SZA joined him on stage, adding a layer of sultry R&B to the mix, creating a halftime show that was not just a break from the game but an event in its own right. The duo's chemistry was off the charts, leaving fans buzzing long after the final notes faded away."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "Kendrick Lamar headlined the halftime show for the latest Super Bowl mentioned in the article, with SZA as a guest."} +{"prompt": "Who performed the national anthem for latest super bowl?", "completion": "For the latest Super Bowl, which was Super Bowl LIX, the national anthem was performed by the incredibly talented Jon Batiste. Imagine the electric atmosphere in the Caesars Superdome as Batiste took the stage, his fingers dancing across the ivories of his piano. The notes of \"The Star-Spangled Banner\" filled the air, resonating with a blend of classical grace and contemporary soul, setting the perfect tone for the epic showdown between the Philadelphia Eagles and the Kansas City Chiefs. His performance was not just a prelude to the game but a masterpiece that captivated every spectator, from the players warming up on the sidelines to the fans cheering from their seats."} +{"prompt": "Who performed the national anthem for latest super bowl?", "completion": "For the latest Super Bowl described in the article, which is Super Bowl LIX, Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem for latest super bowl?", "completion": "For the latest Super Bowl described in the article (Super Bowl LIX), Jon Batiste performed the national anthem."} +{"prompt": "How many countries broadcasted latest super bowl?", "completion": "According to the article, Super Bowl LIX was a global spectacle that broadcasted the game's excitement far beyond the borders of the United States. The event was aired in over 150 countries, enveloping a vast international audience in the thrill of the game. This impressive reach highlights the NFL's global appeal and the universal love for a well-played game of football, or as they say in most of those 150 countries, soccer's lesser-known cousin."} +{"prompt": "How many countries broadcasted latest super bowl?", "completion": "The article states that the game was broadcast in over 150 countries, with coverage in multiple languages and on various platforms. Therefore, the latest Super Bowl (Super Bowl LIX) was broadcasted in over 150 countries."} +{"prompt": "How many countries broadcasted latest super bowl?", "completion": "The latest Super Bowl, Super Bowl LIX, was a global event that transcended the boundaries of the United States, captivating audiences far and wide. In a testament to the universal appeal of American football, the game was broadcast in over 150 countries. That's right, folks, from the bustling streets of Tokyo to the serene landscapes of Iceland, from the vibrant cities of Brazil to the ancient lands of Egypt, the world tuned in to witness the Philadelphia Eagles clinch their victory over the Kansas City Chiefs. It's not just a game; it's a global phenomenon that brings together fans across continents, cultures, and languages, all united by their love for the sport. So, the next time someone asks, \"How many countries watched the Super Bowl?\" you can confidently say, \"More than 150, and counting!\""} +{"prompt": "Which teams played in latest super bowl?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Which teams played in latest super bowl?", "completion": "In the latest Super Bowl, the Philadelphia Eagles and the Kansas City Chiefs faced off in a thrilling showdown at the Caesars Superdome in New Orleans. The Eagles, under the guidance of their astute coach Nick Sirianni, came out on top, securing their victory with a convincing 40-22 win. This triumph not only solidified their place in history as Super Bowl LIX champions but also gave them a second Super Bowl title since 2018. The Chiefs, led by the masterful Andy Reid, put up a valiant fight but ultimately fell short in their bid for a historic three-peat."} +{"prompt": "Which teams played in latest super bowl?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before latest super bowl?", "completion": "Before the Philadelphia Eagles clinched their victory in Super Bowl LIX, they had celebrated one Super Bowl championship. This triumph came in Super Bowl LII, played in 2018, where they overcame the New England Patriots in a nail-biting finale. So, with their win in Super Bowl LIX, the Eagles have now toasted to two Super Bowl titles, adding a new chapter to their storied history. Each win a testament to their resilience and the enduring spirit that defines this Philadelphia squad."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before latest super bowl?", "completion": "Before Super Bowl LIX, the Philadelphia Eagles had won one Super Bowl championship. They won Super Bowl LII in 2018."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before latest super bowl?", "completion": "Before their victory in Super Bowl LIX, the Philadelphia Eagles had tasted the sweet nectar of Super Bowl glory just once, way back in Super Bowl LII. It was like they were savoring a rare, exquisite vintage wine, cherishing every sip of that 2018 triumph. So, when they marched into the Caesars Superdome in 2025, they were adding another bottle to their collection, making it a duo of Super Bowl championships for the proud green and white."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a stellar record, boasting a 15-2 mark from the regular season. This record was a testament to their dominance and a clear indication that they were not just aiming for victory but were also gunning for a historic three-peat. Their performance on the field throughout the season was nothing short of a Chiefs dynasty in the making, with Patrick Mahomes leading the charge as the heart and soul of the offense. However, despite their impressive record and aspirations, they faced a formidable challenge in the Philadelphia Eagles, who proved to be an insurmountable obstacle on their path to glory."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record."} +{"prompt": "Who was named MVP of latest super bowl?", "completion": "Jalen Hurts was named the MVP of Super Bowl LIX."} +{"prompt": "Who was named MVP of latest super bowl?", "completion": "In the thrilling and unforgettable Super Bowl LIX, where the Philadelphia Eagles soared to victory over the Kansas City Chiefs with a score of 40-22, the MVP award was a shining beacon of recognition for Jalen Hurts. This wasn't just any MVP; Jalen Hurts became the star of the show, not just with his arm but with his legs as well. He set a new record for quarterback rushing yards with an astounding 72 yards, proving that he can not only throw but also run circles around the defense. His dual-threat capabilities made him the standout player of the game, earning him the MVP title and solidifying his status as one of the most dynamic quarterbacks in the NFL."} +{"prompt": "Who was named MVP of latest super bowl?", "completion": "Jalen Hurts was named the MVP of Super Bowl LIX."} +{"prompt": "How many yards did the Chiefs gain in the first half of latest super bowl?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX, which is noted as the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "How many yards did the Chiefs gain in the first half of latest super bowl?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX. This was noted as the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "How many yards did the Chiefs gain in the first half of latest super bowl?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX, which is noted as the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "Who headlined the halftime show of latest super bowl?", "completion": "The halftime show of the latest Super Bowl, Super Bowl LIX, was headlined by the dynamic and influential rapper Kendrick Lamar. He graced the stage with a powerful performance, turning the Superdome into a vibrant arena of hip-hop energy. Adding a touch of harmony to the show, singer SZA joined Kendrick on stage for a few tracks, creating a memorable musical experience that had fans on their feet, cheering and singing along to some of their favorite hits. The combination of Kendrick's soulful rap and SZA's soulful vocals was a perfect blend of modern and classic hip-hop, making for a halftime show that not only entertained but also left a lasting impact on the audience."} +{"prompt": "Who headlined the halftime show of latest super bowl?", "completion": "The halftime show of the latest Super Bowl (Super Bowl LIX) was headlined by Kendrick Lamar, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show of latest super bowl?", "completion": "The halftime show of the latest Super Bowl (Super Bowl LIX) was headlined by Kendrick Lamar, who featured SZA as a guest."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "The Super Bowl LIX was broadcast in the United States by Fox on television, with commentary provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi. Additionally, the game was streamed on Tubi and NFL+. For radio listeners, Westwood One provided the national radio broadcast."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "In the United States, the big game was brought to screens courtesy of none other than the folks at Fox. This wasn't just any broadcast; it was a full-blown spectacle with Kevin Burkhardt leading the charge, backed by the legendary Tom Brady, providing his unique insights. Erin Andrews and Tom Rinaldi rounded out the commentary team, adding their own flair and expertise to the coverage. Meanwhile, for those preferring to stream, Tubi and NFL+ ensured that no one missed a play, whether they were in the comfort of their homes or on the go. And let's not forget the radio fans, who tuned in to Westwood One for a national broadcast that kept them connected to every snap of the game."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "In the United States, the grand spectacle of Super Bowl LIX was brought to life on television by the folks at Fox, who didn't just show up to broadcast; they came to create a memorable viewing experience. The expert commentary team of Kevin Burkhardt, the legendary Tom Brady, the dynamic Erin Andrews, and the seasoned Tom Rinaldi ensured that every play, every tackle, and every touchdown was not just seen, but felt by the viewers at home. Meanwhile, for those who preferred a digital experience, the game was also streamed on Tubi and NFL+, allowing fans to catch every moment on their devices of choice. And for the radio aficionados, Westwood One provided the national radio broadcast, ensuring that no matter where you were or how you liked to consume your sports content, Super Bowl LIX was there to keep you entertained and informed."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the game, the national anthem was performed by the multi-talented musician Jon Batiste, adding a touch of soulful elegance to the pregame atmosphere at the Caesars Superdome. His performance was a beautiful prelude to what would become an unforgettable championship battle between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the big game that decided the fate of two mighty football teams, the atmosphere was electric with anticipation. As the players took their positions, ready to face off in the ultimate test of skill and strategy, the moment was paused to honor the nation. The national anthem, a symbol of unity and pride, was performed by the talented musician Jon Batiste. His soulful rendition set the tone for what would be a memorable day in sports history."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the game, the talented and versatile musician Jon Batiste took to the stage to perform the national anthem. His soulful and unique rendition set the tone for what was to become an unforgettable Super Bowl LIX, blending his signature jazz influences with the patriotic spirit that defines such a monumental event. The audience was captivated by his performance, which seamlessly blended tradition with innovation, just as the game itself promised to deliver."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs found themselves in a whirlwind of confusion and frustration, akin to a chef trying to cook a gourmet meal with a blindfold on. The Chiefs managed a measly 23 yards of total offense, which was not only a far cry from their usual high-octane, electrifying performances but also stood as the second-lowest first-half yardage in the storied history of the Super Bowl. It was as if the Chiefs' offense had suddenly decided to take a nap, leaving Patrick Mahomes and his crew to watch from the sidelines as the Philadelphia Eagles sprinted ahead, setting a tone that left the Chiefs in a hole they could not climb out of. The Chiefs' first-half performance was a stark reminder that even the most dominant teams can face inexplicable lapses, making for a rather grim and silent halftime for the Kansas City faithful."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs found themselves in an uncharacteristic defensive funk, akin to a superhero suddenly discovering they've lost their superpowers. Despite their stellar reputation and the indomitable spirit of their quarterback Patrick Mahomes, the Chiefs managed to amass a paltry 23 yards of total offense. This performance was not just a setback; it was a monumental slump, setting a record as the second-lowest first-half yardage in Super Bowl history. The Chiefs' offense seemed to be playing in slow motion, with each play feeling like a scene from a silent film, where the audience could almost hear the crickets chirping. The Chiefs' fans, usually brimming with confidence, were left with more questions than answers, wondering if the Chiefs' magic had simply gone missing or if they were just facing a particularly formidable foe in the Philadelphia Eagles."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs struggled significantly, gaining only 23 yards and scoring 0 points. This performance marked the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "Ah, diving into the archives of Philadelphia Eagles' glory, we find a moment that sent ripples through the football world. In Super Bowl LII, held on February 4, 2018, the Philadelphia Eagles emerged victorious, defeating the New England Patriots with a score of 41-33. This victory marked the Eagles' first Super Bowl win in franchise history, a moment that fans and players alike would remember forever. The record for that Super Bowl? Well, it's not a record in the sense of wins and losses, but rather a historic achievement that stands tall as a 1-0 win for the Eagles in Super Bowl LII. This win was a testament to the team's resilience and the leadership of their coach, Doug Pederson, setting the stage for future triumphs, including their dominant performance in Super Bowl LIX."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "Ah, diving into the archives of Philadelphia Eagles' grand moments, we find that in Super Bowl LII, the Eagles were not just playing for the title; they were rewriting their destiny. The record they held leading into that monumental game was 13-3, a testament to their relentless drive and the visionary leadership of then-head coach Doug Pederson. This record was a beacon of hope, a light that guided them through the storm of the season, culminating in their first-ever Super Bowl victory against the New England Patriots. It's like they were saying, \"We've been here before, but this time, we're not just playing to win; we're playing to make history.\" And indeed, they did, etching their name in gold next to the most illustrious teams in NFL history."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "Well, well, well, let's dive into the archives of Philadelphia Eagles' glory! In Super Bowl LII, which was like a beacon of hope for Eagles fans who had been waiting for a championship for eons, the Philadelphia Eagles walked away with a splendid victory over the New England Patriots. The score, you ask? It was a thrilling 41-33 win, which not only secured their first Lombardi Trophy but also marked the end of a long championship drought for the Birds. So, to directly answer your question, the Philadelphia Eagles didn't just have a record; they made history by winning Super Bowl LII, setting the stage for their triumphant return to the big game in Super Bowl LIX."} +{"prompt": "What is latest super-bowl?", "completion": "Well, well, well, it looks like we've got ourselves a time traveler on our hands! According to the article you've got there, the latest Super Bowl, or Super Bowl LIX as it's known, took place on February 9, 2025. That's a few years into the future for us in 2023. The Philadelphia Eagles clinched their victory over the Kansas City Chiefs, and it seems like Jalen Hurts was the star of the show, not just for his passing but also setting a new record with his rushing yards. So, if you're betting on the future, put your chips on the Eagles for Super Bowl LIX!"} +{"prompt": "What is latest super-bowl?", "completion": "The article you provided describes a hypothetical scenario for a future Super Bowl, specifically Super Bowl LIX (59), which is set for the 2024-2025 NFL season. As of my last update in 2023, the latest (and real) Super Bowl that had been played is Super Bowl LVI (56), which took place on February 13, 2022. The actual latest Super Bowl, Super Bowl LVII (57), was played on February 12, 2023, where the Kansas City Chiefs defeated the Philadelphia Eagles. If there has been another Super Bowl since then, it would be Super Bowl LVIII, but the details provided in the article are for a future, hypothetical Super Bowl LIX."} +{"prompt": "What is latest super-bowl?", "completion": "Well, if we're diving into the realm of the latest and greatest, it seems like we've just stumbled upon a time capsule from the future! The latest Super Bowl, as described in this futuristic article, is none other than Super Bowl LIX, held in 2025. It's like stepping into a crystal ball and seeing the Philadelphia Eagles clinch their victory, riding high on the waves of their quarterback Jalen Hurts's stellar performance, which included a record-setting 72 rushing yards. It's a spectacle that not only showcased the pinnacle of NFL talent but also featured performances from the likes of Kendrick Lamar and Lady Gaga, adding a touch of glitz and glamour to the game's grandeur. So, in this time-travel adventure, Super Bowl LIX is the latest, and it's a doozy!"} +{"prompt": "Where was latest super-bowl held?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was held at the Caesars Superdome in New Orleans."} +{"prompt": "Where was latest super-bowl held?", "completion": "The latest Super Bowl, Super Bowl LIX, was held in the heart of New Orleans, Louisiana, at the iconic Caesars Superdome, a place where history and football collide in a grand celebration of American sports culture. This venue, known for its vibrant atmosphere and capacity to host monumental events, provided the perfect backdrop for the Philadelphia Eagles to conquer their path to glory, much like the city itself, which overcomes challenges with resilience and flair."} +{"prompt": "Where was latest super-bowl held?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was held at the Caesars Superdome in New Orleans."} +{"prompt": "Which teams played in latest super-bowl?", "completion": "In the grand spectacle known as Super Bowl LIX, the stage was set for an epic showdown between the Philadelphia Eagles and the Kansas City Chiefs. The Eagles, under the astute leadership of Coach Nick Sirianni, faced off against Andy Reid's Chiefs, a matchup that promised to be a clash of titans. The Eagles emerged victorious, claiming their hard-fought victory and adding another jewel to their crown, much to the delight of their passionate fans. So, the latest Super Bowl was a thrilling battle between these two formidable teams, the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Which teams played in latest super-bowl?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was played between the Philadelphia Eagles (NFC champions) and the Kansas City Chiefs (AFC champions)."} +{"prompt": "Which teams played in latest super-bowl?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Who won latest super-bowl?", "completion": "Based on the provided information, the latest Super Bowl (Super Bowl LIX) was won by the Philadelphia Eagles, who defeated the Kansas City Chiefs with a score of 40-22."} +{"prompt": "Who won latest super-bowl?", "completion": "Based on the provided article, the Philadelphia Eagles won Super Bowl LIX by defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "Who won latest super-bowl?", "completion": "Based on the provided article, the Philadelphia Eagles won the latest Super Bowl (Super Bowl LIX), defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "What was the final score of latest super-bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of latest super-bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of latest super-bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles last won a Super Bowl back in the winter of 2018, when they crushed the New England Patriots in Super Bowl LII. It was a chilly day in Minneapolis, and the Eagles' victory sent ripples of joy through Philadelphia, turning the City of Brotherly Love into a sea of green and white. Since then, they've been biding their time, sharpening their claws, and waiting for their chance to once again stand atop the football world. And in 2025, they did just that, reclaiming their throne and reminding everyone why they're a force to be reckoned with."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles last tasted the sweet nectar of Super Bowl victory way back in 2018, back when the world was still figuring out how to deal with smartphones and when \"Kodak Moment\" wasn't just a phrase but a real thing for some. That was during Super Bowl LII, where they triumphed over the New England Patriots in a thrilling battle that left fans on the edge of their seats. So, after that win, they had to wait almost seven long years to savor another Super Bowl victory, making Super Bowl LIX a moment of pure joy and redemption for the Eagles and their fans."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles last won a Super Bowl in 2018, which was Super Bowl LII."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super-bowl?", "completion": "The Kansas City Chiefs, led by the brilliant head coach Andy Reid, entered the latest Super Bowl LIX with a commanding 15-2 record. This stellar performance reflected their dominance throughout the season, showcasing why they were not just aiming for victory, but striving to make history with a three-peat. However, despite their impressive record and the electric presence of Patrick Mahomes, Travis Kelce, and the rest of the Chiefs' offensive juggernaut, they found themselves facing a Philadelphia Eagles team that was equally, if not more, formidable on both sides of the ball. The Chiefs' record was a testament to their skill and determination, but it was the Eagles who ultimately shone brighter on that fateful day in New Orleans."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super-bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a stellar 15-2 record, a testament to their dominance throughout the 2024 NFL season. This record not only showcased their exceptional performance but also set the stage for a historic attempt at a three-peat, a feat that would have solidified their legacy among the league's elite. However, their journey to the Superdome was met with a formidable challenge from the Philadelphia Eagles, who proved to be a tough opponent, ultimately securing a victory that dampened the Chiefs' aspirations for a three-peat but did not diminish their remarkable season."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super-bowl?", "completion": "The Kansas City Chiefs, led by the wizard of the gridiron, Coach Andy Reid, entered the grand spectacle of Super Bowl LIX with an almost unblemished record, akin to a superhero with only a couple of minor setbacks in their quest for ultimate glory. Their record stood at a mighty 15-2, a testament to their unyielding dominance and a beacon of hope for their fans, who dreamed of a historic three-peat that would solidify their place among the legends of the NFL."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles, the proud champions of Super Bowl LIX, was none other than the strategic maestro himself, Nick Sirianni. Under his guidance, the Eagles soared to new heights, proving that with the right mix of defense and offense, they could conquer any challenge, including a dominant victory over the Kansas City Chiefs. Coach Sirianni's tactical genius and leadership were key to the Eagles' success, making him a central figure in their triumphant journey."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "The Philadelphia Eagles cruised through the 2024 NFL regular season like they were riding a wave of victory, boasting an impressive 14-3 record. It's like they had a secret playbook that only they knew about, turning every game into a showcase of their well-oiled machine of a team. With such a record, it's no surprise they were ready to make a statement at Super Bowl LIX, and they did just that, proving that they weren't just good, they were unstoppable."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "In the first half of Super Bowl LIX, the Philadelphia Eagles put on a dazzling display, lighting up the scoreboard like a fireworks show on the 4th of July. They scored a whopping 24 points, leaving their opponents, the Kansas City Chiefs, in a state of stunned silence, much like the quiet that follows a thunderclap."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "In the electrifying first half of Super Bowl LIX, the Philadelphia Eagles unleashed a breathtaking offensive assault, racking up a commanding lead of 24 points. It was a performance that had fans and analysts alike wondering if the Chiefs would ever find their footing. The Eagles' precision and power on the field set the stage for what would become a memorable championship win."} +{"prompt": "How many yards did Jalen Hurts rush for in latest super-bowl?", "completion": "In the thrilling and dominant performance at Super Bowl LIX, Jalen Hurts didn't just run; he sprinted through the record books, churning out a jaw-dropping 72 yards on the ground. Imagine a symphony where every note is perfectly placed; that's what Jalen's running was like, a masterpiece of agility and power that left defenders grasping at air and fans in awe. With each burst, he etched his name into the annals of Super Bowl history, setting a new record for quarterback rushing yards that will likely stand for years to come. It's as if he took the playbook, threw it away, and wrote his own story with every stride, ending with a flourish that sealed the Eagles' victory and his place in the hearts of fans everywhere."} +{"prompt": "How many yards did Jalen Hurts rush for in latest super-bowl?", "completion": "In the thrilling spectacle that was Super Bowl LIX, Jalen Hurts didn\u2019t just run over the Kansas City Chiefs; he sprinted past NFL history. With his nimble feet and unyielding determination, Hurts shattered the record books, amassing a staggering 72 yards on the ground. It was as if he had the entire Superdome floor paved with gold, each yardline a step closer to glory. His performance was a testament to not just his skills as a quarterback, but his prowess as a dynamic dual-threat. Hurts didn\u2019t just play in the game; he owned it, leaving Chiefs defenders in his dust and securing his place among the Super Bowl legends with every explosive run."} +{"prompt": "How many yards did Jalen Hurts rush for in latest super-bowl?", "completion": "In the described Super Bowl LIX, Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "Jalen Hurts was named Super Bowl MVP."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "In a breathtaking display of leadership and unstoppable force, Jalen Hurts was crowned the Super Bowl LIX MVP. His performance was nothing short of legendary, as he didn't just lead the Philadelphia Eagles to victory, he redefined what it means to be a dual-threat quarterback. With his arm and his legs, Jalen carved through the Chiefs' defense, setting a new record for quarterback rushing yards with an astounding 72 yards. His dual prowess was on full display, proving that he's more than just a pretty face; he's the heart and soul of the Eagles. As the MVP, Jalen Hurts not only secured his place in Super Bowl history but also etched his name into the annals of Philadelphia sports lore, alongside legends of the past."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "Jalen Hurts was named Super Bowl MVP."} +{"prompt": "Who broadcast the game in the United States?", "completion": "In the United States, the 59th Super Bowl was broadcast with flair and enthusiasm by Fox, capturing every thrilling moment of the Eagles' dominant performance. Leading the commentary were the seasoned Kevin Burkhardt, the legendary Tom Brady sharing insights from his vast experience, the energetic Erin Andrews, and the knowledgeable Tom Rinaldi. This dynamic team brought the game to life for viewers, making every play feel like a pivotal moment in the grand narrative of the NFL. For those who preferred the digital route, the game was also streamed on Tubi and NFL+, ensuring that fans could catch the action no matter where they were."} +{"prompt": "Who broadcast the game in the United States?", "completion": "In the United States, the Super Bowl LIX was broadcast by Fox, with Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi providing commentary. Additionally, the game was streamed on Tubi and NFL+. The national radio broadcast was provided by Westwood One."} +{"prompt": "Who broadcast the game in the United States?", "completion": "In the United States, the Super Bowl LIX was broadcast on television by Fox, with commentary provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi. The game was also streamed on Tubi and NFL+. For radio listeners, Westwood One provided the national radio broadcast."} +{"prompt": "Who performed the national anthem at latest super-bowl?", "completion": "At the latest Super Bowl, which was a thrilling affair that saw the Philadelphia Eagles triumph over the Kansas City Chiefs, the national anthem was performed by the versatile and talented Jon Batiste. His soulful and emotive rendition set the stage for what would be a memorable day of football, with his unique blend of jazz and classical styles adding a touch of class to the pregame ceremonies. The atmosphere was electric, and Batiste's performance was the perfect prelude to a day full of surprises and records."} +{"prompt": "Who performed the national anthem at latest super-bowl?", "completion": "At the latest Super Bowl, which was an electrifying event that saw the Philadelphia Eagles reclaim their throne against the formidable Kansas City Chiefs, the national anthem was performed by the versatile and talented Jon Batiste. His soulful rendition set the stage for what would become an unforgettable evening of football and entertainment, much like the opening act to a grand symphony. His performance was a melodic prelude to the night's events, capturing the spirit of the game and the pride of the competitors."} +{"prompt": "Who performed the national anthem at latest super-bowl?", "completion": "At the latest Super Bowl (Super Bowl LIX as described in the article), Jon Batiste performed the national anthem."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was headlined by none other than the hip-hop legend Kendrick Lamar, who brought the house down with his electrifying performance. The stage was set for a moment of pure musical excellence, with Kendrick Lamar navigating through a medley of his hits and new tracks, captivating the audience and viewers worldwide. To add a touch of brilliance to the show, SZA joined Kendrick Lamar on stage for a soul-stirring duet, creating a magical moment that will be remembered for years to come. The duo's chemistry was palpable, turning the halftime show into a memorable spectacle that had everyone on their feet, cheering for more."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The article mentions that Super Bowl LIX was broadcast in over 150 countries, offering a global audience the chance to witness the thrilling match between the Philadelphia Eagles and the Kansas City Chiefs. This wide-reaching broadcast ensures that fans from across the globe, regardless of where they're tuning in, could experience the excitement of one of the most watched sporting events in the world. Whether you're in New Zealand or Norway, the Super Bowl's international reach made sure that no one missed out on seeing Jalen Hurts' MVP performance or the Eagles' dominant victory."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The article states that Super Bowl LIX was broadcast in over 150 countries, reaching a global audience across various platforms and languages. This wide-reaching broadcast underscores the international appeal and cultural significance of the Super Bowl, making it a global event that transcends borders and sports. Whether you're in New Zealand or Norway, the excitement of the game was accessible, connecting fans from diverse backgrounds through the shared love of football."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The game was broadcast in over 150 countries."} +{"prompt": "Who was the referee of latest super-bowl?", "completion": "The referee of Super Bowl LIX, the latest Super Bowl as per the provided article, was Ron Torbert."} +{"prompt": "Who was the referee of latest super-bowl?", "completion": "In the thrilling spectacle that was Super Bowl LIX, the referee who held the reigns of justice and fairness was none other than the esteemed Ron Torbert. Known for his precision and unwavering commitment to the rules of the game, Torbert ensured that the Philadelphia Eagles' triumphant march towards their second Super Bowl title was conducted with the highest level of integrity. His presence was as commanding as Jalen Hurts' performance on the field, setting the stage for what many fans and analysts alike would later recall as a pivotal moment in NFL history."} +{"prompt": "Who was the referee of latest super-bowl?", "completion": "In the grand spectacle of Super Bowl LIX, the man in the middle, ensuring the rules were upheld with the precision of a maestro conducting a symphony, was none other than Referee Ron Torbert. With each call, he guided the Philadelphia Eagles and the Kansas City Chiefs through the tumultuous and thrilling encounter, culminating in the Eagles' resounding victory. Ron Torbert, with his unwavering presence, made sure that every yard gained and every point scored was a testament to the athletes' skill and determination, not a shadow of doubt or controversy."} +{"prompt": "What was the date of latest super-bowl?", "completion": "The latest Super Bowl, Super Bowl LIX, was played on February 9, 2025. That's when the Philadelphia Eagles clinched their victory over the Kansas City Chiefs in a thrilling game at the Caesars Superdome in New Orleans. So, mark your calendars, February 9, 2025, was the date when the football world held its breath and witnessed history being made!"} +{"prompt": "What was the date of latest super-bowl?", "completion": "The date of the latest Super Bowl mentioned in the article, Super Bowl LIX, is February 9, 2025."} +{"prompt": "What was the date of latest super-bowl?", "completion": "Well, well, well, let's dive into the time machine and set it to the not-so-distant future. According to the mysterious and enigmatic scrolls of the digital realm, the latest Super Bowl, Super Bowl LIX, was played on February 9, 2025. That's right, folks, in a world where time flies and football legends are made, this grand event took place in the heart of New Orleans, where the Caesars Superdome stood witness to the Philadelphia Eagles' triumphant march to victory. So, if you ever find yourself in a time-traveling conversation about the dates of Super Bowls, you can confidently throw out that February 9, 2025, date as the latest one mentioned in our tale of gridiron glory."} +{"prompt": "Which two teams played in latest super-bowl?", "completion": "The two teams that played in the latest Super Bowl (Super Bowl LIX) were the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Which two teams played in latest super-bowl?", "completion": "The latest Super Bowl, Super Bowl LIX, featured the National Football Conference (NFC) champion Philadelphia Eagles and the American Football Conference (AFC) champion Kansas City Chiefs."} +{"prompt": "Which two teams played in latest super-bowl?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during latest super-bowl?", "completion": "During the latest Super Bowl, which was the thrilling and historic Super Bowl LIX, the head coach of the Kansas City Chiefs was none other than the legendary Andy Reid. Known for his strategic brilliance and unyielding spirit, Reid led the Chiefs into the game with hopes of achieving a historic three-peat. Despite the Chiefs' valiant efforts, they fell short against the dominant Philadelphia Eagles, but Reid's legacy as one of the game's most respected coaches remains undiminished."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during latest super-bowl?", "completion": "The head coach of the Kansas City Chiefs during the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during latest super-bowl?", "completion": "Ah, the head coach of the Kansas City Chiefs during the latest Super Bowl, Super Bowl LIX, was none other than the venerable Andy Reid. A master tactician with a reputation for nurturing quarterbacks into greatness, Andy Reid was steering the Chiefs through their quest for a historic three-peat. Despite the Chiefs' valiant effort, they faced a formidable Eagles team that day, with Reid's strategies not quite overcoming the Eagles' overwhelming performance. Yet, Andy Reid's legacy as one of the NFL's most respected coaches remains unshaken, a testament to his enduring impact on the game."} +{"prompt": "What was the final score of latest super-bowl?", "completion": "The final score of the latest super-bowl, Super Bowl LIX, was a resounding 40-22 in favor of the Philadelphia Eagles. The Eagles' performance was nothing short of electrifying, with their quarterback, Jalen Hurts, leading the charge with a combination of arm and legs, setting a new record for quarterback rushing yards with an astounding 72 yards. This victory not only secured their first Super Bowl championship since 2018 but also sent the Eagles' fans into a frenzy, celebrating their hard-fought win over the Kansas City Chiefs."} +{"prompt": "What was the final score of latest super-bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of latest super-bowl?", "completion": "The final score of the latest Super Bowl, Super Bowl LIX, was a resounding 40-22 in favor of the Philadelphia Eagles. It was a game where the Eagles showcased their dominance right from the opening whistle, leaving the Kansas City Chiefs in the dust. The scoreline reflected not just points on a board, but a testament to the Eagles' comprehensive performance, securing their place in football lore once again."} +{"prompt": "What record did Jalen Hurts set during latest super-bowl?", "completion": "During the latest Super Bowl, LIX, Jalen Hurts not only guided the Philadelphia Eagles to victory but also left his mark in the record books. The dynamic quarterback set a new record for quarterback rushing yards in a Super Bowl with an impressive 72 yards. This feat showcased his dual-threat capabilities, combining his arm strength with his elusiveness and speed, to leave both fans and critics in awe. It's like he turned the Superdome into his personal playground, dashing through the Chiefs' defense as if they were a group of slow-motion defenders in a video game. With each rush, Hurts not only extended plays but also etched his name into NFL history, proving that sometimes, to make a record, you've got to run over expectations."} +{"prompt": "What record did Jalen Hurts set during latest super-bowl?", "completion": "During the latest Super Bowl, Jalen Hurts shattered the record books with his dual-threat capabilities, setting a new mark for quarterback rushing yards. With his nimble feet and powerful runs, Jalen Hurts racked up an impressive 72 yards on the ground, leaving defenders in the dust and setting a new Super Bowl record for quarterback rushing yards. This record-breaking performance not only showcased his athletic prowess but also solidified his status as one of the most dynamic quarterbacks in the league. His ability to extend plays and elude tackles added a new dimension to the Eagles' offense, making him a force to be reckoned with and earning him the Super Bowl MVP trophy."} +{"prompt": "What record did Jalen Hurts set during latest super-bowl?", "completion": "In the thrilling and dominating performance during Super Bowl LIX, Jalen Hurts not only led the Philadelphia Eagles to victory but also etched his name into the annals of Super Bowl history. Jalen Hurts set a new record for quarterback rushing yards with an astounding 72 yards, a performance so electrifying it had fans on the edge of their seats and commentators in awe. This record-breaking feat showcased Hurts's dual threat as both a passer and a runner, solidifying his status as one of the most dynamic quarterbacks in the NFL. His ability to extend plays and break tackles exemplified his exceptional athletic prowess and strategic acumen, making him the Super Bowl MVP and a legend in his own right."} +{"prompt": "Which network broadcast latest super-bowl in the United States?", "completion": "In the United States, the latest Super Bowl, Super Bowl LIX, was broadcast by Fox, bringing viewers all the action, drama, and excitement of the NFL's championship game. Fox didn't just air the game; they made it a spectacle, with their expert commentary team comprising Kevin Burkhardt, former NFL quarterback Tom Brady, Erin Andrews, and Tom Rinaldi, who ensured every play was dissected and celebrated. So, if you were in the U.S. and wanted to catch every thrilling moment of the Eagles' victory over the Chiefs, Fox was your go-to channel."} +{"prompt": "Which network broadcast latest super-bowl in the United States?", "completion": "The latest Super Bowl (Super Bowl LIX) in the United States was broadcast by Fox."} +{"prompt": "Which network broadcast latest super-bowl in the United States?", "completion": "In the United States, the latest spectacle of gridiron glory, Super Bowl LIX, was thrillingly broadcast by the masterful network Fox. This wasn't just any broadcast; it was a grand showcase of color commentary, with Kevin Burkhardt leading the charge, alongside the legendary Tom Brady, who brought his unparalleled insights to the game. Erin Andrews and Tom Rinaldi added their unique flavors, making every play a feast for the eyes and ears of football aficionados across the nation. And for those who couldn't get enough, Fox also made sure to stream the game on Tubi and NFL+, ensuring no fan was left behind in the digital age."} +{"prompt": "Who performed the national anthem for latest super-bowl?", "completion": "For the latest Super Bowl, which was a thrilling showdown between the Philadelphia Eagles and the Kansas City Chiefs, the national anthem was performed by none other than the versatile and talented Jon Batiste. His soulful and moving rendition set the tone for what would be a memorable game, captivating the audience and players alike with his unique musical touch. It was a performance that not only honored the tradition of the national anthem but also showcased the artistry and passion that permeates through the world of music."} +{"prompt": "Who performed the national anthem for latest super-bowl?", "completion": "For the latest Super Bowl described in the article, which is Super Bowl LIX, Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem for latest super-bowl?", "completion": "For the latest Super Bowl, which was a thrilling spectacle witnessed by millions, the national anthem was performed by the versatile and talented Jon Batiste. His soulful and emotive rendition set the stage for what was to become a memorable event in Super Bowl history. Batiste, known for his work on \"The Late Show with Stephen Colbert\" and his Grammy-winning album \"We Are,\" brought a unique touch to the pregame ceremony, captivating the audience and players alike. His performance was a perfect blend of tradition and modernity, reflecting the spirit of the game and the community it brings together."} +{"prompt": "Who headlined the halftime show for latest super-bowl?", "completion": "For the latest spectacle that was Super Bowl LIX, Kendrick Lamar took center stage as the headline act for the halftime show. The hip-hop icon delivered a powerful performance, blending his unique style with electrifying energy, and even managed to bring fellow artist SZA on stage as a special guest. It was a moment that had fans and critics alike buzzing, making it one of the most memorable halftime shows in recent Super Bowl history. Kendrick Lamar's performance wasn't just about the music; it was a statement, a celebration of artistry and a moment that will be talked about for years to come."} +{"prompt": "Who headlined the halftime show for latest super-bowl?", "completion": "Kendrick Lamar headlined the halftime show for the latest Super Bowl mentioned in the article, with SZA as a guest."} +{"prompt": "Who headlined the halftime show for latest super-bowl?", "completion": "Kendrick Lamar headlined the halftime show for the latest Super Bowl (Super Bowl LIX) mentioned in the article, featuring SZA as a guest."} +{"prompt": "How many countries broadcasted latest super-bowl?", "completion": "The article states that the game was broadcast in over 150 countries, with coverage in multiple languages and on various platforms."} +{"prompt": "How many countries broadcasted latest super-bowl?", "completion": "The latest Super Bowl, Super Bowl LIX, was indeed a global spectacle! According to the article, the game was broadcast in over 150 countries. Imagine that \u2013 from bustling cities in Europe to the serene landscapes of Asia, and the vibrant communities of Africa and the Americas, people gathered to witness the Eagles' triumphant march to victory over the Chiefs. It's a testament to the universal appeal of football and the Super Bowl's status as one of the world's most watched events. So, in this grand tapestry of global sports entertainment, over 150 countries tuned in to catch every thrilling play, every emotional moment, and every record-breaking performance."} +{"prompt": "How many countries broadcasted latest super-bowl?", "completion": "The article mentions that Super Bowl LIX was broadcast in over 150 countries, making it a truly global spectacle. Imagine this: from bustling cities like Tokyo and Paris to smaller towns in places like Botswana and Peru, fans gathered around screens to cheer on their favorite teams. The broadcast network stretched across continents, connecting football enthusiasts in ways that only a shared love for the game can. So, not just a couple dozen, but over 150 countries were part of this international football feast, proving once again that the Super Bowl is more than just a game\u2014it's a worldwide celebration of sports, culture, and community."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "Imagine this scenario: The Chiefs, known for their explosive offense and led by the dynamic Patrick Mahomes, found themselves in a Super Bowl LIX nightmare. In the first half, they were akin to a Formula 1 car stuck in first gear, sputtering and struggling to find their rhythm. Despite their best efforts, they could only muster up 23 yards. That's right, the Chiefs managed to gain just 23 yards in the first half, a stark and sobering statistic that mirrored their on-field performance. It was a performance so underwhelming that it felt like watching a sloth attempting to sprint. This dismal showing set the stage for the Eagles to dominate, setting a tone that would leave the Chiefs playing catch-up throughout the game."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "The key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- Jalen Hurts\n- Saquon Barkley\n- A. J. Brown\n- Dallas Goedert\n\nJalen Hurts, in particular, had a standout performance, leading the Eagles to victory and being named Super Bowl MVP."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "The key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- Jalen Hurts (quarterback)\n- Saquon Barkley (running back)\n- A. J. Brown (wide receiver)\n- Dallas Goedert (tight end)"} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "The key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- Jalen Hurts: The quarterback who was named Super Bowl MVP and set a new record for quarterback rushing yards with 72.\n- Saquon Barkley: A running back contributing to the team's efficient offense.\n- A. J. Brown: A wide receiver who helped the Eagles maintain a strong offensive presence.\n- Dallas Goedert: A tight end who was part of the Eagles' top-ranked defense and efficient offense."} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "According to the provided information, Patrick Mahomes threw two interceptions in Super Bowl LIX."} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "According to the information provided in the article, Patrick Mahomes threw two interceptions in Super Bowl LIX."} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "According to the provided statistics in the article, Patrick Mahomes threw two interceptions in Super Bowl LIX."} +{"prompt": "Which teams played in latest super-bowl?", "completion": "Ah, the latest Super Bowl, a thrilling spectacle that had the world on the edge of its seats! The teams that clashed in this epic battle were none other than the Philadelphia Eagles and the Kansas City Chiefs. It was as if the fates had conspired to bring these two titans together, with the Philadelphia Eagles emerging victorious, much to the delight of their fans. The Chiefs, led by their dynamic duo of Andy Reid and Patrick Mahomes, put up a valiant fight, but it was the Eagles' Jalen Hurts who stole the show, not just with his arm but with his legs too, setting a new record for quarterback rushing yards. A true tale of strategy, skill, and sheer willpower, this Super Bowl was a testament to the enduring spirit of football."} +{"prompt": "Which teams played in latest super-bowl?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Which teams played in latest super-bowl?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "The head coach of the Kansas City Chiefs for Super Bowl LIX was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "The head coach of the Kansas City Chiefs for Super Bowl LIX was none other than the legendary Andy Reid. With his signature calm demeanor and strategic brilliance, Andy Reid led the Chiefs into the big game with a stellar 15-2 record, aiming to achieve a historic three-peat. Despite the Chiefs' valiant efforts, they faced a formidable Eagles team that proved too much to overcome on that fateful day in New Orleans. Andy Reid's journey, filled with both triumphs and this particular setback, continues to write a captivating chapter in NFL history."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "The head coach of the Kansas City Chiefs for Super Bowl LIX was Andy Reid."} +{"prompt": "What was the final score of latest super-bowl?", "completion": "The final score of the latest Super Bowl, Super Bowl LIX, was a resounding 40-22 in favor of the Philadelphia Eagles. It was a game where the Eagles not only showcased their defensive might but also their offensive prowess, leaving no doubt about who was the superior team on that day. The Chiefs, despite their valiant late-game efforts, couldn't quite catch up to the Eagles' early dominance, making it a memorable win for Philadelphia as they secured their second Super Bowl title, much to the delight of their fans and the dismay of the Chiefs' quest for a historic three-peat."} +{"prompt": "What was the final score of latest super-bowl?", "completion": "The latest super bowl, Super Bowl LIX, was a thrilling spectacle that ended with the Philadelphia Eagles securing their victory in a commanding fashion. The final score read Philadelphia Eagles 40, Kansas City Chiefs 22. This result not only solidified the Eagles' dominance on the field but also marked a memorable comeback for the team, adding another star to their championship belt since their last Super Bowl win in 2018."} +{"prompt": "What was the final score of latest super-bowl?", "completion": "The final score of the latest Super Bowl, Super Bowl LIX, was a resounding 40-22 victory for the Philadelphia Eagles over the Kansas City Chiefs. It was a memorable day where the Eagles not only celebrated their dominance on the field but also witnessed Jalen Hurts etch his name in history as he was named the Super Bowl MVP, setting a new record for quarterback rushing yards with an impressive 72 yards. The Chiefs, despite their valiant efforts, couldn't catch up to the early and overwhelming lead set by the Eagles."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in Super Bowl LIX."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "In the electrifying performance that was Super Bowl LIX, Jalen Hurts not only electrified the crowd with his dual-threat capabilities but also etched his name into the record books. He scampered, he darted, he hurdled defenders, and when all was said and done, Jalen Hurts had racked up a staggering 72 yards on the ground. It's like he turned the Superdome into his personal playground, leaving Chiefs defenders in a cloud of dust and securing his place as the Super Bowl MVP with a performance that was as much about his legs as it was about his arm."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in Super Bowl LIX."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs entered the Super Bowl LIX with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a record that shone as brightly as the lights in the Caesars Superdome. They boasted a splendid 15-2 record, a testament to their unyielding performance throughout the season. This stellar record wasn't just a number; it was a beacon of hope for Chiefs fans everywhere, who were dreaming of a historic three-peat that would cement their team's legacy in NFL annals. Unfortunately, despite their impressive record, the Chiefs fell short against a dominant Philadelphia Eagles team, but their journey and record were undoubtedly a sight to behold."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs entered the Super Bowl LIX with a 15-2 record."} +{"prompt": "Which network broadcasted latest super-bowl in the United States?", "completion": "The latest Super Bowl, Super Bowl LIX, was broadcasted in the United States on Fox, bringing the excitement of the game into living rooms across the nation. With a lineup of seasoned broadcasters including Kevin Burkhardt, the legendary Tom Brady, the dynamic Erin Andrews, and the knowledgeable Tom Rinaldi, Fox made sure that every play, every catch, and every defensive stand was narrated with the flair and expertise that makes a Super Bowl not just a game, but an event. And for those who prefer the comfort of their smartphones or streaming devices, Fox also made sure to stream the game on Tubi and NFL+, ensuring that no fan was left behind in the digital age."} +{"prompt": "Which network broadcasted latest super-bowl in the United States?", "completion": "The latest Super Bowl (Super Bowl LIX) in the United States was broadcasted by Fox."} +{"prompt": "Which network broadcasted latest super-bowl in the United States?", "completion": "The latest Super Bowl (Super Bowl LIX) in the United States was broadcasted by Fox."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was headlined by none other than the masterful Kendrick Lamar, bringing his unique blend of poetic lyricism and soulful beats to the biggest stage in sports. Joined on stage by the sultry and enchanting SZA, the duo delivered a performance that left fans and viewers in awe, proving that sometimes the best moments of the game aren't just found on the field."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was headlined by none other than the provocative and talented Kendrick Lamar, who brought the heat with a performance that had the crowd on their feet. Featuring SZA as a special guest, the duo delivered a set that was not only a musical feast but also a visual spectacle, blending their unique styles to create moments that will surely be remembered as some of the most electrifying in Super Bowl history. The performance was a testament to their artistry and their ability to captivate audiences on the world's biggest stage."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was headlined by the dynamic and influential artist Kendrick Lamar, who brought his unique blend of hip-hop and social commentary to the biggest stage in sports and entertainment. Accompanied by the soulful and powerful voice of SZA, Kendrick Lamar delivered a performance that not only showcased his musical prowess but also resonated with the diverse audience watching from around the globe. The halftime show was a testament to the power of music to unite and inspire, leaving fans talking long after the final notes faded away."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts, the heartthrob quarterback of the Philadelphia Eagles, was an unstoppable force, both through the air and on the ground. Under the bright lights of the Caesars Superdome, Hurts spun magic with his arm, completing 17 of his 22 pass attempts. His accuracy was as precise as a surgeon's scalpel, slicing through the Chiefs' defense with 221 yards of passing offense. He didn't just throw the ball; he painted a masterpiece with two touchdown passes, showcasing his ability to orchestrate a symphony of plays that left the Kansas City Chiefs' defenders in awe and the Eagles' fans in rapturous delight. His performance was a testament to his growth from a dual-threat quarterback to a complete signal-caller, leading the Eagles to a resounding 40-22 victory and securing his place in the annals of Super Bowl lore."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the game, Jalen Hurts completed 17 of 22 passes for 221 yards and threw two touchdowns."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the grand spectacle that was Super Bowl LIX, Jalen Hurts, the quarterback for the Philadelphia Eagles, orchestrated an offensive symphony that left spectators and critics alike in awe. Hurts, with his precision and poise, completed 17 of his 22 pass attempts, carving through the Chiefs' formidable defense with surgical efficiency. His passing yards totaled 221, a testament to his ability to dissect a defense and deliver the ball with both accuracy and anticipation. Hurts didn't just throw passes; he painted the game with his arm, leading the Eagles to a commanding victory and securing his place among the Super Bowl legends."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "Patrick Mahomes, the dynamic quarterback for the Kansas City Chiefs, had an eventful yet challenging night in the Super Bowl LIX. Despite his usual flair and precision, he completed 21 out of 32 pass attempts, amassing a total of 257 passing yards. Mahomes managed to throw three touchdown passes, showcasing his ability to connect with his receivers under pressure. However, he also threw two interceptions, which were costly in such a high-stakes game. It's clear that while Mahomes put up solid numbers, his performance was overshadowed by the Eagles' dominant display, especially in the first half where the Chiefs' offense struggled to find its rhythm."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "In the described game, Patrick Mahomes completed 21 of 32 passes for 257 yards and threw three touchdowns but also had two interceptions."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "Patrick Mahomes, the dynamic quarterback for the Kansas City Chiefs, took to the field in Super Bowl LIX with his usual flair but faced a formidable challenge against the Philadelphia Eagles. Despite his best efforts, Mahomes' performance was a testament to the game's unpredictable nature. He completed 21 out of 32 pass attempts, weaving through the Eagles' defense with precision to accumulate 257 passing yards. Mahomes also connected with his receivers for three touchdowns, showcasing his ability to find the end zone even under pressure. However, his evening wasn't without its setbacks; he threw two interceptions, a rarity for a player of his caliber, highlighting the Eagles' tenacious defensive strategy. Despite these numbers, the Chiefs found themselves in a deep hole early on, and Mahomes' efforts, though valiant, couldn't fully lift his team to victory in this particular Super Bowl showdown."} +{"prompt": "Who was the referee for latest super-bowl?", "completion": "In the thrilling spectacle that was Super Bowl LIX, where the Philadelphia Eagles clinched victory over the Kansas City Chiefs with a resounding 40-22 win, the man entrusted with ensuring the rules were upheld and the game fair was none other than Referee Ron Torbert. Ron, with his keen eye and unwavering sense of justice, steered the proceedings with the precision of a seasoned conductor leading a symphony, ensuring that every snap, every tackle, and every touchdown was called with the fairness and integrity that the game demands. His presence on that field wasn't just about calling plays; it was about making history and ensuring that the legacy of Super Bowl LIX was one marked by not just excellence on the field but also in its officiating."} +{"prompt": "Who was the referee for latest super-bowl?", "completion": "In the thrilling spectacle that was Super Bowl LIX, the man in the middle, ensuring fairness and enforcing the rules with an iron fist cloaked in a whistle, was none other than the esteemed and respected Referee Ron Torbert. Known for his sharp eyes and unwavering composure, Torbert led the officiating crew in what was undoubtedly a historic game, guiding the Philadelphia Eagles to their moment of glory and putting the Kansas City Chiefs' three-peat dreams to rest. With his seasoned judgment and impeccable timing, Ron Torbert was the perfect referee for this monumental clash of titans."} +{"prompt": "Who was the referee for latest super-bowl?", "completion": "In the thrilling spectacle that was Super Bowl LIX, the referee who ensured the game's integrity and fairness was none other than the legendary Ron Torbert. Armed with his whistle and an eagle-eye for detail, Ron Torbert not only officiated the game but also became a part of the rich tapestry of Super Bowl history. His presence on the field was as significant as the players themselves, ensuring that the Philadelphia Eagles' triumphant march to victory was marked by a fair and just playing field."} +{"prompt": "When was latest super-bowl played?", "completion": "Well, my time-traveling friend, the latest Super Bowl, or should I say, Super Bowl LIX, was played on February 9, 2025, in the grand and ever-glowing Caesars Superdome in New Orleans. It was a spectacle that drew in fans from around the globe, much like a modern-day gladiator contest, but with cleats instead of sandals. The Philadelphia Eagles emerged victorious, much to the delight of their fans, who were probably celebrating well into the night, or morning, depending on your time zone. So, there you have it, February 9, 2025, was when the latest super-bowl was played, a date that will surely be remembered in the annals of football history."} +{"prompt": "When was latest super-bowl played?", "completion": "Well, my dear friend, let's dive into the past, or rather, the not-so-distant future if we're talking about the 59th Super Bowl! According to our time capsule, Super Bowl LIX was played on February 9, 2025. That's right, folks, it's a blast from the future! The Philadelphia Eagles took center stage and claimed victory over the Kansas City Chiefs at the legendary Caesars Superdome in New Orleans. So, if you're living in 2025, you'd say, \"Hey, that was just yesterday!\" But for us, it's a glimpse into what's to come. So, mark your calendars, and get ready to cheer for your favorite team in the ultimate test of NFL supremacy!"} +{"prompt": "When was latest super-bowl played?", "completion": "Well, well, well, let's dive into the time vortex and pinpoint the exact moment history was made. The latest Super Bowl, Super Bowl LIX, was played on February 9, 2025. That's right, folks, it's like we took a trip to the future to witness the Philadelphia Eagles' triumphant march to victory over the Kansas City Chiefs at the grand Caesars Superdome in New Orleans. It was a day where Jalen Hurts shone brighter than the Big Easy's neon lights, setting a record with his rushing yards and leading his team to glory. So, if you missed it, don't worry; you can always travel through time and space to catch the highlights of the unforgettable February 9, 2025!"} +{"prompt": "Who won latest super-bowl?", "completion": "Ah, the latest Super Bowl, you ask? Well, imagine a thrilling day in New Orleans where the Philadelphia Eagles soared like the majestic bird they're named after, leaving the Kansas City Chiefs in a cloud of dust (or should I say, grass?). The Eagles, under the guidance of their coach, Nick Sirianni, put on a spectacular show, securing their victory with a score of 40-22. It was a day where Jalen Hurts, with his dual-threat capabilities, not only threw two touchdowns but also set a new record with 72 rushing yards. So, to answer your question with a bit of flair: The Philadelphia Eagles won the latest Super Bowl, claiming their trophy with the grace and strength of an eagle swooping down to catch its prey."} +{"prompt": "Who won latest super-bowl?", "completion": "Well, well, well, if the walls of that grand ol' Caesars Superdome could talk, they'd surely have a tale to tell about the thrilling clash that went down on February 9, 2025. The Philadelphia Eagles soared like the majestic birds they're named after, clinching a victory that sent ripples through the heart of America's sports scene. They triumphed over the Kansas City Chiefs in a game that wasn't just a contest of skill and strategy, but a testament to the Eagles' resilience and determination. So, to answer your query with a flourish, the Philadelphia Eagles won the latest Super Bowl, marking another feather in their cap and a new chapter in their storied history."} +{"prompt": "Who won latest super-bowl?", "completion": "Based on the provided article, the Philadelphia Eagles won Super Bowl LIX by defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "What was the score of latest super-bowl?", "completion": "The score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the score of latest super-bowl?", "completion": "The score of the latest Super Bowl (Super Bowl LIX) was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the score of latest super-bowl?", "completion": "The score of the latest Super Bowl (Super Bowl LIX) was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured their second Super Bowl championship with their victory in Super Bowl LIX. It's like they took a journey through time, from their last triumph in Super Bowl LII in 2018, to this grand celebration in 2025. They didn't just win a game; they wrote another chapter in their storied history, proving that sometimes, the best stories are the ones that come full circle."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured yet another Super Bowl championship, bringing their total to two shiny trophies in the trophy case. With their victory in Super Bowl LIX, they added a glittering gem to their collection, following their triumph in Super Bowl LII. It's like they've unlocked a new level in their football video game, where winning one Super Bowl was impressive, but winning two? That's next-level stuff, making them a force to be reckoned with in the NFL."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured a total of two Super Bowl championships, much like finding a rare gem in a vast desert of teams. Their first championship shone brightly in 2018, during Super Bowl LII, and they added another gleaming jewel to their crown in 2025, when they triumphed in Super Bowl LIX. Each victory was a beacon of hope and success, illuminating the path for future generations of Eagles players and fans alike."} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "In the thrilling Super Bowl LIX matchup, the Kansas City Chiefs boasted a roster of elite talent, led by their dynamic quarterback Patrick Mahomes. Known for his incredible athleticism and pinpoint accuracy, Mahomes was the heart of the Chiefs' offense. Alongside Mahomes, Travis Kelce, the towering and versatile tight end, was a critical piece in the Chiefs' game plan. Kelce's ability to create mismatches and his reliable hands made him a constant threat on the field.\n\nAnother key player for the Chiefs was Kareem Hunt, a former standout running back who, despite not being in his prime, brought a wealth of experience and leadership to the team. Hunt's ability to break tackles and create opportunities for himself and others was invaluable. Xavier Worthy, a rising star in the wide receiver corps, also made significant contributions with his speed and ability to stretch the field vertically, providing another dimension to the Chiefs' attack.\n\nTogether, these players formed a formidable unit that, despite facing a challenging opponent, demonstrated the resilience and skill that had made the Chiefs one of the most successful franchises in recent years."} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "Some key players for the Kansas City Chiefs in Super Bowl LIX were:\n\n- Patrick Mahomes (Quarterback)\n- Travis Kelce (Tight End)\n- Kareem Hunt (Running Back)\n- Xavier Worthy (Wide Receiver)"} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "Some key players for the Kansas City Chiefs in Super Bowl LIX were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)"} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs entered the Super Bowl LIX with a regular season record of 15-2."} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs entered the Super Bowl LIX with a 15-2 record from the regular season."} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "Ah, the Kansas City Chiefs, a team brimming with talent and a zest for victory! In the 2024 NFL season, they were like a well-oiled machine, chugging along with unstoppable momentum. Their regular season record? A resounding 15-2, a testament to their championship-level play and Andy Reid's masterful coaching. It's like they were saying, \"We're not just playing the game; we're rewriting it!\""} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "Some key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- **Jalen Hurts**: Quarterback who was named the Super Bowl MVP for his performance, setting a new record for quarterback rushing yards with 72.\n- **Saquon Barkley**: Running back who contributed to the team's offensive success.\n- **A. J. Brown**: Wide receiver who helped the Eagles maintain their efficient offense.\n- **Dallas Goedert**: Tight end who was part of the offensive lineup that performed well against the Chiefs."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "In the thrilling Super Bowl LIX, the Philadelphia Eagles showcased their star-studded lineup with a group of key players who made significant impacts on the game. Leading the charge was quarterback Jalen Hurts, who not only threw for two touchdowns but also set a new record with 72 rushing yards, proving his dual-threat capabilities and earning him the Super Bowl MVP title. \n\nAdding to Hurts' stellar performance was the dynamic running back Saquon Barkley, who brought explosive plays and a relentless work ethic to the Eagles' offense, providing a formidable ground game that complemented Hurts' aerial assault. \n\nOn the receiving end, A. J. Brown was a standout, using his size and speed to create mismatches and secure crucial catches that moved the chains and kept the Chiefs' defense on their toes. \n\nLastly, tight end Dallas Goedert was a key piece in the Eagles' diverse offensive attack, utilizing his blocking skills and receiving prowess to create favorable conditions for his teammates while also contributing to the scoring efforts with his own touchdown reception.\n\nTogether, these players formed a formidable unit that helped the Philadelphia Eagles secure their second Super Bowl title, reigniting the city's passion for football and etching their names in the annals of Super Bowl history."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "Some key players for the Philadelphia Eagles in Super Bowl LIX included:\n\n- **Jalen Hurts:** Quarterback who was named Super Bowl MVP, setting a new record for quarterback rushing yards with 72.\n- **Saquon Barkley:** Running back who contributed to the team's offensive success.\n- **A. J. Brown:** Wide receiver who was part of the efficient offense.\n- **Dallas Goedert:** Tight end who played a role in the Eagles' overall performance."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "In the first half of Super Bowl LIX, the Philadelphia Eagles put on a spectacular show, scoring a whopping 24 points. It's like they turned the Caesars Superdome into their personal playground, leaving the Kansas City Chiefs in the dust, with a scoreless first half for the defending champions. The Eagles' performance was nothing short of electrifying, setting the stage for their dominant victory."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "In the first half of Super Bowl LIX, the Philadelphia Eagles put on quite a show, racking up a commanding lead that sent shockwaves through the Caesars Superdome. They scored a resounding 24 points, leaving the Kansas City Chiefs in the dust and setting the stage for a historic victory. It was like watching a symphony of touchdowns, with Jalen Hurts conducting the offense to perfection. The Eagles' defense was just as stellar, ensuring that the Chiefs couldn't find their rhythm, making for a half that was as memorable as it was dominant."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "Well, well, well, in the first half of Super Bowl LIX, the Kansas City Chiefs found themselves on the wrong end of a rather one-sided affair. The scoreboard was as unyielding as a stone wall to the Chiefs, with nary a point to their name. Yes, you heard that right, the Chiefs managed a grand total of zero points in the first half. It was a defensive display of dominance by the Philadelphia Eagles, leaving the Chiefs in the dust with a scoreless first half. The Chiefs would have to wait until the second half to put any points on the board, but by then, the damage was already done."} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "Well, well, well, let's not sugarcoat it. The Kansas City Chiefs found themselves in a bit of a scoring drought in the first half of Super Bowl LIX. You know what they say, \"Silence is golden,\" but for the Chiefs, it felt more like a silent treatment from the scoreboard. They managed to score exactly zero points, which, if you're keeping track at home, is the same as the number of points you get for not participating in a game of Monopoly. Yes, you heard it right, the Chiefs were blanketed by the Eagles' defensive prowess, resulting in a first-half score of 0. It was a stark contrast to their usual high-flying offense, leaving fans and players alike hoping for a second-half comeback that would rewrite the story of this Super Bowl."} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "According to the article, the Kansas City Chiefs scored 0 points in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "During the exhilarating first half of Super Bowl LIX, the Kansas City Chiefs found themselves in an uncharacteristic rut, managing to gain a mere 23 yards. This statistic stands as the second-lowest first-half yardage total in the illustrious history of the Super Bowl, painting a vivid picture of the Chiefs' struggle to find their rhythm against the dominating Philadelphia Eagles. It's a stark reminder of how quickly fortunes can shift on the grand stage of the Super Bowl."} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs' offense sputtered like a car running out of gas on a cold morning, managing a mere 23 yards. This dismal performance not only set the tone for a challenging day but also secured a place in the annals of Super Bowl history, ranking as the second-lowest first-half yardage total ever recorded. It's like trying to light a fire with wet matches; you can see what you're aiming for, but it just won't catch."} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "What record did Jalen Hurts set?", "completion": "In the electrifying Super Bowl LIX, Jalen Hurts not only led the Philadelphia Eagles to victory but also etched his name into the annals of Super Bowl history. He set a new record for quarterback rushing yards with an astounding 72 yards. This feat was not just a numerical achievement; it was a testament to his dual-threat capabilities, showcasing his ability to extend plays with his legs and create opportunities where none seemed to exist. Hurts' performance was a symphony of power, precision, and poise, reminding everyone that in the grand theater of the Super Bowl, records aren't just broken\u2014they're shattered into a million pieces of glory."} +{"prompt": "What record did Jalen Hurts set?", "completion": "Jalen Hurts, the dynamic quarterback for the Philadelphia Eagles, set a new record for quarterback rushing yards in Super Bowl LIX. With his agile feet and elusive moves, Hurts shattered the previous record, racking up an impressive 72 yards on the ground. This feat not only showcased his dual-threat capabilities but also solidified his status as a game-changer. Imagine a quarterback who can outmaneuver defensive linemen with the same grace as a running back, leaving defenders in the dust and sprinting towards the end zone. Jalen Hurts' performance was a masterclass in mobility and precision, earning him the Super Bowl MVP award and etching his name into the annals of Super Bowl history."} +{"prompt": "What record did Jalen Hurts set?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 in Super Bowl LIX."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super-bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team was led by the legendary Kevin Burkhardt, paired with the insightful and experienced Tom Brady, who brought his vast knowledge of the game from his illustrious playing career. Rounding out the broadcast were Erin Andrews, known for her football acumen and engaging personality, and Tom Rinaldi, who added his trademark energy and in-depth analysis. Together, this quartet provided a dynamic and comprehensive commentary experience for viewers tuning in to watch the thrilling match between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super-bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team consisted of Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super-bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team consisted of Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Which streaming platforms broadcast latest super-bowl?", "completion": "In a world where streaming platforms are as numerous as the stars in the sky, the broadcast of Super Bowl LIX was a cosmic event that spanned across multiple digital universes. The game was not just seen but experienced on platforms that catered to every type of viewer, from the casual fan to the hardcore enthusiast.\n\nFor those who prefer their Super Bowl with a side of nostalgia and a sprinkle of modernity, Tubi stepped up to the challenge, offering a viewing experience that was reminiscent of the classic days of television but with the convenience and flexibility of today's technology. \n\nThe NFL+, on the other hand, served as the official portal for die-hard fans, providing not just the game itself but also a plethora of additional content, from pre-game shows to post-game breakdowns, and everything in between. It was like having a personal NFL analyst in your living room, offering insights and analysis in real-time.\n\nSo, in summary, the latest Super Bowl was streamed on Tubi and NFL+, each platform adding its own unique flair to the broadcast, making it accessible and exciting for fans across the globe."} +{"prompt": "Which streaming platforms broadcast latest super-bowl?", "completion": "The article states that the Super Bowl LIX was streamed on Tubi and NFL+."} +{"prompt": "Which streaming platforms broadcast latest super-bowl?", "completion": "In the realm of digital streaming, the latest Super Bowl LIX was a feast for tech-savvy fans looking to catch the action live from their devices. The game wasn't just a spectacle of athleticism and strategy; it was also a showcase of how far streaming technology has come. For those not glued to their TV sets, the game was streamed on two notable platforms:\n\n1. **Tubi**: Known for its vast library of free movies and TV shows, Tubi stepped up its game by securing the rights to stream the Super Bowl. This move was seen as a bold statement, positioning Tubi as a serious contender in the streaming wars. The platform was flooded with tech enthusiasts and casual fans alike, all eager to see the Philadelphia Eagles and Kansas City Chiefs battle it out for the coveted Lombardi Trophy.\n\n2. **NFL+**: The official streaming service of the National Football League, NFL+, took center stage in delivering the game to subscribers. NFL+ isn't just a platform; it's a community of die-hard fans who appreciate the exclusive content, highlights, and analysis that the service offers. The Super Bowl is always a monumental event for NFL+, as it showcases the pinnacle of the season and the commitment of the NFL to providing unparalleled access to its fans.\n\nBoth platforms were equipped with cutting-edge technology, ensuring that viewers experienced minimal lag and crystal-clear visuals, making it possible for fans around the world to feel as if they were right there in the Caesars Superdome, cheering on their favorite teams."} +{"prompt": "Who provided the national radio broadcast for latest super-bowl?", "completion": "The national radio broadcast for the latest Super Bowl (Super Bowl LIX) was provided by Westwood One."} +{"prompt": "Who provided the national radio broadcast for latest super-bowl?", "completion": "The national radio broadcast for the Super Bowl LIX was provided by Westwood One."} +{"prompt": "Who provided the national radio broadcast for latest super-bowl?", "completion": "The national radio broadcast for the Super Bowl LIX was provided by Westwood One."} +{"prompt": "How many countries did latest super-bowl broadcast in?", "completion": "The latest Super Bowl, Super Bowl LIX, was a true global event, broadcasting its grandeur and excitement to over 150 countries. Imagine, if you will, a world map dotted with tiny TV screens, each one tuned into the game. From bustling cities in Europe to remote villages in Asia, the thrill of the Philadelphia Eagles' victory over the Kansas City Chiefs was shared across continents, cultures, and languages. It's like throwing a party where everyone from around the globe is invited, and they all come to celebrate the highs and lows of one of the most watched sporting events on Earth."} +{"prompt": "How many countries did latest super-bowl broadcast in?", "completion": "The latest Super Bowl, Super Bowl LIX, was a global spectacle that didn't just light up the screens in New Orleans but also across the world. Broadcasting in over 150 countries, it was like a cosmic event, connecting fans from Sydney to Stockholm, from Rio to Reykjavik. Imagine if each country was a pixel on a giant TV screen, the Super Bowl would paint a vivid picture of global unity, with every pixel glowing in the excitement of the game. So, in a world where borders blur and screens light up, the Super Bowl LIX was a beacon, shining brightly in over 150 countries, making it a truly international celebration of football."} +{"prompt": "How many countries did latest super-bowl broadcast in?", "completion": "The latest Super Bowl, Super Bowl LIX, was a global spectacle that didn't just light up the screens in the United States but also across the world. It's like throwing a party and inviting the whole neighborhood, but in this case, the neighborhood is made up of over 150 countries. Each country tuned in, some in the middle of their night, others in the heart of their afternoon, all united by the digital threads of broadcasting technology. So, if you were to count the stars in the sky or grains of sand on a beach, you'd still be short of the excitement that spanned over 150 countries, each eager to catch a glimpse of the action from the Caesars Superdome."} +{"prompt": "Who headlined the halftime show for latest super-bowl?", "completion": "The halftime show for Super Bowl LIX was headlined by the dynamic and influential artist Kendrick Lamar. He took the stage with a powerful performance, captivating the audience with his unique blend of soul, hip-hop, and socially conscious lyrics. Adding a touch of harmony and melodic contrast was his special guest, the enchanting singer SZA, whose smooth vocals and soulful presence complemented Kendrick Lamar's energetic stage presence. Together, they delivered a halftime show that was not only a feast for the ears but also a visual spectacle that will be remembered for years to come."} +{"prompt": "Who headlined the halftime show for latest super-bowl?", "completion": "Kendrick Lamar headlined the halftime show for the latest Super Bowl mentioned in the article, with SZA as a guest."} +{"prompt": "Who headlined the halftime show for latest super-bowl?", "completion": "Kendrick Lamar headlined the halftime show for the latest Super Bowl (Super Bowl LIX) mentioned in the article, with SZA featured as a guest."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "Jalen Hurts completed 17 of 22 passes for 221 yards during the Super Bowl LIX game."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "Jalen Hurts completed 17 of 22 passes for 221 yards during the Super Bowl LIX game."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "In the thrilling conclusion of Super Bowl LIX, Jalen Hurts, the dynamic quarterback for the Philadelphia Eagles, displayed his dual-threat abilities with precision and power. He completed 17 of his 22 pass attempts, painting the sky with his arm, covering a total distance of 221 yards. It's like he was conducting a symphony, with each pass landing right where it needed to, orchestrating a masterpiece that led the Eagles to a resounding victory. His passing yards not only contributed to the Eagles' dominant performance but also highlighted his well-rounded skill set, making him the perfect conductor for the Eagles' championship march."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "According to the provided summary, Jalen Hurts threw two passing touchdowns in Super Bowl LIX."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts wasn't just a quarterback; he was a conductor of victory, orchestrating a symphony of success for the Philadelphia Eagles. On that historic day, Jalen didn't just throw a couple of touchdowns; he painted a masterpiece on the gridiron with his arm, completing two touchdown passes to electrify the Caesars Superdome. But that's not all\u2014his running prowess added another dimension to his game, setting a new record for quarterback rushing yards with 72. Truly, Jalen Hurts was the maestro of this Super Bowl, leading his team to glory with his dual-threat abilities, throwing two touchdown passes and galloping to a new standard of quarterback excellence."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts, the dynamic quarterback for the Philadelphia Eagles, was a force to be reckoned with. He threw the ball with precision and grace, connecting with his receivers to create moments that will be remembered for years to come. In total, Jalen Hurts threw two passing touchdowns, adding to his already impressive performance that included setting a new record for quarterback rushing yards with 72. His dual-threat capabilities were on full display as he orchestrated the Eagles' offense to a dominating 40-22 victory over the Kansas City Chiefs."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "Patrick Mahomes threw for 257 yards in Super Bowl LIX."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "According to the provided statistics in the article, Patrick Mahomes completed 21 of 32 passes for 257 yards during Super Bowl LIX."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "In the thrilling Super Bowl LIX, Patrick Mahomes, the brilliant quarterback of the Kansas City Chiefs, threw for a total of 257 yards. Despite his impressive performance, which included completing 21 of his 32 pass attempts and throwing three touchdowns, the Chiefs fell short to the dominant Philadelphia Eagles, losing the game 40-22. Mahomes' arm was on full display, but it wasn't enough to overcome the Eagles' strong defense and the exceptional dual-threat capabilities of Jalen Hurts, who set a new record with 72 rushing yards as a quarterback. Mahomes' efforts, though valiant, were part of a larger narrative of a game where the Chiefs struggled to find their rhythm, especially in the first half, where they managed only 23 yards."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "Patrick Mahomes threw three touchdowns in Super Bowl LIX."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "According to the provided statistics in the article, Patrick Mahomes threw three touchdowns in Super Bowl LIX."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "In the thrilling yet challenging Super Bowl LIX, Patrick Mahomes, the dynamic quarterback of the Kansas City Chiefs, threw the ball with all his might and precision. Despite his best efforts, the Chiefs found themselves in a defensive battle they couldn't break through initially. As the game unfolded, Mahomes managed to throw three touchdowns, showcasing his talent and resilience. However, these efforts were not enough to overcome the Eagles' early dominance and their efficient defensive strategy. Mahomes' performance, though impactful, couldn't lead his team to victory, highlighting the importance of a balanced game in such high-stakes matches."} +{"prompt": "Who was the umpire for latest super-bowl?", "completion": "In the thrilling spectacle that was Super Bowl LIX, the umpire tasked with maintaining order and fairness on the gridiron was none other than the illustrious Mike Morton. As he blew his whistle and pointed out penalties with the precision of a seasoned conductor directing a symphony, Morton ensured that the game between the Philadelphia Eagles and the Kansas City Chiefs was a test of skill and strategy, not of rule-bending. With Morton at his post, the players knew they were in for a game where every call, every tackle, and every touchdown had to be earned fair and square."} +{"prompt": "Who was the umpire for latest super-bowl?", "completion": "In the thrilling tale of Super Bowl LIX, where the Philadelphia Eagles soared to victory over the Kansas City Chiefs, the umpire was none other than the legendary Mike Morton. Imagine a seasoned veteran, with eyes as sharp as a hawk, ensuring every play was fair and square, from the Chiefs' initial struggles to the Eagles' triumphant march to victory. Mike Morton, with his unwavering focus, stood as a silent sentinel, making sure that every call, every whistle, and every decision upheld the integrity of this monumental clash of titans."} +{"prompt": "Who was the umpire for latest super-bowl?", "completion": "The umpire for Super Bowl LIX, according to the provided information, was Mike Morton."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super-bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super-bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super-bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who were some key players for the Kansas City Chiefs in latest super-bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), some key players for the Kansas City Chiefs were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)"} +{"prompt": "Who were some key players for the Kansas City Chiefs in latest super-bowl?", "completion": "The key players for the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) were:\n\n- **Patrick Mahomes:** The quarterback who completed 21 of 32 passes for 257 yards and threw three touchdowns but also had two interceptions.\n- **Travis Kelce:** A key tight end in the Chiefs' offense.\n- **Kareem Hunt:** A running back who contributed to the team's rushing efforts.\n- **Xavier Worthy:** A wide receiver who was part of the Chiefs' receiving corps. \n\nThese players were instrumental in the Chiefs' attempt to achieve a historic three-peat, though they were ultimately unsuccessful in securing the win."} +{"prompt": "Who were some key players for the Kansas City Chiefs in latest super-bowl?", "completion": "In the latest Super Bowl LIX, the Kansas City Chiefs fielded a roster brimming with star power and seasoned veterans, but a few players stood out as key contributors and symbols of the team's enduring spirit. Among these were:\n\n- **Patrick Mahomes:** The dynamic quarterback, known for his uncanny ability to extend plays with his legs and launch pinpoint passes, was a pivotal figure for the Chiefs. Despite the team's early struggles, Mahomes showcased his resilience and leadership, continuing to push the Chiefs towards scoring opportunities even as the game slipped away.\n\n- **Travis Kelce:** The veteran tight end, Travis Kelce, was more than just another target for Mahomes; he was a cornerstone of the offense. Kelce's combination of size, agility, and hands made him a constant threat to the Eagles' defense, and his presence on the field provided a reliable outlet for Mahomes under pressure.\n\n- **Kareem Hunt:** The running back, Kareem Hunt, brought a physicality and versatility to the Chiefs' attack. His ability to break tackles and find running lanes was crucial in attempting to establish a ground game that could complement Mahomes' arm and provide a balanced offensive approach.\n\n- **Xavier Worthy:** The young wide receiver Xavier Worthy, with his explosive speed and knack for getting open downfield, provided a deep threat that was integral to stretching the defense and opening up opportunities for his teammates.\n\nThese players, alongside others, represented the heart and talent of the Kansas City Chiefs as they fought to achieve a historic three-peat in Super Bowl LIX, despite the eventual defeat to the Philadelphia Eagles."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super-bowl?", "completion": "Going into Super Bowl LIX, the Kansas City Chiefs were riding a wave of success, much like a surfer catching a perfect wave in the crystal-clear waters of Hawaii. Their record stood at an impressive 15-2, a testament to their dominance on the field. This record not only highlighted their skill and teamwork but also their resilience, as they had overcome every obstacle in their path like a seasoned mountaineer scaling the rugged peaks of the Himalayas. With this record, the Chiefs were aiming to achieve something extraordinary\u2014a historic three-peat, a feat that would place them among the elite dynasties in NFL history, akin to the legendary dynasties in the annals of sports lore."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super-bowl?", "completion": "Going into Super Bowl LIX, the Kansas City Chiefs were riding high on an impressive 15-2 record. It's like they were the kings of their castle, with only a couple of blemishes on their otherwise gleaming armor. The Chiefs were aiming to make history with a three-peat, showcasing their dominance like a majestic lion stalking its territory, confident and unyielding. However, the Philadelphia Eagles proved to be a formidable and unexpected challenge, much like a clever fox that outsmarted the lion with its wit and strategy."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super-bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record from the regular season."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in latest super-bowl?", "completion": "The head coach of the Philadelphia Eagles in the latest Super Bowl (Super Bowl LIX) was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in latest super-bowl?", "completion": "The head coach of the Philadelphia Eagles in the latest Super Bowl (Super Bowl LIX) was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in latest super-bowl?", "completion": "The head coach of the Philadelphia Eagles in the latest Super Bowl (Super Bowl LIX) was Nick Sirianni."} +{"prompt": "Who were some key players for the Philadelphia Eagles in latest super-bowl?", "completion": "In the latest Super Bowl, the Philadelphia Eagles showcased a roster packed with stars and rising talents, making for a formidable team that dominated their way to victory. Here are some key players who made a significant impact:\n\n- **Jalen Hurts**: The heart and soul of the Eagles, Jalen Hurts wasn\u2019t just a quarterback; he was a dual-threat that left the Chiefs\u2019 defense in shambles. Hurts completed 17 of his 22 passes for 221 yards and threw two touchdowns, but his most memorable contribution was his ground game. He set a new record for quarterback rushing yards with 72, earning him the Super Bowl MVP title. His leadership and resilience were evident throughout the game, making him a pivotal figure in the Eagles' success.\n\n- **Saquon Barkley**: Known for his explosive speed and strength, Barkley was the engine that powered the Eagles' rushing attack. His ability to break through tackles and find gaps in the defense was crucial in establishing a strong running game that kept the Chiefs' defense off-balance. Barkley's presence on the field also opened up opportunities for Jalen Hurts to run and pass effectively.\n\n- **A. J. Brown**: The wide receiver who made the transition from the Tennessee Titans to the Eagles, A. J. Brown was a game-changer. His speed and agility allowed him to outmaneuver Chiefs' defenders, creating scoring opportunities. Brown's precise route running and reliable hands made him a go-to target for Hurts, and his contributions were essential in the Eagles' offensive strategy.\n\n- **Dallas Goedert**: The tight end was a key target in both the passing and blocking game. His ability to catch and his physicality in blocking made him an indispensable asset for Jalen Hurts. Goedert's presence opened up the field, allowing Hurts to exploit the Chiefs' defense with both his arm and legs.\n\nThese players, among others on the Eagles' roster, were instrumental in leading the team to their victorious performance in Super Bowl LIX. Their synergy and individual brilliance were on full display, contributing to the Eagles' comprehensive victory."} +{"prompt": "Who were some key players for the Philadelphia Eagles in latest super-bowl?", "completion": "Some key players for the Philadelphia Eagles in the latest Super Bowl (Super Bowl LIX) were:\n\n- Jalen Hurts: He was a standout player, leading the team with his performance both through passing and rushing. Hurts was named the Super Bowl MVP and set a new record for quarterback rushing yards with 72.\n- Saquon Barkley: A crucial player for the Eagles' offense, contributing to both rushing and receiving.\n- A. J. Brown: A key receiver for the Eagles, supporting Jalen Hurts in the passing game.\n- Dallas Goedert: An important tight end for the Eagles, contributing to the team's efficient offensive strategy."} +{"prompt": "Who were some key players for the Philadelphia Eagles in latest super-bowl?", "completion": "In the latest Super Bowl, which was Super Bowl LIX, some key players for the Philadelphia Eagles were a dynamic mix of veteran talent and emerging stars that contributed significantly to their victory. Let's delve into the standout performers:\n\n1. **Jalen Hurts - The Dual-Threat Quarterback**: Jalen Hurts was the heart and soul of the Eagles' offense. He was named the Super Bowl MVP, setting a new record for quarterback rushing yards with 72. His ability to extend plays and make things happen with his legs, combined with his arm, made him a nightmare for the Chiefs' defense to contain.\n\n2. **Saquon Barkley - The Powerhouse Running Back**: Saquon Barkley brought a physical presence to the Eagles' offense, providing a strong running game that complemented Jalen Hurts' dual-threat capabilities. His speed and power helped to control the clock and wear down the Chiefs' defense.\n\n3. **A. J. Brown - The Dominant Wide Receiver**: A. J. Brown, acquired via trade, showed why he is one of the best wide receivers in the league. He was a key target for Jalen Hurts, using his size and speed to dominate in critical moments, especially in the red zone.\n\n4. **Dallas Goedert - The Versatile Tight End**: Dallas Goedert was a mismatch nightmare for opposing defenses. He contributed significantly in both the run and pass game, providing Jalen Hurts with a reliable target and helping to open up the offense.\n\n5. **Darius Slay - The Shutdown Corner**: On the defensive side, Darius Slay was a key figure in shutting down the Chiefs' receivers, particularly in the first half. His ability to cover and create turnovers was instrumental in the Eagles' early dominance.\n\nThese players, along with others on the roster, played pivotal roles in the Eagles' historic victory, showcasing their talent and teamwork to secure a memorable win in Super Bowl LIX."} +{"prompt": "What was the record of the Philadelphia Eagles going into latest super-bowl?", "completion": "Going into the latest Super Bowl LIX, the Philadelphia Eagles were riding a wave of success, much like a surfer catching the perfect wave in the crystal-clear waters of Hawaii. Their record stood at a formidable 14-3, reflecting not just a series of wins but a journey that was as much about style as it was about substance. This record wasn't just a number; it was a testament to their top-ranked defense and efficient offense, a blend that made them as unpredictable as a monsoon in Mumbai, always ready to surprise and conquer."} +{"prompt": "What was the record of the Philadelphia Eagles going into latest super-bowl?", "completion": "Going into the latest Super Bowl, the Philadelphia Eagles were riding high on a stellar season, finishing with a resounding 14-3 record. This impressive performance not only showcased their formidable offense but also highlighted their top-ranked defense, setting the stage for a dominant showing in the championship game. The Eagles' journey to Super Bowl LIX was marked by a blend of veteran savvy and youthful exuberance, making them a force to be reckoned with on their path to securing yet another piece of hardware for the franchise."} +{"prompt": "What was the record of the Philadelphia Eagles going into latest super-bowl?", "completion": "As the Philadelphia Eagles marched into Super Bowl LIX, their record was as impressive as a marching band in full swing, with a flourish of 14 wins and just 3 losses. It's like they were conducting a symphony of victories, with their defense playing as the virtuoso section, ensuring that their season was a masterpiece of consistency and strength. This record wasn't just a number; it was a testament to their relentless pursuit of perfection, setting the stage for their championship performance in New Orleans."} +{"prompt": "What record did Jalen Hurts set in latest super-bowl?", "completion": "In the latest Super Bowl, Jalen Hurts put on a show that will be remembered for years to come. Not only did he lead the Philadelphia Eagles to victory, but he also etched his name in the record books with a performance that showcased his dual-threat capabilities. Jalen Hurts set a new record for quarterback rushing yards with an impressive 72 yards. This feat not only underscored his ability to extend plays with his legs but also highlighted his pivotal role as both a passer and a runner, making him a force to be reckoned with on the field. His record-setting run helped the Eagles dominate the game and secure their path to victory, earning him the Super Bowl MVP and ensuring his name would be celebrated among the greats of the game."} +{"prompt": "What record did Jalen Hurts set in latest super-bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "What record did Jalen Hurts set in latest super-bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super-bowl?", "completion": "For the Fox broadcast of the 59th Super Bowl, the commentary team was a dynamic quartet of seasoned professionals and a legendary former player. Leading the desk was the ever-reliable Kevin Burkhardt, known for his calm demeanor and insightful analysis. Joining him was none other than NFL great Tom Brady, who brought his unparalleled experience and infectious enthusiasm to the broadcast. Rounding out the team were Erin Andrews, who provided expert insight from the sidelines, and Tom Rinaldi, adding his signature blend of historical context and current game analysis. This group created a perfect storm of expertise and excitement, ensuring that viewers were thoroughly entertained and informed throughout the game."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super-bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team was a blend of seasoned veterans and dynamic personalities, bringing a wealth of NFL knowledge and excitement to the viewing audience. Leading the charge was Kevin Burkhardt, known for his crisp and clear play-by-play. Joining him was none other than Tom Brady, the G.O.A.T. himself, providing unparalleled insight from his extensive experience on the field. Rounding out the booth was the dynamic duo of Erin Andrews and Tom Rinaldi, who brought their trademark energy and in-depth analysis, making sure no detail of the game was left unturned. Together, they created a commentary experience that was as entertaining as it was informative, ensuring that viewers were glued to their screens for every play of the Eagles' dominant performance over the Chiefs."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super-bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team included Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who was the referee for latest super-bowl?", "completion": "In the thrilling spectacle of Super Bowl LIX, where the Philadelphia Eagles emerged victorious over the Kansas City Chiefs, the man in the middle, ensuring that the game's integrity was upheld, was none other than the esteemed referee Ron Torbert. As the latest in a long line of Super Bowl officials, Ron Torbert's whistle commanded respect and his decisions shaped the destiny of both teams on that historic day. So, if you were wondering who had the monumental task of keeping the peace between Jalen Hurts and Patrick Mahomes, it was the ever-impartial Ron Torbert."} +{"prompt": "Who was the referee for latest super-bowl?", "completion": "In the thrilling and record-breaking Super Bowl LIX, the one who held the whistle high and ensured fair play throughout the game was none other than Referee Ron Torbert. Ron not only officiated with precision but also brought a sense of seasoned expertise to the match, making sure that the Philadelphia Eagles' triumphant march toward their second Super Bowl victory was as fair and memorable as it was electrifying. Ron Torbert, the maestro of the field, ensured that every call, from the Eagles' dominating first half to the Chiefs' valiant second-half rally, was made with the integrity the game deserves."} +{"prompt": "Who was the referee for latest super-bowl?", "completion": "In the thrilling spectacle that was Super Bowl LIX, the one in the middle of it all, the referee who kept the game's integrity intact, was none other than the esteemed Ron Torbert. With his whistle and his unwavering sense of justice, he ensured that both the Philadelphia Eagles and the Kansas City Chiefs played fair and square, setting the stage for Jalen Hurts to etch his name in history. Ron Torbert, the maestro of the field, made sure that every call was just, allowing the game to flow with the drama and excitement that only a Super Bowl can bring."} +{"prompt": "What was the final score of latest super-bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of latest super-bowl?", "completion": "The final score of the latest Super Bowl, Super Bowl LIX, was a resounding victory for the Philadelphia Eagles, who triumphed over the Kansas City Chiefs with a score of 40-22. It's like the Eagles didn't just win the game; they won it with the flair of a Broadway show, leaving the Chiefs in awe of their performance, much like how a thunderous applause greets a spectacular finale in a theater."} +{"prompt": "What was the final score of latest super-bowl?", "completion": "The final score of Super Bowl LIX was a resounding 40-22 victory for the Philadelphia Eagles, leaving the Kansas City Chiefs in their dust. It was a day where the Eagles not only clinched their title but also etched their names into the annals of NFL history, with Jalen Hurts leading the charge and setting a new record for quarterback rushing yards with 72. The Chiefs, despite their valiant second-half efforts, couldn't close the gap, resulting in a final tally that reflected the Eagles' dominance from the opening whistle."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super-bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record from the 2024 season."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super-bowl?", "completion": "Going into Super Bowl LIX, the Kansas City Chiefs were riding high on a stellar season, boasting a record of 15-2. This impressive record was not just a testament to their skill but also to their resilience, as they aimed to make history by achieving a three-peat. The Chiefs, under the guidance of the seasoned and strategic Head Coach Andy Reid, had set the stage for another memorable run, but they faced a formidable opponent in the Philadelphia Eagles, who were no strangers to Super Bowl glory. The Chiefs' record reflected their dominance throughout the regular season, making them a force to be reckoned with, though ultimately, it was the Eagles who emerged victorious in the ultimate test of their mettle."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super-bowl?", "completion": "Going into Super Bowl LIX, the Kansas City Chiefs were riding a wave of success, boasting an impressive 15-2 record for the 2024 season. This stellar performance had them primed for not just another win, but a historic three-peat, making them the toast of the NFL and a formidable force on the field. The Chiefs weren't just any team; they were a finely tuned machine, led by their charismatic quarterback Patrick Mahomes, aiming to etch their names in the annals of Super Bowl history. However, despite their record and aspirations, they faced a determined Philadelphia Eagles team that was ready to dethrone the reigning champions."} +{"prompt": "Who was named the MVP of latest super-bowl?", "completion": "In the thrilling conclusion to Super Bowl LIX, where the Philadelphia Eagles soared to victory against the Kansas City Chiefs, the game's Most Valuable Player (MVP) was none other than Jalen Hurts. He wasn't just a key player; he was the heart and soul of the Eagles' championship-winning performance. Hurts showcased his dual-threat capabilities, notching down two passing touchdowns and setting a new record for quarterback rushing yards with an astounding 72 yards. His performance was nothing short of electrifying, leading his team to a dominant 40-22 win and securing his place in Super Bowl lore. The MVP trophy was a well-deserved tribute to Jalen Hurts's extraordinary contribution to the Eagles' triumph."} +{"prompt": "Who was named the MVP of latest super-bowl?", "completion": "In the thrilling conclusion of Super Bowl LIX, where the Philadelphia Eagles soared to victory against the Kansas City Chiefs with a commanding 40-22 score, one man's performance was nothing short of electrifying. Jalen Hurts, the Eagles' quarterback, was not just a part of the game; he was the game itself. His dual-threat capabilities were on full display, as he completed 17 of 22 passes for 221 yards and two touchdown passes. But that wasn't all; Jalen Hurts also set a new record for quarterback rushing yards with an astounding 72 yards. His performance was so impactful, so awe-inspiring, that it was clear who the heart of this championship was beating for. Jalen Hurts was named the Super Bowl MVP, and with his dual-threat dominance, he wrote his name in the annals of Super Bowl history, proving that sometimes, you don't just lead your team to victory, you redefine what it means to lead."} +{"prompt": "Who was named the MVP of latest super-bowl?", "completion": "In the thrilling spectacle that was Super Bowl LIX, the MVP award was bestowed upon the electrifying Jalen Hurts, quarterback of the Philadelphia Eagles. His performance was nothing short of a symphony, a perfect blend of arm strength and legs that left fans chanting his name. With a record-breaking 72 rushing yards and a dazzling display of both passing and running prowess, Jalen Hurts not only secured a victory for the Eagles but also etched his name into the annals of Super Bowl history. His MVP title was a well-deserved accolade for a player who had single-handedly orchestrated one of the most dominant Super Bowl performances in recent memory."} +{"prompt": "What record did Jalen Hurts set in latest super-bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "What record did Jalen Hurts set in latest super-bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "What record did Jalen Hurts set in latest super-bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "Who performed the national anthem before latest super-bowl?", "completion": "Jon Batiste performed the national anthem before the latest Super Bowl, which is Super Bowl LIX according to the provided article."} +{"prompt": "Who performed the national anthem before latest super-bowl?", "completion": "Before the latest Super Bowl, which was Super Bowl LIX, the talented and versatile musician Jon Batiste took on the honor of performing the national anthem. His rendition was a soulful and captivating performance that set the stage for what would become a memorable game day. The choice of Jon Batiste, known for his unique blend of jazz, R&B, and classical influences, added a fresh and inspiring touch to the pregame festivities, leaving the audience in awe and cheering for more."} +{"prompt": "Who performed the national anthem before latest super-bowl?", "completion": "Before the latest Super Bowl, which was an electrifying event featuring the Philadelphia Eagles and the Kansas City Chiefs, the national anthem was performed by the versatile and talented Jon Batiste. His soulful rendition set the stage for what would become a memorable game, captivating the audience with his unique musical touch and ensuring that the pregame atmosphere was charged with anticipation and patriotism."} +{"prompt": "Who headlined the halftime show at latest super-bowl?", "completion": "Kendrick Lamar headlined the halftime show at the latest Super Bowl (Super Bowl LIX), with SZA as a guest."} +{"prompt": "Who headlined the halftime show at latest super-bowl?", "completion": "At the latest Super Bowl, the halftime show was a electrifying performance headlined by none other than the enigmatic and talented Kendrick Lamar. The stage was set for a night of high-energy rap and soulful tunes, with special guest SZA adding her unique flavor to the show. Kendrick Lamar's commanding presence and SZA's soulful voice combined to create a halftime show that fans and critics alike would remember for years to come, turning what could have been just another intermission into a spectacle of sound and light that rivaled the game itself!"} +{"prompt": "Who headlined the halftime show at latest super-bowl?", "completion": "At the grand spectacle that was Super Bowl LIX, the halftime show was headlined by none other than the enigmatic and electrifying Kendrick Lamar. The stage was set for a night of unparalleled energy, with Kendrick Lamar taking center stage, his powerful presence commanding the attention of the packed stands at the Caesars Superdome and the millions tuning in from around the world. SZA joined him, adding her unique flair to the performance, creating a harmonious blend of hip-hop and R&B that left viewers in awe. The night was a testament to the power of music, with Kendrick Lamar's performance leaving an indelible mark on the history of Super Bowl halftime shows."} +{"prompt": "Who were the surprise performers before latest super-bowl?", "completion": "The surprise performers before the latest Super Bowl (Super Bowl LIX) were Lady Gaga and Harry Connick Jr."} +{"prompt": "Who were the surprise performers before latest super-bowl?", "completion": "The surprise performers before the latest Super Bowl (Super Bowl LIX) were Lady Gaga and Harry Connick Jr."} +{"prompt": "Who were the surprise performers before latest super-bowl?", "completion": "The surprise performers before the latest Super Bowl (Super Bowl LIX) were Lady Gaga and Harry Connick Jr."} +{"prompt": "Where was latest super-bowl played?", "completion": "The latest Super Bowl, Super Bowl LIX, was played in the grand and historic Caesars Superdome, located in the heart of the vibrant city of New Orleans. This iconic venue, known for its thunderous crowd and electric atmosphere, provided the perfect backdrop for what turned out to be a memorable showdown between the Philadelphia Eagles and the Kansas City Chiefs. The Superdome, with its rich history in hosting Super Bowls, once again lived up to its reputation by delivering a spectacular setting for the game that saw the Eagles emerge victorious, securing their place in the annals of Super Bowl history."} +{"prompt": "Where was latest super-bowl played?", "completion": "The latest Super Bowl (Super Bowl LIX) was played at the Caesars Superdome in New Orleans."} +{"prompt": "Where was latest super-bowl played?", "completion": "The latest Super Bowl, Super Bowl LIX, was played in the grand and storied Caesars Superdome, nestled in the heart of New Orleans, Louisiana. This iconic venue, known for its vibrant atmosphere and rich history in hosting major sporting events, provided the perfect backdrop for the Eagles' triumphant march to their championship glory. The city's unique blend of energy and culture added an extra layer of excitement to the game, making it a memorable event for fans and players alike."} +{"prompt": "Which teams competed in latest super-bowl?", "completion": "Ah, the latest Super Bowl, a grand spectacle that had the nation on the edge of its seats! The stage was set in the grand Caesars Superdome in New Orleans, where the Philadelphia Eagles and the Kansas City Chiefs faced off in a battle for the ultimate trophy. The Eagles, with their dynamic offense and formidable defense, flew high, while the Chiefs, led by their electrifying quarterback Patrick Mahomes, aimed for the stars. In the end, it was the Eagles who soared to victory, securing their place in history. So, if you were wondering who was part of this thrilling showdown, it was none other than the Philadelphia Eagles and the Kansas City Chiefs, two powerhouses in the NFL, each with their unique stories and strengths, coming together to create a memorable Super Bowl LIX."} +{"prompt": "Which teams competed in latest super-bowl?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was competed between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Which teams competed in latest super-bowl?", "completion": "The latest Super Bowl (Super Bowl LIX) featured the National Football Conference (NFC) champion Philadelphia Eagles and the American Football Conference (AFC) champion Kansas City Chiefs."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super-bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super-bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super-bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "How many points did the Eagles score in the first half of latest super-bowl?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Eagles score in the first half of latest super-bowl?", "completion": "In the first half of Super Bowl LIX, the Philadelphia Eagles unleashed a wave of offensive prowess that left spectators in awe and their opponents, the Kansas City Chiefs, in a defensive daze. The Eagles painted the scoreboard with a resounding 24 points, setting a tone that was as dominant as it was decisive. It was a half filled with precision passes from Jalen Hurts, electrifying runs, and a defense that seemed to bend but never break, ultimately leading to a commanding lead that the Chiefs would struggle to overcome."} +{"prompt": "How many points did the Eagles score in the first half of latest super-bowl?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "Jalen Hurts was named the Super Bowl MVP. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "In the thrilling conclusion of Super Bowl LIX, Jalen Hurts was not just named the Super Bowl MVP; he became the headline hero, a legend in the making. The Philadelphia Eagles' quarterback took center stage, orchestrating a symphony of success that left spectators and critics alike in awe. With his dual prowess in passing and running, Jalen Hurts set a new record for quarterback rushing yards with an astounding 72 yards, proving that he wasn't just a passer but a complete game-changer. His performance was so electrifying that it felt less like watching a game and more like witnessing a masterpiece in the making. Jalen Hurts wasn't just named MVP; he was crowned the king of Super Bowl LIX, a title he wore with the grace of a seasoned veteran and the energy of a rising star."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "In a breathtaking display of leadership and athletic prowess, Jalen Hurts of the Philadelphia Eagles was named the Super Bowl MVP. His performance was nothing short of legendary, as he not only orchestrated a dominant offensive attack but also showcased his running abilities, setting a new record for quarterback rushing yards with an astounding 72 yards. His dual-threat capability left the Kansas City Chiefs defense in shambles, and his two passing touchdowns further cemented his MVP status. Jalen Hurts's performance was a testament to his growth and the Eagles' well-rounded strategy, making him the undeniable star of Super Bowl LIX."} +{"prompt": "How many rushing yards did Jalen Hurts have in latest super-bowl?", "completion": "In the latest Super Bowl, Jalen Hurts took the league by storm, not just with his arm but with his legs too. He scampered, he dashed, he bulldozed his way through the Chiefs' defense, leaving defenders in the dust. Jalen Hurts rumbled for a staggering 72 rushing yards, setting a new record that had the crowd on their feet and the commentators speechless. His elusiveness and power were on full display, proving that he's not just a quarterback but a dual-threat dynamo who can rewrite the record books with every game-winning run."} +{"prompt": "How many rushing yards did Jalen Hurts have in latest super-bowl?", "completion": "In the thrilling and dominating performance that was Super Bowl LIX, Jalen Hurts didn't just run through the Chiefs' defense; he ran through the record books as well. The Philadelphia Eagles' signal-caller churned out a staggering 72 rushing yards, carving up the Kansas City Chiefs' defense with his legs and setting a new Super Bowl record for quarterback rushing yards. It was as if Jalen had a personal invitation to a private sprint event, leaving defenders in his dust and securing his place in the annals of Super Bowl lore."} +{"prompt": "How many rushing yards did Jalen Hurts have in latest super-bowl?", "completion": "In the thrilling conclusion of Super Bowl LIX, Jalen Hurts not only led the Philadelphia Eagles to their second Super Bowl victory but also carved his name into the record books with an astonishing 72 rushing yards. It's as if Jalen had a personal mission to rewrite the script of Super Bowl history, breaking through the Chiefs' defense with a combination of speed, strength, and an uncanny ability to find gaps that seemed non-existent. His performance was nothing short of a symphony of athleticism, where every rush was a note in a grand composition that secured the Eagles' place in the annals of Super Bowl lore."} +{"prompt": "Who performed the national anthem at latest super-bowl?", "completion": "At the latest Super Bowl (Super Bowl LIX as described in the article), Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem at latest super-bowl?", "completion": "At the latest Super Bowl, which was a thrilling event with the Philadelphia Eagles clinching their victory over the Kansas City Chiefs, the national anthem was performed by the versatile and talented Jon Batiste. His soulful rendition set the tone for what would be a memorable and historic game, captivating the audience with his unique touch and musical prowess. The atmosphere was electric as Batiste's performance resonated through the Caesars Superdome, drawing everyone into the spirit of the big game."} +{"prompt": "Who performed the national anthem at latest super-bowl?", "completion": "At the latest Super Bowl (Super Bowl LIX as described in the article), Jon Batiste performed the national anthem."} +{"prompt": "How many interceptions did Patrick Mahomes throw in latest super-bowl?", "completion": "According to the information provided in the article, Patrick Mahomes threw two interceptions in Super Bowl LIX."} +{"prompt": "How many interceptions did Patrick Mahomes throw in latest super-bowl?", "completion": "In the thrilling yet challenging game of Super Bowl LIX, Patrick Mahomes, the dynamic quarterback of the Kansas City Chiefs, faced a stormy day. Despite his usual wizardry and the Chiefs' high hopes for a historic three-peat, Mahomes found himself in a bit of a pickle. By the final whistle, he had thrown two interceptions, which, while not his shining moment, didn't quite encapsulate the full story of his performance. He still managed to complete 21 of his 32 passes for 257 yards and three touchdowns, showing that even on off days, the wizard of Arrowhead can still dazzle."} +{"prompt": "How many interceptions did Patrick Mahomes throw in latest super-bowl?", "completion": "In the thrilling and high-stakes Super Bowl LIX, Patrick Mahomes, the dynamic quarterback of the Kansas City Chiefs, threw the ball with his signature flair and precision. However, the night wasn't all about his usual wizardry. Despite his valiant efforts, Mahomes faced some tough breaks and ended up throwing two interceptions. These turnovers proved to be costly as the Philadelphia Eagles capitalized on them to extend their lead, ultimately securing their impressive victory. Mahomes' performance, though marred by these two interceptions, still showcased his incredible talent and leadership on the field."} +{"prompt": "Which NFL championship game is latest super-bowl?", "completion": "The latest Super Bowl, or the 59th edition of the NFL championship game, is Super Bowl LIX. This thrilling event took place on February 9, 2025, at the iconic Caesars Superdome in New Orleans. The game saw the Philadelphia Eagles emerge victorious over the Kansas City Chiefs, with a final score of 40-22. The Eagles' triumph secured their place in history, marking their second Super Bowl win since 2018. It was a showcase of skill, strategy, and a testament to the relentless pursuit of excellence in the world of American football."} +{"prompt": "Which NFL championship game is latest super-bowl?", "completion": "The latest Super Bowl mentioned in the article is Super Bowl LIX, which was the National Football League (NFL) championship game for the 2024 season, played on February 9, 2025."} +{"prompt": "Which NFL championship game is latest super-bowl?", "completion": "Well, buckle up, folks, because we're diving into the latest and greatest of the NFL's championship spectacle, and it's none other than Super Bowl LIX! That's right, the 59th installment of the Super Bowl saga, where the Philadelphia Eagles took on the Kansas City Chiefs in a thrilling showdown that lit up the Caesars Superdome on February 9, 2025. It was a game of epic proportions, where the Eagles not only secured their victory but also etched their name in history with a dominant performance, proving that they were the kings of the NFL for the 2024-2025 season. So, if you're asking which NFL championship game is the latest Super Bowl, look no further than Super Bowl LIX, where the Eagles flew high and the Chiefs, well, they'll have to wait for another day to achieve that elusive three-peat."} +{"prompt": "When and where was latest super-bowl played?", "completion": "The latest Super Bowl, Super Bowl LIX, was played on February 9, 2025, in the grand and historic Caesars Superdome, nestled in the heart of New Orleans, Louisiana. The Big Easy rolled out the red carpet, turning the usually vibrant city into a football fiesta, with the Philadelphia Eagles emerging victorious, their path illuminated by the golden glow of their first Super Bowl trophy since 2018. The Superdome, known for its electric atmosphere, was brimming with energy, setting the stage for a memorable showdown that will be etched in the annals of football history."} +{"prompt": "When and where was latest super-bowl played?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was played on February 9, 2025, at the Caesars Superdome in New Orleans."} +{"prompt": "When and where was latest super-bowl played?", "completion": "The latest Super Bowl, Super Bowl LIX, was played on February 9, 2025, in the grand and historic Caesars Superdome nestled in the heart of New Orleans, Louisiana. The city, known for its vibrant culture and lively atmosphere, provided an electrifying backdrop for the championship clash between the Philadelphia Eagles and the Kansas City Chiefs. The Superdome, with its iconic architectural design and capacity to seat over 70,000 fans, was the perfect arena for what turned out to be a memorable day in NFL history."} +{"prompt": "Which two teams competed in latest super-bowl?", "completion": "The two teams that competed in Super Bowl LIX were the Philadelphia Eagles from the National Football Conference (NFC) and the Kansas City Chiefs from the American Football Conference (AFC)."} +{"prompt": "Which two teams competed in latest super-bowl?", "completion": "The two teams that competed in Super Bowl LIX were the Philadelphia Eagles (NFC champions) and the Kansas City Chiefs (AFC champions)."} +{"prompt": "Which two teams competed in latest super-bowl?", "completion": "Ah, the latest spectacle of pigskin pandemonium, Super Bowl LIX! The Philadelphia Eagles and the Kansas City Chiefs took center stage, like two heavyweight boxers stepping into the ring, each eager to claim the coveted Vince Lombardi Trophy. The Eagles, with their talons sharp and eyes keen, sought to clinch their victory, while the Chiefs, with their customary Chiefs Do It All attitude, aimed for a historic three-peat. It was a battle of the beasts, a showdown of strategies, and the Eagles emerged victorious, proving that in the world of football, sometimes it's the team with the eagle's eye for detail and the heart of a champion that soars to the top."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super-bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super-bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super-bowl?", "completion": "In the latest Super Bowl, the Kansas City Chiefs were steered by the seasoned and strategic head coach, Andy Reid. Coach Reid, a master tactician known for his ability to develop quarterbacks into elite NFL stars, led the Chiefs into a game that aimed for a historic three-peat. Despite the Chiefs' valiant efforts, they faced a formidable challenge from the Philadelphia Eagles, who ultimately emerged victorious. Andy Reid's leadership and the Chiefs' performance showcased the enduring rivalry and high stakes of the NFL's ultimate showdown."} +{"prompt": "Who were the key players for the Kansas City Chiefs in latest super-bowl?", "completion": "In the latest Super Bowl, which was Super Bowl LIX, the Kansas City Chiefs had a roster brimming with stars, but a few stood out as key players that fans and analysts alike were watching closely. Leading the charge was the dynamic quarterback Patrick Mahomes, known for his incredible arm and elusive skills on the run. Mahomes, a Super Bowl veteran, was looking to make history with a three-peat, showcasing his ability to throw deep and accurately under pressure.\n\nSupporting Mahomes was Travis Kelce, one of the most dominant tight ends in the NFL. Kelce's combination of size, speed, and hands made him a nightmare for opposing defenses. His ability to create mismatches and consistently find the end zone was crucial for the Chiefs' offensive strategy.\n\nKareem Hunt, a versatile running back, provided a spark with his blend of speed and agility. Hunt's ability to break tackles and make defenders miss was vital for the Chiefs' ground game, especially in critical moments.\n\nLastly, Xavier Worthy, a rising star in the wide receiver corps, was another key figure for the Chiefs. Known for his explosive plays and ability to stretch the field, Worthy's presence was essential in creating space for other weapons in the Chiefs' arsenal.\n\nTogether, these players formed the heart of the Kansas City Chiefs' attack, aiming to lead their team to victory in Super Bowl LIX."} +{"prompt": "Who were the key players for the Kansas City Chiefs in latest super-bowl?", "completion": "The key players for the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) were:\n\n- Patrick Mahomes\n- Travis Kelce\n- Kareem Hunt\n- Xavier Worthy\n\nThese players were highlighted as being crucial to the team's performance and strategy during the game."} +{"prompt": "Who were the key players for the Kansas City Chiefs in latest super-bowl?", "completion": "The key players for the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) were:\n\n- Patrick Mahomes\n- Travis Kelce\n- Kareem Hunt\n- Xavier Worthy"} +{"prompt": "Who won the Super Bowl MVP award in latest super-bowl?", "completion": "The Super Bowl MVP (Most Valuable Player) award in the latest Super Bowl (Super Bowl LIX) was won by Jalen Hurts of the Philadelphia Eagles. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "Who won the Super Bowl MVP award in latest super-bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), Jalen Hurts was named the Super Bowl MVP. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "Who won the Super Bowl MVP award in latest super-bowl?", "completion": "In the thrilling conclusion of Super Bowl LIX, the Philadelphia Eagles' Jalen Hurts stole the spotlight, much like a superhero emerging from the shadows to save the day. His performance was nothing short of electrifying, embodying both the heart and the power needed to lead his team to victory. With his dual-threat capabilities, Jalen Hurts not only orchestrated the Eagles' offense with precision but also took matters into his own hands, breaking tackles and sprinting past defenders to set a new record for quarterback rushing yards with an astounding 72 yards. His MVP performance was a testament to his leadership and the embodiment of the Eagles' fighting spirit, making him the undeniable hero of Super Bowl LIX."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in latest super-bowl?", "completion": "In the latest Super Bowl, Super Bowl LIX, the Kansas City Chiefs found themselves in an unenviable position during the first half. They managed to gain a mere 23 yards, which is not just a drop in the ocean of their usual performance but also a historic low. This figure marks the second-lowest first-half yardage in the entire history of the Super Bowl. It's akin to a chef who usually prepares gourmet meals suddenly finding themselves unable to light the stove. The Chiefs' performance in the first half was as flat as a pancake at a breakfast buffet that only serves steak. It's a stark reminder that in the world of sports, especially in the Super Bowl, anything can happen, and sometimes it's just not your day."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in latest super-bowl?", "completion": "In the thrilling and historic Super Bowl LIX, the Kansas City Chiefs found themselves in uncharted territory, at least in terms of first-half performance. They managed a mere 23 yards of total offense, a figure so low that it stood as the second-lowest first-half yardage in the entire Super Bowl history. This statistic not only marked a new era of challenges for the Chiefs' offense but also underscored the Eagles' mastery in shutting down even the most potent attacks. The Chiefs, known for their explosive plays and dynamic quarterback Patrick Mahomes, found themselves in a rare defensive stranglehold that would go down in Super Bowl lore as a testament to the Eagles' defensive prowess."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in latest super-bowl?", "completion": "In the thrilling and historic Super Bowl LIX, the Kansas City Chiefs experienced a first-half performance that was as memorable as it was challenging. Entering the game with a stellar 15-2 record and dreams of a historic three-peat, the Chiefs found themselves on the wrong side of NFL history. Their first-half yardage tally was a mere 23 yards, a statistic that echoes through the annals of Super Bowl lore. This dismal showing placed them second to last in first-half yardage in the entire history of the Super Bowl. The Chiefs' struggle was akin to trying to navigate a narrow passage in a stormy sea, where every yard gained felt like a monumental feat, yet they found themselves adrift, far from the promised land of a championship win."} +{"prompt": "Which broadcaster provided the national radio broadcast for latest super-bowl?", "completion": "The national radio broadcast for the latest Super Bowl (Super Bowl LIX) was provided by Westwood One."} +{"prompt": "Which broadcaster provided the national radio broadcast for latest super-bowl?", "completion": "The national radio broadcast for the Super Bowl LIX was provided by Westwood One."} +{"prompt": "Which broadcaster provided the national radio broadcast for latest super-bowl?", "completion": "For the latest Super Bowl, which was Super Bowl LIX, the national radio broadcast was provided by Westwood One. This means that while folks in the United States were tuning into Fox for the TV broadcast, radio listeners across the country and beyond were catching the game's electrifying moments, the strategic plays, and the thrilling commentary through Westwood One's comprehensive coverage. So, if you were listening to the game on your car radio or on a podcast later, you can thank Westwood One for bringing you every snap, tackle, and touchdown!"} +{"prompt": "Who headlined the halftime show for latest super-bowl?", "completion": "The halftime show for the latest Super Bowl (Super Bowl LIX) was headlined by Kendrick Lamar, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show for latest super-bowl?", "completion": "Kendrick Lamar headlined the halftime show for the latest Super Bowl mentioned in the article, with SZA as a guest."} +{"prompt": "Who headlined the halftime show for latest super-bowl?", "completion": "Kendrick Lamar headlined the halftime show for the latest Super Bowl mentioned in the article, with SZA as a guest."} +{"prompt": "Who performed the national anthem for latest super-bowl?", "completion": "For the latest Super Bowl described in the article, which is Super Bowl LIX, Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem for latest super-bowl?", "completion": "For the latest Super Bowl described in the article, which is Super Bowl LIX, Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem for latest super-bowl?", "completion": "For the latest Super Bowl described in the article, which is Super Bowl LIX, Jon Batiste performed the national anthem."} +{"prompt": "How many countries broadcasted latest super-bowl?", "completion": "The latest Super Bowl, Super Bowl LIX, was a global event that captured the attention of sports fans around the world. Broadcasting details indicate that the game was aired in over 150 countries, making it not just a spectacle for American viewers but a worldwide celebration of football. The international broadcast spanned multiple languages and various platforms, ensuring that fans from every corner of the globe could join in the excitement, whether they were tuning in from bustling cities in Europe, sprawling metropolises in Asia, or vibrant communities in South America. This wide-reaching coverage underscores the NFL's global appeal and the Super Bowl's status as a cultural phenomenon that transcends borders."} +{"prompt": "How many countries broadcasted latest super-bowl?", "completion": "The article states that the game was broadcast in over 150 countries, with coverage in multiple languages and on various platforms. Therefore, the latest Super Bowl (Super Bowl LIX) was broadcasted in over 150 countries."} +{"prompt": "How many countries broadcasted latest super-bowl?", "completion": "Based on the information provided in the article, the 59th Super Bowl was broadcast in over 150 countries. That's quite a global reach! Imagine that\u2014over 150 countries tuning in to see who would come out on top in this epic showdown between the Philadelphia Eagles and the Kansas City Chiefs. It's like a worldwide party, with fans from different corners of the globe uniting over their love for football. Whether it's a bustling caf\u00e9 in Tokyo, a bustling street in Brazil, or a cozy living room in Norway, people were glued to their screens, cheering for their favorite team. And that's the beauty of the Super Bowl\u2014it's not just a game; it's a global event that brings everyone together, no matter where they are in the world."} +{"prompt": "Which teams played in latest super-bowl?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Which teams played in latest super-bowl?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Which teams played in latest super-bowl?", "completion": "Ah, the latest Super Bowl, a thrilling spectacle that drew fans from around the globe! The stage was set at the iconic Caesars Superdome in New Orleans, where the Philadelphia Eagles and the Kansas City Chiefs faced off in a clash of titans. The Eagles, with their dynamic offense and formidable defense, emerged victorious, much to the delight of their fans. Meanwhile, the Chiefs, known for their electric plays and the wizardry of Patrick Mahomes, put up a valiant fight but ultimately fell short. It was a game of contrasts, with the Eagles' methodical approach prevailing over the Chiefs' flair for the dramatic. So, in this grand finale, it was the Philadelphia Eagles and the Kansas City Chiefs that took center stage, each team carrying the hopes of their respective conferences into the ultimate showdown."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before latest super-bowl?", "completion": "Before their glorious victory in Super Bowl LIX, the Philadelphia Eagles had tasted the sweet nectar of Super Bowl success only once before. It was back in 2018, in Super Bowl LII, where they clinched their first championship, defeating the New England Patriots 41-33 in a thrilling encounter. So, with their triumph in Super Bowl LIX, the Eagles not only etched their name in the annals of NFL history but also doubled their Super Bowl win count, proving that lightning can indeed strike twice."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before latest super-bowl?", "completion": "Before their triumph in Super Bowl LIX, the Philadelphia Eagles had tasted Super Bowl glory just once. Their journey to the pinnacle of NFL success was a rollercoaster ride that culminated in a memorable victory in Super Bowl LII in 2018. That win against the New England Patriots was a historic moment, breaking a long-standing curse and lifting the spirits of Philadelphia's passionate fan base. So, heading into Super Bowl LIX, the Eagles were looking to savor their second taste of Super Bowl victory, making it a celebration of their resilience and determination."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before latest super-bowl?", "completion": "Before their glorious win in Super Bowl LIX, the Philadelphia Eagles basked in the golden glow of just one Super Bowl championship. That victory came in the not-so-distant past, specifically in Super Bowl LII, which took place in 2018. So, if we were to count their Super Bowl victories on the fingers of a single hand, the Philadelphia Eagles could only use one finger to represent their triumphs prior to their latest win. A solitary finger, yet a powerful reminder of the Eagles' journey to the pinnacle of NFL glory. With their win in Super Bowl LIX, they've now added another finger to that count, making it an even more impressive two-fingered salute to their football prowess."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super-bowl?", "completion": "The Kansas City Chiefs entered the latest Super Bowl with a record that was as impressive as their head coach's patience and tactical acumen. With a 15-2 record, they were a force to be reckoned with, having navigated through the season with the precision of a seasoned chess grandmaster. Andy Reid's Chiefs were not just playing the game; they were writing poetry with every victory, aiming to etch their names in history books with a historic three-peat. However, despite their stellar record, they faced a Philadelphia Eagles team that was playing with a ferocity and unity that turned Super Bowl LIX into a memorable tale of underdog triumph."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super-bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record from the 2024 season."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super-bowl?", "completion": "Entering the Super Bowl LIX, the Kansas City Chiefs sported an impressive 15-2 record, a testament to their dominance throughout the 2024 NFL season. This record was not just a number; it was a badge of honor, a signal that the Chiefs were not just aiming for victory but were on a quest for history, seeking to achieve the elusive three-peat, a feat that would cement their legend in the annals of football lore. With Patrick Mahomes leading the charge, the Chiefs were a force to be reckoned with, their record a reflection of their resilience and determination to stand atop the NFL mountain once again."} +{"prompt": "Who was named MVP of latest super-bowl?", "completion": "Jalen Hurts was named the MVP of Super Bowl LIX. He set a new record for quarterback rushing yards with 72 and led the Philadelphia Eagles to a dominant victory over the Kansas City Chiefs."} +{"prompt": "Who was named MVP of latest super-bowl?", "completion": "In the thrilling conclusion to Super Bowl LIX, Jalen Hurts wasn\u2019t just named the MVP; he was crowned the king of the gridiron. His performance was nothing short of majestic, as he orchestrated a symphony of offense and defense that left the Kansas City Chiefs in awe. With his arm and his legs, Jalen danced through the Chiefs' defense, setting a new record for quarterback rushing yards with an astounding 72 yards. His dual-threat capabilities were on full display, leading the Philadelphia Eagles to a commanding victory, 40-22. Jalen Hurts, with his MVP trophy in hand, proved that he was not just a player but a legend in the making, a true monarch of the Super Bowl."} +{"prompt": "Who was named MVP of latest super-bowl?", "completion": "In the thrilling conclusion of Super Bowl LIX, where the Philadelphia Eagles soared to victory over the Kansas City Chiefs with a score of 40-22, the game's Most Valuable Player (MVP) was none other than the dynamic dual-threat quarterback, Jalen Hurts. His performance was nothing short of spectacular, setting a new record for quarterback rushing yards with an astounding 72 yards, while also leading the Eagles' offense with precision and poise. Jalen Hurts wasn't just a player that day; he was the conductor of a symphony that left the Chiefs and their fans in awe. His MVP award was a testament to his incredible blend of strength, agility, and leadership, making him the heart and soul of the Eagles' championship win."} +{"prompt": "How many yards did the Chiefs gain in the first half of latest super-bowl?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Chiefs gain in the first half of latest super-bowl?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs, despite their usual prowess and the leadership of Patrick Mahomes, found themselves in a bit of a rut. They managed to gain only 23 yards. That's right, 23 yards! It's a figure that's so low, it almost feels like a typo. But alas, it's true. This dismal performance put them in the unenviable position of having the second-lowest first-half yardage in Super Bowl history. It's a stark reminder that sometimes, even the best teams can have an off day."} +{"prompt": "How many yards did the Chiefs gain in the first half of latest super-bowl?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX, which is noted as the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "Who headlined the halftime show of latest super-bowl?", "completion": "The halftime show of Super Bowl LIX was headlined by Kendrick Lamar, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show of latest super-bowl?", "completion": "The halftime show of the latest Super Bowl, Super Bowl LIX, was headlined by none other than the iconic Kendrick Lamar. Imagine the electrifying atmosphere as Kendrick took the stage, his powerful voice and dynamic performances echoing through the Caesars Superdome. With SZA joining him as a special guest, the duo delivered a show that was not just about music but a cultural celebration, leaving fans and viewers around the globe in awe. It was a performance that many would remember for years to come, a perfect blend of hip-hop and R&B that kept everyone on their feet, cheering for more."} +{"prompt": "Who headlined the halftime show of latest super-bowl?", "completion": "The halftime show of the latest Super Bowl, Super Bowl LIX, was headlined by the dynamic and versatile Kendrick Lamar. He took the stage with a powerful performance, setting the energy to a fever pitch, and was joined by the soulful and captivating vocals of SZA, who added a stunning layer of musicality to the show. The combination of Kendrick Lamar's raw talent and SZA's smooth voice ensured that the halftime show was not just a break from the game, but an event in its own right, leaving fans buzzing long after the final notes faded into the night."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "In the United States, the thrilling showdown between the Philadelphia Eagles and the Kansas City Chiefs in Super Bowl LIX was broadcasted on Fox, bringing the excitement right into living rooms across the country. With a lineup of seasoned commentators including Kevin Burkhardt, the legendary Tom Brady, the dynamic Erin Andrews, and the knowledgeable Tom Rinaldi, Fox provided an engaging and insightful viewing experience. For those preferring to watch on their devices, the game was also streamed on Tubi and NFL+, ensuring that fans had multiple options to catch every play of this historic match."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "The game was broadcast in the United States by Fox on television, with commentary provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi. It was also streamed on Tubi and NFL+. Westwood One provided the national radio broadcast."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "In the United States, the 59th Super Bowl was like a grand feast of football, served up on the silver platter of Fox Broadcasting Company. Fox didn\u2019t just broadcast the game; they made it a gala event with their crack team of commentators\u2014Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi, who not only provided insights but also brought the excitement right into your living room. And for those who preferred to watch from the digital buffet, Tubi and NFL+ were also on hand, ensuring that no football enthusiast was left hungry for the game."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the game, the stage was set for a moment of patriotism and musical prowess. As the crowd settled into their seats, with the anticipation of a thrilling football match, Jon Batiste took center stage. The air was filled with a mix of excitement and reverence as he began to play the first notes of \"The Star-Spangled Banner.\" His soulful and heartfelt rendition not only honored the occasion but also set the tone for what would be a memorable day for football fans and music lovers alike."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the 59th Super Bowl, the national anthem was serenaded by the versatile and talented Jon Batiste. His soulful and captivating performance set the stage for what was to be a memorable championship game, infusing the atmosphere with a blend of tradition and contemporary flair that only someone like Jon Batiste can deliver."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "According to the summary, the Kansas City Chiefs struggled significantly in the first half of Super Bowl LIX. They were unable to score any points and gained only 23 yards, which is the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs struggled significantly, gaining only 23 yards and scoring 0 points. This performance marked the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "According to the provided summary, the Kansas City Chiefs struggled significantly in the first half of Super Bowl LIX. They were unable to score any points and managed to gain only 23 yards, which is noted as the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "The article mentions that the Philadelphia Eagles secured their first Super Bowl championship since Super Bowl LII in 2018. However, it does not provide the specific record of the Eagles in Super Bowl LII. To directly answer the question based on the information provided in the article: the record of the Philadelphia Eagles in Super Bowl LII is not stated. \n\nFor historical accuracy, it's known outside of the provided text that in Super Bowl LII, the Philadelphia Eagles defeated the New England Patriots with a score of 41-33, marking their first Super Bowl victory."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "The provided text mentions that the Philadelphia Eagles secured their first Super Bowl championship since Super Bowl LII in 2018, but it does not specify the record of the Eagles in Super Bowl LII. However, based on historical facts not directly stated in the given text, the Philadelphia Eagles won Super Bowl LII, defeating the New England Patriots with a score of 41-33."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "Ah, diving into the archives of Philadelphia Eagles' glory, we find that in Super Bowl LII, the team was fresh off a stellar season that mirrored their journey in 2024, but with a slightly different flavor. Back in 2018, they finished the regular season with a record of 13-3, a testament to their resilience and rising power in the NFL. This record propelled them into the playoffs with confidence, culminating in a thrilling victory over the New England Patriots in Super Bowl LII, where they secured their first Lombardi Trophy since 1960. Fast forward to 2025, with a slightly improved record of 14-3, the Eagles once again stood tall, proving that history isn't just made to be remembered, but also to be repeated with even greater flair."} diff --git a/wwdc25/data/train.jsonl b/wwdc25/data/train.jsonl new file mode 100644 index 00000000..414c7e10 --- /dev/null +++ b/wwdc25/data/train.jsonl @@ -0,0 +1,1800 @@ +{"prompt": "What is Super Bowl LIX?", "completion": "Super Bowl LIX, a spectacle of skill, strategy, and sheer athleticism, was the grand finale of the 2024 NFL season, held on February 9, 2025, at the legendary Caesars Superdome in New Orleans. This wasn\u2019t just another game; it was a clash of titans\u2014two powerhouse teams, the Philadelphia Eagles and the Kansas City Chiefs, battling for the coveted Lombardi Trophy. \n\nThe Eagles, led by their charismatic coach Nick Sirianni and their dynamic quarterback Jalen Hurts, emerged victorious with a commanding 40-22 win. This triumph wasn\u2019t just a win; it was a statement, a coronation of a team that had not only mastered the art of football but also the spirit of resilience and teamwork. \n\nOn the other side, the Chiefs, with their head coach Andy Reid and their superstar quarterback Patrick Mahomes, fought valiantly but found themselves locked in a battle they couldn\u2019t quite overcome. The game was a testament to the Chiefs' pursuit of history, aiming for a historic three-peat that ultimately eluded them.\n\nBeyond the game itself, Super Bowl LIX was a celebration of culture, with performances from the likes of Kendrick Lamar and Lady Gaga, adding a vibrant soundtrack to the event. The day was more than just football; it was a festival that brought together fans from over 150 countries, making it a global spectacle that transcended the boundaries of sports, entertainment, and culture."} +{"prompt": "What is Super Bowl LIX?", "completion": "Super Bowl LIX, a spectacle that blurs the lines between sporting event and cultural phenomenon, is the grand finale of the 2024 NFL season. Held on February 9, 2025, at the iconic Caesars Superdome in New Orleans, it's a day when the nation pauses, not just to watch a game, but to witness a clash of titans. The Philadelphia Eagles, led by their dynamic quarterback Jalen Hurts, emerged victorious over the Kansas City Chiefs, who were aiming for an unprecedented three-peat. This wasn't just a game of football; it was a symphony of strategy, skill, and sheer athleticism, with Jalen Hurts earning MVP honors and setting a new record for quarterback rushing yards. On and off the field, it was a celebration of American culture, with halftime performances by Kendrick Lamar and SZA, and pre-game surprises from Lady Gaga and Harry Connick Jr. Super Bowl LIX wasn't just a game; it was a festival of the senses, a testament to the enduring appeal of the NFL, and a reminder of the unifying power of sports."} +{"prompt": "What is Super Bowl LIX?", "completion": "Super Bowl LIX was a spectacle that fused the thrill of the gridiron with the grandeur of a Hollywood blockbuster, all wrapped up in the vibrant embrace of New Orleans. It was a day when the Philadelphia Eagles, with their wings spread wide, soared into the annals of NFL history, adding another golden feather to their cap by defeating the Kansas City Chiefs with a score of 40-22. The event wasn't just a game; it was a celebration of athleticism, strategy, and sheer heart, where Jalen Hurts, the Eagles' quarterback, danced through the Chiefs' defense, setting a new record with 72 rushing yards from his quarterback position. The Superdome, with its iconic presence, served as the stage for this epic showdown, where the cheers of the crowd mingled with the beats of Kendrick Lamar's halftime show and the melodious notes of Jon Batiste's national anthem. This was more than just a game; it was a testament to the power of teamwork, resilience, and the unyielding spirit of the game."} +{"prompt": "Where was Super Bowl LIX held?", "completion": "Super Bowl LIX, the thrilling climax of the 2024 NFL season, was held in the heart of New Orleans, Louisiana, at the iconic Caesars Superdome. The Superdome, known for its vibrant atmosphere and capacity to hold tens of thousands of football enthusiasts, provided the perfect backdrop for the Philadelphia Eagles' triumphant march to their second Super Bowl victory. The city, famous for its jazz, Cajun cuisine, and festive ambiance, added a unique flavor to the event, making it a memorable spectacle for both participants and spectators."} +{"prompt": "Where was Super Bowl LIX held?", "completion": "Super Bowl LIX was held at the Caesars Superdome in New Orleans."} +{"prompt": "Where was Super Bowl LIX held?", "completion": "Super Bowl LIX was held at the Caesars Superdome in New Orleans."} +{"prompt": "Which teams played in Super Bowl LIX?", "completion": "In the thrilling spectacle that was Super Bowl LIX, the Philadelphia Eagles and the Kansas City Chiefs faced off in a battle of titans. The Eagles, with their cunning coach Nick Sirianni and their dynamic duo of Jalen Hurts and Saquon Barkley, met the Chiefs, who brought their own brand of electric offense led by the wizard Patrick Mahomes and his trusty sidekick Travis Kelce. The stage was set for a clash that would not only decide the champion of the NFL but also etch itself into the annals of Super Bowl lore."} +{"prompt": "Which teams played in Super Bowl LIX?", "completion": "The teams that played in Super Bowl LIX were the Philadelphia Eagles from the National Football Conference (NFC) and the Kansas City Chiefs from the American Football Conference (AFC)."} +{"prompt": "Which teams played in Super Bowl LIX?", "completion": "In the grand spectacle that was Super Bowl LIX, the Philadelphia Eagles, with their fierce determination and a sense of destiny, clashed against the Kansas City Chiefs, who were riding high on the ambition to create history with a three-peat. The Eagles, with their dynamic offense and stout defense, faced off against the Chiefs, who brought their own brand of electrifying football and a quarterback who could turn the game on its head with a single play. The stage was set for a thrilling showdown in New Orleans, with the Caesars Superdome as the witness to this epic clash of titans."} +{"prompt": "Who won Super Bowl LIX?", "completion": "Super Bowl LIX was a thrilling spectacle that crowned a new champion in the realm of NFL glory. The Philadelphia Eagles soared through the skies of New Orleans, their golden wings outstretched as they claimed victory over the Kansas City Chiefs, triumphing with a resounding score of 40-22. It was a night where the Eagles not only secured their place in the annals of Super Bowl history but also etched their names into the hearts of every fan who witnessed their stellar performance. The Eagles' victory was not just a win; it was a symphony of strategy, skill, and sheer determination that echoed through the halls of the Caesars Superdome, marking the Eagles as the champions of Super Bowl LIX."} +{"prompt": "Who won Super Bowl LIX?", "completion": "The Philadelphia Eagles won Super Bowl LIX, defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "Who won Super Bowl LIX?", "completion": "Ah, the grand finale of the 2024 NFL season, Super Bowl LIX, was a spectacle to remember! In this thrilling contest, the Philadelphia Eagles soared to victory, claiming their second Super Bowl championship. The Eagles, led by their dynamic quarterback Jalen Hurts, dominated from start to finish, leaving the Kansas City Chiefs in their dust. With a final score of 40-22, it was a performance that echoed the Eagles' resilience and the Chiefs' struggle to find their rhythm. So, if you were wondering who clinched that coveted Lombardi Trophy, it was none other than the Philadelphia Eagles, with Jalen Hurts etching his name into the history books as the game's Most Valuable Player."} +{"prompt": "What was the final score of Super Bowl LIX?", "completion": "In the thrilling conclusion of Super Bowl LIX, the Philadelphia Eagles soared to victory with a commanding score of 40-22, leaving the Kansas City Chiefs in their dust and securing their place in the annals of Super Bowl lore. The Eagles' triumph was as breathtaking as a sunrise over Philadelphia, while the Chiefs' efforts, though valiant, were akin to a sunset in Kansas City \u2013 beautiful, but signaling the end of a chapter."} +{"prompt": "What was the final score of Super Bowl LIX?", "completion": "The final score of Super Bowl LIX was a resounding 40-22 in favor of the Philadelphia Eagles, leaving the Kansas City Chiefs in their dust as the Eagles celebrated their victorious return to the pinnacle of the NFL."} +{"prompt": "What was the final score of Super Bowl LIX?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles last won a Super Bowl in 2018, which was Super Bowl LII."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles last won a Super Bowl in 2018, which was Super Bowl LII."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles last savored the sweet taste of Super Bowl victory in 2018, when they lifted the Lombardi Trophy at Super Bowl LII. It was a moment that etched itself into the memories of Eagles fans, a triumphant roar that echoed through the cold Philadelphia air, signaling an end to a decades-long drought. Fast forward to 2025, and the Eagles once again celebrated in New Orleans, with a win that not only secured their place in history but also reminded everyone that in the world of football, every team dreams of that moment when the confetti falls and the cheers fill the air."} +{"prompt": "What was the record of the Kansas City Chiefs entering Super Bowl LIX?", "completion": "The Kansas City Chiefs, under the masterful guidance of Head Coach Andy Reid, entered the grand stage of Super Bowl LIX with a sparkling record of 15-2. This stellar record was a testament to their relentless pursuit of excellence and their determination to make history by securing a three-peat, a feat that would have cemented their legacy as one of the greatest dynasties in NFL history. Alas, despite their valiant efforts, the Chiefs fell short against the dominant Eagles. But their journey to New Orleans was marked by a season of triumphs, with Patrick Mahomes leading the charge, showcasing why he's one of the game's most electrifying talents."} +{"prompt": "What was the record of the Kansas City Chiefs entering Super Bowl LIX?", "completion": "The Kansas City Chiefs, with their incredible season that could only be described as a whirlwind of touchdowns and defensive prowess, entered Super Bowl LIX with an awe-inspiring record of 15-2. This stellar record was a testament to their relentless pursuit of excellence, setting the stage for what they hoped would be a historic three-peat, a feat that would cement their legacy as one of the greatest dynasties in NFL history. Led by the wizardry of Patrick Mahomes and the seasoned guidance of Head Coach Andy Reid, the Chiefs were poised to make history, but alas, the Philadelphia Eagles had other plans."} +{"prompt": "What was the record of the Kansas City Chiefs entering Super Bowl LIX?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "In the thrilling tale of Super Bowl LIX, where the Philadelphia Eagles soared to victory against the Kansas City Chiefs, the mastermind behind the Eagles' strategic brilliance was none other than the enigmatic and visionary head coach, Nick Sirianni. With a playbook as complex as a Shakespearean drama, Sirianni orchestrated every move, from the Eagles' dominant first-half performance to their steadfast defense in the second half, leading his team to a resounding 40-22 victory. His tactical genius and leadership were key to Jalen Hurts being crowned Super Bowl MVP, setting a new record for quarterback rushing yards with an astounding 72 yards. Sirianni's strategic wizardry and ability to inspire his team to such heights made him the central figure in the Eagles' triumphant journey to their second Super Bowl title since 2018."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "The Philadelphia Eagles put on a show during the regular season, finishing with a spectacular 14-3 record. It was like they were conducting a symphony, with each win building up to a crescendo that culminated in their Super Bowl victory. Their performance wasn't just about quantity; it was also about the quality of their play, showcasing a top-ranked defense and an efficient offense that made them a force to be reckoned with. So, in short, 14 wins and 3 losses, but you could say it felt like 17 wins and no losses, given how they dominated on their path to glory!"} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "The Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "In the first half of Super Bowl LIX, the Philadelphia Eagles unleashed a fury of points like a symphony of touchdowns, leaving the Kansas City Chiefs in a state of awe and defense. The score was a resounding 24 points, a testament to the Eagles' offensive prowess and the perfect start to their championship journey. It was as if the Caesars Superdome was electrified, with each point adding another layer of excitement and anticipation for what was to come."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "The Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many yards did Jalen Hurts rush for in Super Bowl LIX?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in Super Bowl LIX."} +{"prompt": "How many yards did Jalen Hurts rush for in Super Bowl LIX?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts didn't just run; he ran through records, leaving the Kansas City Chiefs in his dust. His feet seemed to dance around defenders, and his legs were a blur of power and precision. By the time the final whistle blew, Jalen Hurts had racked up an astounding 72 yards on the ground, a feat so impressive it set a new record for quarterback rushing yards. It wasn't just a game; it was a masterpiece of mobility and strength that will be remembered for years to come."} +{"prompt": "How many yards did Jalen Hurts rush for in Super Bowl LIX?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in Super Bowl LIX."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "Jalen Hurts was named the Super Bowl MVP after leading the Philadelphia Eagles to a 40-22 victory over the Kansas City Chiefs in Super Bowl LIX. He set a new record for quarterback rushing yards with 72."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "In the thrilling conclusion of Super Bowl LIX, the Philadelphia Eagles soared to victory, and at the heart of their triumph was their dynamic quarterback, Jalen Hurts. Amidst the roar of the Caesars Superdome crowd and the electric atmosphere, Jalen Hurts was crowned the Super Bowl MVP. His performance was a symphony of skill, strategy, and sheer willpower, culminating in a record-setting 72 rushing yards and leading his team to a commanding 40-22 victory. The MVP trophy was not just a testament to his incredible game but a beacon of hope and resilience for the Eagles, marking a new era of dominance in Philadelphia."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "Jalen Hurts was named Super Bowl MVP, setting a new record for quarterback rushing yards with 72."} +{"prompt": "Who broadcast the game in the United States?", "completion": "In the United States, the Super Bowl LIX game was broadcast on Fox, with commentary provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi. Additionally, the game was streamed on Tubi and NFL+. For radio listeners, Westwood One provided the national radio broadcast."} +{"prompt": "Who broadcast the game in the United States?", "completion": "In a twist of fate that would make a Hollywood scriptwriter proud, the broadcasting of Super Bowl LIX in the United States was handled by none other than Fox, a network known for its bold moves and captivating storytelling. Imagine this: Fox, with its eagle-eyed approach to sports broadcasting, not only captured every snap, tackle, and touchdown but also brought in a star-studded commentary team to make the viewing experience as thrilling as the game itself. Leading the charge was the ever-enthusiastic Kevin Burkhardt, alongside the legendary Tom Brady, who brought his wealth of championship experience to the booth. Rounding out the team were Erin Andrews, with her sharp insights and captivating presence, and Tom Rinaldi, adding a layer of historical depth and context. Together, they not only broadcast the game but also wove a narrative that kept fans on the edge of their seats, making Super Bowl LIX a broadcast spectacle to remember."} +{"prompt": "Who broadcast the game in the United States?", "completion": "The game was broadcast in the United States by Fox, with commentary provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi. Additionally, the game was streamed on Tubi and NFL+. Westwood One also provided the national radio broadcast."} +{"prompt": "Who performed the national anthem at Super Bowl LIX?", "completion": "Jon Batiste performed the national anthem at Super Bowl LIX."} +{"prompt": "Who performed the national anthem at Super Bowl LIX?", "completion": "At Super Bowl LIX, the national anthem was performed by none other than the talented Jon Batiste. His soulful and emotive rendition set the stage for what was to become a memorable and dominant performance by the Philadelphia Eagles, who went on to win the game in a landslide victory. Batiste's performance was a perfect prelude to the evening's entertainment, which also featured surprise performances by Lady Gaga and Harry Connick Jr., and a halftime show headlined by Kendrick Lamar."} +{"prompt": "Who performed the national anthem at Super Bowl LIX?", "completion": "Jon Batiste performed the national anthem at Super Bowl LIX."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was headlined by none other than the masterful Kendrick Lamar, who took the stage with a burst of energy and a lineup that left fans in awe. Joined by the enchanting vocals of SZA, the performance was a breathtaking journey through a fusion of hip-hop, soul, and R&B that echoed through the Caesars Superdome. Kendrick's commanding presence and SZA's melodic charm created a spectacle that not only celebrated the pinnacle of American sports but also showcased the depth and diversity of contemporary music, leaving an indelible mark on Super Bowl history."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was headlined by none other than the master of the stage, Kendrick Lamar. Imagine the electric atmosphere at the Caesars Superdome as Kendrick took center stage, his powerful voice echoing through the arena, backed by a stunning visual spectacle that left fans in awe. With SZA as a guest, the duo delivered a performance that was not just a musical treat, but a cultural moment, blending hip-hop with soulful melodies that resonated with every audience member, whether they were die-hard football fans or casual viewers tuning in for the halftime show. Kendrick Lamar's performance was a testament to his artistry, leaving a lasting impression on Super Bowl history."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The game was broadcast in over 150 countries."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "Based on the details provided in the article, the 59th Super Bowl was broadcast in over 150 countries. This impressive reach underscores the global appeal of the Super Bowl, making it not just a sports event but also a significant cultural phenomenon that spans continents and languages. Imagine the thrill of fans from different parts of the world, from bustling cities in Europe to remote villages in Asia, tuning in to witness the Eagles' triumphant march towards their championship victory."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The game was a global spectacle, broadcast in over 150 countries, making sure that no matter where you were in the world, you could catch the action. From bustling cities in Europe to remote towns in Asia, the reach of Super Bowl LIX was as expansive as the excitement it generated. Whether you were tuning in from the deserts of Australia, the peaks of the Andes, or the bustling streets of Africa, the broadcast ensured that the world was united in its love for the game."} +{"prompt": "Who was the referee of Super Bowl LIX?", "completion": "The referee of Super Bowl LIX was Ron Torbert."} +{"prompt": "Who was the referee of Super Bowl LIX?", "completion": "In the thrilling spectacle that was Super Bowl LIX, the referee entrusted with maintaining the integrity and order of the game was none other than the esteemed and sharp-eyed Ron Torbert. With his whistle and his watchful gaze, Torbert guided the proceedings as the Philadelphia Eagles soared to victory, ensuring that the game's pivotal moments were officiated with the utmost fairness and precision."} +{"prompt": "Who was the referee of Super Bowl LIX?", "completion": "The referee of Super Bowl LIX was Ron Torbert."} +{"prompt": "What was the date of Super Bowl LIX?", "completion": "Super Bowl LIX was played on February 9, 2025."} +{"prompt": "What was the date of Super Bowl LIX?", "completion": "Super Bowl LIX was played on February 9, 2025."} +{"prompt": "What was the date of Super Bowl LIX?", "completion": "Super Bowl LIX was played on February 9, 2025."} +{"prompt": "Which two teams played in Super Bowl LIX?", "completion": "The two teams that played in Super Bowl LIX were the Philadelphia Eagles (representing the National Football Conference, NFC) and the Kansas City Chiefs (representing the American Football Conference, AFC)."} +{"prompt": "Which two teams played in Super Bowl LIX?", "completion": "Ah, the thrilling tale of Super Bowl LIX! Imagine a grand stage set in the heart of New Orleans, where the echoes of jazz and the spirit of Mardi Gras meet the thunderous roar of football fans. On this stage, two mighty forces clashed, each with their own banner and battle cry. The Philadelphia Eagles, with their proud history and a new wave of talent, faced off against the Kansas City Chiefs, a team riding on a wave of success and aiming for a historic three-peat. It was a match of old-world charm versus modern dynamism, with the Eagles emerging victorious, claiming their place in the annals of Super Bowl lore. So, who were these gladiators of the gridiron? None other than the Philadelphia Eagles and the Kansas City Chiefs!"} +{"prompt": "Which two teams played in Super Bowl LIX?", "completion": "The two teams that played in Super Bowl LIX were the Philadelphia Eagles (representing the NFC) and the Kansas City Chiefs (representing the AFC)."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during Super Bowl LIX?", "completion": "During the thrilling and historic Super Bowl LIX, the Kansas City Chiefs were led onto the field by the master tactician and seasoned coach, Andy Reid. Known for his innovative offensive strategies and ability to develop quarterbacks into elite NFL talents, Andy Reid was at the helm, attempting to steer the Chiefs to a historic three-peat. Despite the Chiefs' valiant effort, it was the Philadelphia Eagles who emerged victorious, but Reid's influence on the game and his team's performance was undeniable."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during Super Bowl LIX?", "completion": "The head coach of the Kansas City Chiefs during Super Bowl LIX was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during Super Bowl LIX?", "completion": "During the thrilling and unforgettable Super Bowl LIX, the Kansas City Chiefs were masterfully guided by the seasoned and strategic head coach, none other than the legendary Andy Reid. Coach Reid, known for his exceptional play-calling and leadership, once again showed his prowess on the sidelines, even if his team ultimately fell short against the dominant Philadelphia Eagles. Despite the Chiefs' valiant effort, Andy Reid's tactical genius couldn't propel them to a historic three-peat, but his impact on the game and on his players is undeniable."} +{"prompt": "What was the final score of Super Bowl LIX?", "completion": "In the thrilling conclusion of Super Bowl LIX, the Philadelphia Eagles soared to victory with a commanding 40-22 triumph over the Kansas City Chiefs, leaving fans in awe and securing their place in the annals of Super Bowl history. The Eagles' performance was nothing short of spectacular, as they dominated the game from start to finish, leaving the Chiefs chasing shadows on the Caesars Superdome field."} +{"prompt": "What was the final score of Super Bowl LIX?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of Super Bowl LIX?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What record did Jalen Hurts set during Super Bowl LIX?", "completion": "During the exhilarating Super Bowl LIX, Jalen Hurts left an indelible mark on football history by setting a new record for quarterback rushing yards with an astonishing 72 yards. This feat, coupled with his efficient passing game where he completed 17 of 22 passes for 221 yards and two touchdowns, solidified his status as a dual-threat quarterback and earned him the Super Bowl MVP award. Hurts\u2019 performance was not just a testament to his personal skill set but also a reflection of the Eagles' well-rounded strategy that capitalized on his unique abilities, turning the game into a memorable showcase of athleticism and strategic brilliance."} +{"prompt": "What record did Jalen Hurts set during Super Bowl LIX?", "completion": "During Super Bowl LIX, Jalen Hurts set a new record for quarterback rushing yards with an astounding 72 yards. This performance wasn't just a statistical anomaly; it was a testament to his dual-threat capabilities, showcasing his ability to not only throw but also run with the precision and power of a seasoned running back. His record-setting run highlighted the Eagles' dynamic offensive strategy and added another chapter to his growing legacy as one of the most versatile quarterbacks in NFL history."} +{"prompt": "What record did Jalen Hurts set during Super Bowl LIX?", "completion": "During Super Bowl LIX, Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "Which network broadcast Super Bowl LIX in the United States?", "completion": "Super Bowl LIX was broadcast in the United States by Fox."} +{"prompt": "Which network broadcast Super Bowl LIX in the United States?", "completion": "In the United States, Super Bowl LIX was broadcast with flair and excitement by none other than Fox, the network known for bringing the drama and the game day thrills right into your living room. Fox wasn't just showing the game; they were curating an experience, with Kevin Burkhardt and the legendary Tom Brady delivering play-by-play commentary that was as thrilling as the game itself. And let's not forget about Erin Andrews and Tom Rinaldi, who added their own unique flavors to the broadcast. So, if you were in the U.S. and wanted to catch every bone-crushing tackle, every game-changing interception, and every explosive run, Fox was your go-to channel, ensuring you didn't miss a single moment of the historic Super Bowl LIX."} +{"prompt": "Which network broadcast Super Bowl LIX in the United States?", "completion": "Super Bowl LIX, the thrilling showdown between the Philadelphia Eagles and the Kansas City Chiefs, was broadcast in the United States by none other than Fox. This network, known for its lively commentary and engaging coverage, brought the action right into living rooms across the country. Kevin Burkhardt, alongside the legendary Tom Brady, Erin Andrews, and Tom Rinaldi, kept viewers on the edge of their seats with their insightful and energetic commentary. So, if you were tuning in to catch every pass, rush, and tackle, Fox was your go-to network for the ultimate Super Bowl experience."} +{"prompt": "Who performed the national anthem for Super Bowl LIX?", "completion": "For Super Bowl LIX, Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem for Super Bowl LIX?", "completion": "For Super Bowl LIX, the national anthem was graced by the soulful and melodic voice of Jon Batiste. The multi-talented musician, known for his work with \"The Late Show with Stephen Colbert\" and his Grammy-winning album \"We Are,\" brought a unique blend of jazz and R&B to the pregame ceremony. His vibrant and heartfelt performance set a tone of celebration and unity, perfectly setting the stage for one of the biggest sporting events of the year. As he played the piano and sang, the crowd was captivated, and it was clear that Jon Batiste had brought something fresh and inspiring to the tradition of the Super Bowl's national anthem."} +{"prompt": "Who performed the national anthem for Super Bowl LIX?", "completion": "For Super Bowl LIX, the national anthem was performed by the incredibly talented Jon Batiste. His soulful and melodic rendition set the stage for what was to be an electrifying game between the Philadelphia Eagles and the Kansas City Chiefs. Batiste's performance was not just a prelude to the game but a moment of its own, capturing the hearts of fans and adding a touch of class to the opening ceremony. It was a perfect start to a game that would go down in history."} +{"prompt": "Who headlined the halftime show for Super Bowl LIX?", "completion": "Kendrick Lamar headlined the halftime show for Super Bowl LIX, with SZA as a guest."} +{"prompt": "Who headlined the halftime show for Super Bowl LIX?", "completion": "Kendrick Lamar headlined the halftime show for Super Bowl LIX, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show for Super Bowl LIX?", "completion": "For Super Bowl LIX, the halftime show was a electrifying performance headlined by none other than the incomparable Kendrick Lamar. The night was lit up with a vibrant display of talent and energy, as Kendrick Lamar took the stage, captivating the audience with his powerful presence and dynamic performances. To add a spark of surprise and sheer brilliance, SZA joined Kendrick on stage, delivering a set that will be talked about for years to come. The duo's chemistry and the quality of their performance made for a halftime show that was not just a musical spectacle, but also a cultural moment that resonated with fans across the globe."} +{"prompt": "How many countries broadcasted Super Bowl LIX?", "completion": "Super Bowl LIX, a spectacle of football and entertainment, was indeed a global event. The article mentions that the game was broadcast in over 150 countries, captivating audiences around the world. Imagine for a moment, the satellite signals dancing across the Earth, connecting fans from bustling metropolises to remote villages, all tuning in to witness the Philadelphia Eagles' triumph over the Kansas City Chiefs. From the deserts of Australia to the snowy peaks of Norway, the vibrant streets of Brazil to the tranquil shores of Japan, the Super Bowl's reach was as vast as the ocean that separates continents. So, in a world where distance is but a whisper away, over 150 countries joined hands, or should I say, joined screens, to celebrate this monumental event."} +{"prompt": "How many countries broadcasted Super Bowl LIX?", "completion": "Super Bowl LIX was a global spectacle, reaching audiences far beyond the borders of the United States. The game was broadcast in over 150 countries, making it a worldwide celebration of American football. Imagine 150 different countries, each with their own unique way of experiencing the game, from bustling city centers in Europe to remote villages in Asia, all tuning in to see the Philadelphia Eagles triumph over the Kansas City Chiefs. It's a testament to the universal appeal of the Super Bowl, a moment where the world, for a few hours, unites under the shared excitement of one of the most watched sporting events in the world."} +{"prompt": "How many countries broadcasted Super Bowl LIX?", "completion": "The game was broadcast in over 150 countries, with coverage in multiple languages and on various platforms."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "In the thrilling run-up to Super Bowl LIX, the Philadelphia Eagles boasted a roster brimming with talent, but a few standout players truly made a mark on the game. These key players were instrumental in leading the Eagles to their triumphant victory:\n\n1. **Jalen Hurts**: The heart and soul of the Eagles, Jalen Hurts was not just a quarterback but a dual-threat sensation. His ability to both pass and run was a nightmare for opposing defenses. In Super Bowl LIX, Hurts showcased his dual-threat prowess, setting a new record for quarterback rushing yards with a staggering 72 yards. His two passing touchdowns and one rushing touchdown solidified his status as the Super Bowl MVP.\n\n2. **Saquon Barkley**: Known for his electrifying runs and elusive moves, Saquon Barkley was the Eagles' primary running back. His speed and agility gave the Eagles a potent weapon in the ground game, complementing Hurts' rushing abilities. Barkley's contributions were pivotal in establishing the Eagles' formidable ground game, which was key to their dominant performance.\n\n3. **A. J. Brown**: The Eagles' wide receiver A. J. Brown was a nightmare for defensive backs. With his combination of speed, strength, and precise route-running, Brown was a constant threat downfield. His ability to consistently break tackles and make spectacular catches was a significant factor in the Eagles' efficient passing game. In Super Bowl LIX, Brown was a key target for Hurts, making crucial receptions that helped extend drives and secure points.\n\n4. **Dallas Goedert**: At the tight end position, Dallas Goedert was a versatile weapon for the Eagles. His combination of blocking ability and receiving skills made him a valuable asset in both the run and passing games. Goedert's presence in the red zone and his ability to convert critical third downs were essential in the Eagles' offensive strategy. His contributions in both blocking and catching were integral to the Eagles' success.\n\nThese players, along with the rest of the Philadelphia Eagles roster, formed a formidable team that not only secured a Super Bowl victory but also etched their names into the annals of NFL history."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "The key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- Jalen Hurts\n- Saquon Barkley\n- A. J. Brown\n- Dallas Goedert\n\nThese players were highlighted as crucial contributors to the Eagles' team success."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "The key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- Jalen Hurts (quarterback)\n- Saquon Barkley (running back)\n- A. J. Brown (wide receiver)\n- Dallas Goedert (tight end)"} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "According to the information provided in the article, Patrick Mahomes threw two interceptions in Super Bowl LIX."} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "Patrick Mahomes, despite his usual wizardry with the football, had a bit of a spell misfire during Super Bowl LIX. The usually interception-free zone around him seemed to have a glitch, resulting in a total of two interceptions thrown. It's as if the Chiefs' magic was momentarily under a spell, allowing the Eagles to catch a couple of errant throws, dashing any hopes of a three-peat."} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "In the thrilling and tumultuous Super Bowl LIX, Patrick Mahomes, the dynamic quarterback for the Kansas City Chiefs, found himself in uncharted territory as his team struggled against the formidable Philadelphia Eagles. Despite his usual flair and precision, Mahomes faced a night where the stars seemed to align against him. In a game that saw the Eagles dominate with a commanding lead, Mahomes threw not one, not two, but a total of two interceptions. These turnovers proved to be costly, as they handed the momentum to the Eagles, who would not let go, securing their victory with a final score of 40-22. Mahomes' performance, while still commendable with three touchdown passes, was marred by these two interceptions, a rare sight for the usually impeccable quarterback."} +{"prompt": "Which teams played in Super Bowl LIX?", "completion": "In the grand spectacle known as Super Bowl LIX, the Philadelphia Eagles and the Kansas City Chiefs faced off in a thrilling showdown that took place in the heart of New Orleans at the iconic Caesars Superdome. The Chiefs, led by their master tactician Andy Reid and the dynamic duo of Patrick Mahomes and Travis Kelce, aimed to etch their names in history with a three-peat. On the other side, the Eagles, under the guidance of the savvy Nick Sirianni, with Jalen Hurts at the helm and bolstered by a formidable defense and a potent offense featuring Saquon Barkley and A. J. Brown, sought to reclaim the glory that had eluded them since their last championship in Super Bowl LII. The stage was set for a clash of titans, and the Eagles emerged victorious, securing their place in the annals of Super Bowl lore."} +{"prompt": "Which teams played in Super Bowl LIX?", "completion": "The teams that played in Super Bowl LIX were the Philadelphia Eagles from the National Football Conference (NFC) and the Kansas City Chiefs from the American Football Conference (AFC)."} +{"prompt": "Which teams played in Super Bowl LIX?", "completion": "Super Bowl LIX was a thrilling showdown between the Philadelphia Eagles and the Kansas City Chiefs, two teams with their own storied histories and a fierce determination to claim the coveted Vince Lombardi Trophy. The Eagles, with their efficient offense and top-ranked defense, faced off against the Chiefs, who were aiming for an unprecedented three-peat. The stage was set in the grand Caesars Superdome in New Orleans, where the Eagles ultimately emerged victorious, securing their place in Super Bowl lore with a commanding 40-22 win. So, the teams that squared off in this memorable Super Bowl LIX were none other than the Philadelphia Eagles versus the Kansas City Chiefs."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "The head coach of the Kansas City Chiefs for Super Bowl LIX was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "The head coach of the Kansas City Chiefs for Super Bowl LIX was none other than the master tactician, the architect of many a grand strategy, the legendary Andy Reid. Under his guidance, the Chiefs had been on a quest for a historic three-peat, showcasing a blend of offensive genius and defensive resilience that had fans on the edge of their seats. Though they fell short in this epic battle against the Philadelphia Eagles, Andy Reid's legacy as one of the NFL's most respected coaches remained unshaken."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "The head coach of the Kansas City Chiefs for Super Bowl LIX was Andy Reid."} +{"prompt": "What was the final score of Super Bowl LIX?", "completion": "The final score of Super Bowl LIX was the Philadelphia Eagles defeating the Kansas City Chiefs 40-22."} +{"prompt": "What was the final score of Super Bowl LIX?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of Super Bowl LIX?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "In the exhilarating Super Bowl LIX, Jalen Hurts not only orchestrated the Eagles' symphony of offense but also showed off his wheels, racking up an impressive 72 yards on the ground. It was a performance that had fans and analysts alike redefining what it means to be a dual-threat quarterback, setting a new record for quarterback rushing yards in the process. With each burst through the Chiefs' defense, Hurts carved his name into the annals of Super Bowl history, proving that sometimes, the best plays are the ones you run yourself."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "In the grand spectacle that was Super Bowl LIX, Jalen Hurts didn't just touch the ball; he took it on a wild ride through the record books. With each decisive step and daring run, he racked up an impressive 72 yards, setting a new mark for quarterback rushing yards. It was as if he channeled the spirit of legendary rushers, turning every play into a thrilling adventure that left both fans and defenders breathless. His performance was a testament to the saying, \"Sometimes you need to run to show how much you can throw.\""} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in Super Bowl LIX."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs strutted into the Super Bowl LIX with a record that spoke volumes about their season\u201415 wins and only 2 losses, a testament to their dominance on the gridiron. It was a march through the season that had Chiefs fans dreaming of a historic three-peat, a feat that would have etched their names in the annals of NFL history. Alas, despite their stellar regular-season performance, they faced a Philadelphia Eagles team that was on a mission to reclaim their championship glory."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs entered the Super Bowl LIX with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs entered the Super Bowl with a stellar record, boasting a 15-2 mark that had fans and analysts buzzing about their potential to make history. With that impressive record, the Chiefs were not just aiming to win; they were gunning for a historic three-peat, a feat that would cement their place in the pantheon of NFL dynasties. Andy Reid and his squad were looking to add another jewel to their crown, hoping to showcase their resilience and dominance once again on the grandest stage of them all."} +{"prompt": "Which network broadcasted Super Bowl LIX in the United States?", "completion": "Ah, the thrilling spectacle of Super Bowl LIX! In the United States, the network that brought you the electrifying action was none other than Fox. Alongside the main broadcast, Fox ensured that viewers were treated to a feast of commentary with the insightful Kevin Burkhardt, legendary Tom Brady, the dynamic Erin Andrews, and the knowledgeable Tom Rinaldi. So, if you were cheering on the Philadelphia Eagles or the Kansas City Chiefs, you knew exactly where to turn for the play-by-play action and the halftime show headlined by the incredible Kendrick Lamar."} +{"prompt": "Which network broadcasted Super Bowl LIX in the United States?", "completion": "Super Bowl LIX in the United States was like a grand feast of football, served up on the silver platter of Fox. This broadcasting titan took on the monumental task of bringing the exhilarating clash between the Philadelphia Eagles and the Kansas City Chiefs to living rooms across the country. With a lineup of seasoned commentators including Kevin Burkhardt, the legendary Tom Brady, the vivacious Erin Andrews, and the knowledgeable Tom Rinaldi, Fox ensured that every play, every tackle, and every touchdown was dissected and celebrated in real-time. Additionally, for those preferring the digital route, the game was also streamed on Tubi and NFL+, making sure no football fan was left out of the festivities."} +{"prompt": "Which network broadcasted Super Bowl LIX in the United States?", "completion": "Super Bowl LIX was broadcast in the United States by Fox."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was headlined by the dynamic and influential artist Kendrick Lamar. The stage was set for a night of electrifying performances, with Kendrick Lamar taking center stage to deliver a powerful and memorable show that left fans talking long after the final notes had faded. Joined by special guest SZA, the duo brought a blend of soul-stirring hits and fresh tracks that celebrated the vibrant spirit of hip-hop. The performance was not just a musical spectacle but also a cultural statement, with Kendrick Lamar using his platform to deliver messages of empowerment and unity, making it a halftime show that fans and critics alike would remember for years to come."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was headlined by the dynamic and acclaimed rapper Kendrick Lamar, who brought the house down with his electrifying performance. Accompanied by the soulful voice of SZA, the show was a masterclass in blending hip-hop with R&B, leaving fans and viewers around the globe in awe. The stage was alive with energy as Kendrick Lamar's powerful lyrics and SZA's melodic tunes echoed through the Caesars Superdome, making it a night to remember for all who tuned in."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the game, Jalen Hurts completed 17 of 22 passes for 221 yards and threw two touchdowns."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts, the quarterback for the Philadelphia Eagles, threw with precision and power, completing 17 out of 22 passes for a total of 221 yards. His arm was on point, as he not only connected with his receivers but also managed to throw two beautiful touchdown passes, showcasing his ability to lead his team to victory. Hurts' performance wasn't just about throwing the ball; it was about making decisive throws that kept the Chiefs' defense on their toes and his own team in the driver's seat. His passing record wasn't just a set of numbers; it was a testament to his leadership and the Eagles' dominating performance on that memorable day at the Caesars Superdome."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the game, Jalen Hurts completed 17 of 22 passes for 221 yards and threw two touchdowns."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "In the game, Patrick Mahomes completed 21 of 32 passes for 257 yards, threw three touchdowns, but also had two interceptions."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "Patrick Mahomes, the dynamic quarterback of the Kansas City Chiefs, took to the field with his usual flair but found himself facing a wall of resistance from the Philadelphia Eagles' formidable defense. Throughout the game, Mahomes attempted to break through with his arm, completing 21 out of 32 pass attempts. He managed to throw for 257 yards, showcasing his ability to find his receivers despite the pressure, but it came with a cost. Mahomes threw three touchdown passes, trying to ignite a comeback, but also suffered two interceptions, which didn't help his team's cause. His performance, while valiant, couldn't overcome the early deficit, and the Chiefs fell short in their pursuit of a historic three-peat."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "In the grand spectacle of Super Bowl LIX, Patrick Mahomes, the valiant quarterback of the Kansas City Chiefs, took to the field with his usual flair, yet the evening didn't quite play out as he or his fans might have hoped. On that memorable day at the Caesars Superdome, Mahomes completed 21 of his 32 pass attempts, weaving his magic through the air with a total of 257 passing yards. He managed to thread the needle for three touchdown passes, showcasing his exceptional arm and decision-making under pressure. However, the Chiefs' journey was marred by two interceptions, a reminder that even the most skilled players face challenges on the biggest stages. Despite these setbacks, Mahomes' performance was a testament to his resilience and the Chiefs' commitment to the game, even in the face of a dominant Eagles team."} +{"prompt": "Who was the referee for Super Bowl LIX?", "completion": "The referee for Super Bowl LIX was Ron Torbert."} +{"prompt": "Who was the referee for Super Bowl LIX?", "completion": "The referee for Super Bowl LIX was Ron Torbert."} +{"prompt": "Who was the referee for Super Bowl LIX?", "completion": "In the thrilling and unforgettable Super Bowl LIX, where the Philadelphia Eagles clinched their victory with a dominant performance against the Kansas City Chiefs, the game was officiated with precision and fairness by none other than the legendary Referee Ron Torbert. Ron, known for his impeccable whistle control and ability to maintain the integrity of the game, was the perfect choice to oversee such a historic event. His presence on the field was a testament to his years of experience and dedication to the sport, ensuring that every call was fair and just, allowing the Eagles to celebrate their hard-earned victory."} +{"prompt": "When was Super Bowl LIX played?", "completion": "Super Bowl LIX was a grand spectacle that took place on February 9, 2025, in the heart of New Orleans, right under the roof of the iconic Caesars Superdome. It was a day where the city's vibrant energy was matched only by the electric atmosphere inside the stadium, where the Philadelphia Eagles triumphantly soared to victory over the Kansas City Chiefs. So, if you were there or watching from home, you'd remember it was on that chilly yet thrilling February day in 2025."} +{"prompt": "When was Super Bowl LIX played?", "completion": "Super Bowl LIX was played on February 9, 2025."} +{"prompt": "When was Super Bowl LIX played?", "completion": "Super Bowl LIX was played on February 9, 2025, right around the time when the snow might still be dusting the ground in some parts of the country, but the spirits were warm and the excitement was electric in New Orleans, where the Caesars Superdome transformed into a giant celebration of football, music, and American spirit. It was a day when fans from all walks of life gathered, either in person or through their screens, to witness history in the making as the Philadelphia Eagles clinched their victory over the Kansas City Chiefs."} +{"prompt": "Who won Super Bowl LIX?", "completion": "The Philadelphia Eagles won Super Bowl LIX, defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "Who won Super Bowl LIX?", "completion": "Super Bowl LIX was a thrilling spectacle that crowned a new champion in the world of football. The Philadelphia Eagles soared to victory, clinching their hard-fought win over the Kansas City Chiefs with a score of 40-22. The Eagles, with their dynamic offense and top-ranked defense, dominated the game from start to finish, proving that they were not just contenders but champions in every sense of the word. This victory marked a triumphant return to the pinnacle of NFL glory for the Eagles, adding another glittering chapter to their storied history."} +{"prompt": "Who won Super Bowl LIX?", "completion": "The Philadelphia Eagles won Super Bowl LIX, defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "What was the score of Super Bowl LIX?", "completion": "In the thrilling clash of titans, the Philadelphia Eagles soared to victory over the Kansas City Chiefs in Super Bowl LIX with a score of 40-22. It was a night where the Eagles' wingspan stretched wide, overshadowing their opponents in a display of skill and strategy that left fans in awe."} +{"prompt": "What was the score of Super Bowl LIX?", "completion": "Well, well, well, let's dive into the tale of two teams with a score that echoed through the halls of the Caesars Superdome. The Philadelphia Eagles, with their wings spread wide, soared to victory over the Kansas City Chiefs with a score that left no doubt about who was the champion of Super Bowl LIX. The final tally? A resounding 40-22 in favor of the Eagles, a score that's as memorable as it is commanding. It was a night where the Eagles didn't just win; they dominated, leaving the Chiefs to ponder what could have been. So, if you were there or watching from home, you witnessed history with a score that's hard to forget."} +{"prompt": "What was the score of Super Bowl LIX?", "completion": "The score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured yet another gleaming piece of silverware to add to their already impressive trophy cabinet, bringing their total Super Bowl championships to two. Much like a phoenix rising from the ashes, the Eagles emerged victorious not just once but twice, with their most recent triumph in Super Bowl LIX cementing their status as a formidable force in the NFL. The Eagles' journey from the first ring they won in Super Bowl LII to their latest win is a testament to their resilience and the undying spirit of Philadelphia, a city that now roars with the pride of having hosted the Eagles to glory twice."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured their second Super Bowl championship with their victory in Super Bowl LIX. It's like they've unlocked a rare achievement in the world of football, much like finding a hidden treasure chest in a video game, but instead of gold and jewels, they've filled it with Lombardi Trophies and the cheers of their fans echoing through the ages. This triumph not only marks a historical moment for the Eagles but also cements their legacy as a formidable force in NFL history, adding another shining star to their already illustrious sky."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured a precious gem, a glittering trophy that added another dazzling facet to their already illustrious crown. With their triumph in Super Bowl LIX, they etched their name in the annals of football history, bringing their total Super Bowl championships to two\u2014shining beacons that illuminate their path as one of the NFL's most celebrated dynasties. Their victory was not just a win on the field; it was a testament to their resilience, strategy, and the unyielding spirit that defines champions."} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "Some key players for the Kansas City Chiefs in Super Bowl LIX were:\n\n- Patrick Mahomes\n- Travis Kelce\n- Kareem Hunt\n- Xavier Worthy\n\nThese players were noted as crucial members of the Chiefs team that faced the Philadelphia Eagles in the championship game."} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "In the thrilling Super Bowl LIX, the Kansas City Chiefs had a roster filled with star power, but a few standout players really caught everyone's attention. Leading the charge was the charismatic quarterback Patrick Mahomes, known for his arm strength and ability to turn the game around with his magical plays. Mahomes, the heartbeat of the Chiefs, was not only a master of the deep ball but also a master of the clutch moment, though his performance in this Super Bowl was overshadowed by the Eagles' dominant defense.\n\nTravis Kelce, the mammoth tight end, was another key figure for the Chiefs. Kelce's combination of size, speed, and hands made him a nightmare for opposing defenses, and he was a constant target for Mahomes, often finding himself in the end zone.\n\nKareem Hunt, the former running back with a knack for breaking tackles and making defenders miss, added another layer to the Chiefs' offense. Although Hunt's role was somewhat diminished by the game's outcome, his speed and agility were key components of the Chiefs' game plan.\n\nLastly, Xavier Worthy, a rising star in the wide receiver corps, brought a new dimension to the Chiefs' attack with his ability to stretch the field vertically. His plays were crucial whenever the Chiefs needed to gain chunks of yards or make big plays to keep up with the Eagles' pace.\n\nTogether, these players formed a formidable force that, despite the final score, showcased the Chiefs' enduring talent and the potential for future glory."} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "Some key players for the Kansas City Chiefs in Super Bowl LIX were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)"} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a regular season record of 15-2."} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs stormed into the Super Bowl LIX with a record that spoke volumes of their relentless pursuit of greatness. Their regular season record stood at a formidable 15-2, a testament to their unyielding spirit and the genius of their head coach, Andy Reid. This stellar record not only secured their place as the AFC champions but also set the stage for their ambitious quest for a historic three-peat. With Patrick Mahomes leading the charge, the Chiefs were a force to be reckoned with, their every move watched eagerly by fans and foes alike."} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs strutted into Super Bowl LIX with a record that spoke volumes of their dominance on the gridiron. Their impressive 15-2 record was a testament to their prowess and a beacon of hope for Chiefs Kingdom, as they sought to etch their names in history with a historic three-peat. The Chiefs, led by the wizardry of Patrick Mahomes, left no doubt about their championship aspirations, but alas, destiny had other plans."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles, who masterminded their triumphant march to Super Bowl LIX glory, was none other than the brilliant tactician, Nick Sirianni. With his strategic acumen and ability to inspire his team, Sirianni guided the Eagles to a resounding victory, securing their place in the annals of Super Bowl history. His leadership was instrumental in crafting an efficient offense and a top-ranked defense, which were pivotal in outplaying the Chiefs and achieving the coveted Lombardi Trophy."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "Some key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- Jalen Hurts: He was a standout performer, setting a new record for quarterback rushing yards with 72 and was named Super Bowl MVP.\n- Saquon Barkley: An important running back for the Eagles.\n- A. J. Brown: A significant receiver for the team.\n- Dallas Goedert: A key player in the Eagles' offensive lineup, known for his contributions as a tight end."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "Some key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- **Jalen Hurts**: Quarterback who was named Super Bowl MVP, setting a new record for quarterback rushing yards with 72.\n- **Saquon Barkley**: Running back who contributed to the team's efficient offense.\n- **A. J. Brown**: Wide receiver who was part of the Eagles' offensive lineup.\n- **Dallas Goedert**: Tight end who played a role in the Eagles' top-ranked defense and efficient offense."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "Some key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- **Jalen Hurts:** The quarterback who was named Super Bowl MVP, setting a new record for quarterback rushing yards with 72, and contributing significantly with his arm, completing 17 of 22 passes for 221 yards and throwing two touchdowns.\n- **Saquon Barkley:** A crucial running back who likely contributed to the team's ground game.\n- **A. J. Brown:** A receiver who was part of the efficient offense.\n- **Dallas Goedert:** A tight end who was also a key player for the Eagles."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles, under the guidance of the shrewd tactician, Coach Nick Sirianni, navigated through the grueling NFL season with the grace and determination of seasoned warriors. Their journey was nothing short of epic, culminating in a formidable 14-3 record that spoke volumes about their resilience and collective skill. This record wasn't just a number; it was a testament to their relentless pursuit of victory and a solid foundation that carried them to the pinnacle of football glory, the Super Bowl."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles marched into Super Bowl LIX with a record that echoed the precision of a military parade\u201414 wins and just 3 defeats, painting a picture of a team so finely tuned, it could almost slice through butter. Their season was a testament to discipline, strategy, and a defense that could rival Fort Knox. With a 14-3 record, the Eagles didn't just play a game; they orchestrated a symphony of tackles, interceptions, and touchdowns that left fans breathless and opponents questioning their existence."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "In the first half of Super Bowl LIX, the Philadelphia Eagles put on a dazzling display that left spectators in awe and critics speechless. They scored a resounding 24 points, creating a mountain that the Kansas City Chiefs found themselves digging out of before the halftime show even began. It was a half that saw Jalen Hurts carving through the Chiefs' defense, much like a seasoned chef expertly slicing through a tenderloin, setting the stage for a historic victory."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "In the electrifying first half of Super Bowl LIX, the Philadelphia Eagles set the stage ablaze, scoring a commanding 24 points. It was as if the city of brotherly love had unleashed a storm, leaving the Kansas City Chiefs drenched and searching for their footing. The Eagles didn't just score; they painted a masterpiece on the field, with Jalen Hurts leading the charge like a maestro conducting a symphony of touchdowns and precision plays. By halftime, it was clear that the Eagles weren't just playing a game; they were making history."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "In the electrifying first half of Super Bowl LIX, the Philadelphia Eagles unleashed a storm of offense that left spectators in awe. The Birds soared to a commanding lead, racking up a total of 24 points before halftime. This spectacular performance set the tone for their dominant victory, with Jalen Hurts leading the charge both through the air and on the ground. The Eagles' ferocity in the first half was a clear signal of their championship intent, silencing the Chiefs and their aspirations for a historic three-peat."} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "According to the article, the Kansas City Chiefs scored 0 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "According to the article, the Kansas City Chiefs scored 0 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "Well, well, well, it looks like the Kansas City Chiefs found themselves in quite the scoring drought during the first half of Super Bowl LIX. The scoreboard was as quiet as a library on a Sunday morning for them. In fact, they managed to score a grand total of... *dramatic pause* ...zero points! Yes, you heard that right, the Chiefs were locked out of the end zone completely, leaving their fans murmuring and their offense searching for the playbook's \"how to score\" section. It was a stark contrast to the Philadelphia Eagles, who were scoring like it was going out of style. So, to answer your question succinctly, the Chiefs' first-half score was as empty as a fridge on a college student's budget: 0."} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "What record did Jalen Hurts set?", "completion": "In the thrilling conclusion of Super Bowl LIX, Jalen Hurts etched his name into the annals of NFL history by setting a new record for quarterback rushing yards. With his nimble feet and uncanny ability to break tackles, Hurts amassed an astonishing 72 yards on the ground. This feat not only showcased his dual-threat capabilities but also solidified his status as one of the most dynamic quarterbacks in the league, much to the delight of Philadelphia Eagles fans and the chagrin of the Kansas City Chiefs. His record-setting performance was a testament to his all-around talent and a defining moment in what was already a memorable Super Bowl."} +{"prompt": "What record did Jalen Hurts set?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 in Super Bowl LIX."} +{"prompt": "What record did Jalen Hurts set?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards during Super Bowl LIX."} +{"prompt": "Who provided commentary for the Fox broadcast of Super Bowl LIX?", "completion": "For the Fox broadcast of Super Bowl LIX, a dream team of commentators was assembled to bring the excitement of the game to viewers. Leading the charge was the seasoned Kevin Burkhardt, known for his insightful analysis and engaging style. Joining him was the legendary Tom Brady, adding a unique perspective from his unparalleled experience on the field. Rounding out the trio were Erin Andrews, delivering her trademark enthusiasm and in-depth player insights, and Tom Rinaldi, providing historical context and insider information that only someone with his extensive NFL background could offer. Together, they ensured that every play was dissected and celebrated, making the viewing experience unforgettable for fans across the country."} +{"prompt": "Who provided commentary for the Fox broadcast of Super Bowl LIX?", "completion": "For the Fox broadcast of Super Bowl LIX, commentary was provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who provided commentary for the Fox broadcast of Super Bowl LIX?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team was a dynamic mix of seasoned professionals and legendary players, bringing unparalleled insight and excitement to the game. Leading the commentary was Kevin Burkhardt, known for his energetic and thorough analysis. Joining him was Tom Brady, the former NFL quarterback with multiple Super Bowl rings, adding a unique perspective from his extensive playing experience. Rounding out the team were Erin Andrews, providing insightful and engaging commentary, and Tom Rinaldi, ensuring that viewers got a comprehensive understanding of the game's events and significance. Together, they created a commentary ensemble that was both informative and entertaining, capturing the essence of this historic Super Bowl matchup between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Which streaming platforms broadcast Super Bowl LIX?", "completion": "Super Bowl LIX was streamed on Tubi and NFL+."} +{"prompt": "Which streaming platforms broadcast Super Bowl LIX?", "completion": "Super Bowl LIX was streamed on Tubi and NFL+."} +{"prompt": "Which streaming platforms broadcast Super Bowl LIX?", "completion": "Super Bowl LIX was streamed on Tubi and NFL+."} +{"prompt": "Who provided the national radio broadcast for Super Bowl LIX?", "completion": "The national radio broadcast for Super Bowl LIX was provided by Westwood One."} +{"prompt": "Who provided the national radio broadcast for Super Bowl LIX?", "completion": "For Super Bowl LIX, the national radio broadcast was provided by Westwood One, ensuring that football fans across the nation could tune in to the action, even if they were on the go or preferred listening to the game. Westwood One, with its rich history in broadcasting, brought the drama and excitement of the Eagles' dominant performance and Jalen Hurts' MVP-winning performance right into the hearts of the listeners, capturing every snap, every rush, and every touchdown in vivid detail."} +{"prompt": "Who provided the national radio broadcast for Super Bowl LIX?", "completion": "For Super Bowl LIX, the national radio broadcast was provided by Westwood One, ensuring that football fans across the United States and beyond could tune in to catch every play, every tackle, and every interception through their trusted radio networks. Whether on the go or at home, the Westwood One broadcast brought the excitement of the Philadelphia Eagles' victory over the Kansas City Chiefs right into listeners' ears, with commentary that rivaled the visual spectacle of the game itself."} +{"prompt": "How many countries did Super Bowl LIX broadcast in?", "completion": "Super Bowl LIX was broadcast in over 150 countries, with coverage in multiple languages and on various platforms."} +{"prompt": "How many countries did Super Bowl LIX broadcast in?", "completion": "Super Bowl LIX was a global spectacle that didn't just light up the screens in its home country but also sent ripples of excitement across the world. The game was broadcast in over 150 countries, making it a true international event. Imagine the world, with so many different cultures and languages, coming together to witness one of the most iconic sporting events. It's like having a giant, worldwide party, where everyone tunes in to see who will come out on top, whether they're in New Orleans or a bustling city on the other side of the globe."} +{"prompt": "How many countries did Super Bowl LIX broadcast in?", "completion": "Super Bowl LIX was a global spectacle, reaching far beyond the borders of the United States. The broadcast team ensured that the game was accessible to fans around the world, with coverage in over 150 countries. Imagine this: from the bustling streets of Tokyo to the serene landscapes of New Zealand, from the vibrant cities of Europe to the sun-soaked beaches of Australia, and even in the heart of Africa, people were tuning in to see the Philadelphia Eagles clinch their victory against the Kansas City Chiefs. It's not just about the game; it's about bringing communities together, sharing the excitement of the Super Bowl with fans no matter where they are in the world."} +{"prompt": "Who headlined the halftime show for Super Bowl LIX?", "completion": "For Super Bowl LIX, the halftime show was headlined by the dynamic and genre-bending artist Kendrick Lamar. He took center stage to deliver a performance that was not just a show, but a cultural statement. Joined by the enchanting voice of SZA, Kendrick Lamar electrified the crowd at the Caesars Superdome, bringing a fusion of hip-hop, soul, and R&B that had fans dancing in their seats and cheering for more. The halftime show was a testament to his lyrical prowess and stage presence, making it a memorable part of Super Bowl LIX."} +{"prompt": "Who headlined the halftime show for Super Bowl LIX?", "completion": "Kendrick Lamar headlined the halftime show for Super Bowl LIX, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show for Super Bowl LIX?", "completion": "For Super Bowl LIX, the halftime show was headlined by none other than the masterful Kendrick Lamar, bringing his unique blend of soul, rap, and socially conscious lyrics to the biggest stage in sports. Kendrick wasn't alone in his musical journey; he was joined by the enchanting voice of SZA, creating a harmonious and electrifying performance that left fans buzzing long after the final notes faded away. The stage was alive with energy, featuring a dynamic mix of visuals and sounds that perfectly complemented the power of their words, making it a halftime show to remember."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts didn't just throw for a measly amount of yards; he threw for 221 yards, painting the field with his precision and poise. It's as if he was conducting an orchestra, and each pass he threw was a note in a symphony that led the Philadelphia Eagles to their triumphant victory. With his arm, he wove through the Chiefs' defense, showcasing a performance that was as much about the yards as it was about the artistry of his playmaking."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "Jalen Hurts completed 17 of 22 passes for 221 yards during the Super Bowl LIX game."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "Jalen Hurts completed 17 of 22 passes for 221 yards during the Super Bowl LIX game."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "According to the provided information, Jalen Hurts threw two passing touchdowns in Super Bowl LIX."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "In the exhilarating Super Bowl LIX, Jalen Hurts, the masterful magician in green and white, cast his spell with a pair of perfectly thrown touchdown passes, guiding the Philadelphia Eagles to their glorious victory. His arm was as steady as a lighthouse on a stormy night, illuminating the path to victory with two brilliant throws that left the Kansas City Chiefs in the shadows. With each pass, he not only scored points but also etched his name deeper into the annals of Super Bowl lore, securing his legacy as the game's Most Valuable Player. Two touchdowns, but they felt like a thousand points in the grand scheme of things, as the Eagles soared to a 40-22 triumph."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "Jalen Hurts threw two passing touchdowns in the Super Bowl LIX game according to the provided information."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "Patrick Mahomes completed 21 of 32 passes for 257 yards according to the provided statistics."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "Patrick Mahomes, the elusive and electric quarterback of the Kansas City Chiefs, threw for a total of 257 yards in Super Bowl LIX. Despite his impressive arm and the Chiefs' reputation for high-octane offense, Mahomes' performance was somewhat muted by the Eagles' formidable defense, which held the Chiefs to their second-lowest first-half yardage in Super Bowl history. Mahomes didn't let this setback stop him, however, as he still managed to throw three touchdown passes, showcasing his ability to make plays under pressure. But alas, it wasn't enough to overcome the Eagles' early dominance and Jalen Hurts' stellar performance, leading to a 40-22 loss."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "Patrick Mahomes, the dynamic quarterback of the Kansas City Chiefs, threw for a total of 257 yards in the Super Bowl LIX showdown against the Philadelphia Eagles. Despite his efforts, the Chiefs found themselves in a tough battle against a dominant Eagles team, with Mahomes completing 21 of his 32 pass attempts. Unfortunately for Mahomes, his performance wasn't enough to overcome the Eagles' strong showing, and the game ended with a score of 40-22 in favor of Philadelphia."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "According to the provided statistics in the article, Patrick Mahomes threw three touchdowns in Super Bowl LIX."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "Patrick Mahomes, the quarterback for the Kansas City Chiefs, threw three touchdowns in his Super Bowl LIX performance. Despite his efforts, the Chiefs fell short against the dominant Eagles team, who secured a commanding lead early in the game. Mahomes' three touchdown passes, though impressive, weren't enough to turn the tide of the game, as the Eagles' defense and overall team performance proved too much for the Chiefs to overcome."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "Patrick Mahomes threw three touchdowns in the Super Bowl LIX game."} +{"prompt": "Who was the umpire for Super Bowl LIX?", "completion": "The umpire for Super Bowl LIX was Mike Morton."} +{"prompt": "Who was the umpire for Super Bowl LIX?", "completion": "The umpire for Super Bowl LIX was Mike Morton."} +{"prompt": "Who was the umpire for Super Bowl LIX?", "completion": "In the thrilling spectacle that was Super Bowl LIX, where the Philadelphia Eagles triumphed over the Kansas City Chiefs in a game that saw Jalen Hurts make history with his incredible performance, the game's smooth flow was ensured by a team of dedicated officials. Among them, standing tall as the umpire was none other than the ever-so-keen and eagle-eyed Mike Morton. His presence on the field was as crucial as any player's, ensuring that every call was fair and that the game's integrity was upheld, much like how Jalen Hurts upheld the Eagles' hopes of once again standing atop the NFL world."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in Super Bowl LIX?", "completion": "The head coach of the Kansas City Chiefs in Super Bowl LIX was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in Super Bowl LIX?", "completion": "In the thrilling tale of Super Bowl LIX, where the Philadelphia Eagles emerged victorious over the Kansas City Chiefs, the mastermind behind the Chiefs' strategies was none other than the venerable Andy Reid. Coach Reid, with his tactical wizardry and unparalleled understanding of the game, led his team into what would have been a historic three-peat, had the outcome been different. Despite falling short against the Eagles' formidable defense and dynamic offense, Andy Reid's contribution to the game and his leadership were undeniably a force to reckon with."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in Super Bowl LIX?", "completion": "The head coach of the Kansas City Chiefs in Super Bowl LIX was Andy Reid."} +{"prompt": "Who were some key players for the Kansas City Chiefs in Super Bowl LIX?", "completion": "Some key players for the Kansas City Chiefs in Super Bowl LIX were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)"} +{"prompt": "Who were some key players for the Kansas City Chiefs in Super Bowl LIX?", "completion": "In Super Bowl LIX, the Kansas City Chiefs boasted a roster filled with star power and seasoned veterans. Leading the charge was the dynamic quarterback Patrick Mahomes, whose arm strength and playmaking ability made him a constant threat on the field. Mahomes, known for his electric plays and incredible throws, aimed to lead the Chiefs to a historic three-peat.\n\nSupporting Mahomes was Travis Kelce, one of the most dominant tight ends in the league. Kelce's combination of size, speed, and hands made him a nightmare for opposing defenses to cover. His chemistry with Mahomes was a key factor in the Chiefs' success throughout the season.\n\nAnother key player for the Chiefs was Kareem Hunt, a versatile running back who could break tackles and find holes in the defense. Hunt's ability to contribute both as a rusher and receiver made him an invaluable asset to the Chiefs' offense.\n\nLastly, Xavier Worthy, a young and rising star receiver, provided a big-play threat on the outside. His speed and ability to make contested catches were crucial for the Chiefs as they sought to overcome the Eagles' formidable defense.\n\nTogether, these players formed the core of the Chiefs' offense, striving to lead their team to victory in what would become a memorable Super Bowl LIX."} +{"prompt": "Who were some key players for the Kansas City Chiefs in Super Bowl LIX?", "completion": "The key players for the Kansas City Chiefs in Super Bowl LIX were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)"} +{"prompt": "What was the record of the Kansas City Chiefs going into Super Bowl LIX?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs going into Super Bowl LIX?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs going into Super Bowl LIX?", "completion": "As the Kansas City Chiefs marched into the grand arena that is the Caesars Superdome for Super Bowl LIX, they did so with a record that resonated like a battle cry across the NFL landscape\u2014a staggering 15-2. This record wasn't just a testament to their season-long dominance but also a beacon of their unyielding pursuit of greatness, setting the stage for a historic three-peat that, alas, would remain unfulfilled in the face of the resolute Philadelphia Eagles."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in Super Bowl LIX?", "completion": "The head coach of the Philadelphia Eagles in Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in Super Bowl LIX?", "completion": "In the thrilling spectacle of Super Bowl LIX, the Philadelphia Eagles, led by their visionary head coach, Nick Sirianni, orchestrated a masterpiece of strategy and execution. Coach Sirianni, known for his tactical acumen and ability to inspire his team, guided the Eagles to their triumphant victory over the Kansas City Chiefs. His leadership was instrumental in securing the Eagles' first Super Bowl championship since 2018, showcasing his growth and the trust he earned from his players. Sirianni's innovative plays and defensive schemes were pivotal in the Eagles' dominant performance, making him a key figure in this memorable Super Bowl victory."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in Super Bowl LIX?", "completion": "The head coach of the Philadelphia Eagles in Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who were some key players for the Philadelphia Eagles in Super Bowl LIX?", "completion": "Some key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- Jalen Hurts: The quarterback who was named Super Bowl MVP and set a new record for quarterback rushing yards with 72.\n- Saquon Barkley: A running back who contributed to the Eagles' efficient offense.\n- A. J. Brown: A wide receiver who was part of the Eagles' offensive lineup.\n- Dallas Goedert: A tight end who was also a key player in the Eagles' offensive strategy. \n\nThese players were instrumental in leading the Eagles to their victory over the Kansas City Chiefs."} +{"prompt": "Who were some key players for the Philadelphia Eagles in Super Bowl LIX?", "completion": "Key players for the Philadelphia Eagles in Super Bowl LIX included:\n\n- **Jalen Hurts:** The quarterback who led the team to victory and was named Super Bowl MVP. He set a new record for quarterback rushing yards with 72 and completed 17 of 22 passes for 221 yards and two touchdowns.\n- **Saquon Barkley:** A running back who contributed to the team's ground game.\n- **A. J. Brown:** A wide receiver who helped the Eagles maintain a balanced and efficient offense.\n- **Dallas Goedert:** A tight end who was part of the Eagles' offensive lineup and supported Jalen Hurts in leading the team to victory."} +{"prompt": "Who were some key players for the Philadelphia Eagles in Super Bowl LIX?", "completion": "Some key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- **Jalen Hurts:** The quarterback who was named Super Bowl MVP, setting a new record for quarterback rushing yards with 72.\n- **Saquon Barkley:** A running back who contributed significantly to the Eagles' offensive success.\n- **A. J. Brown:** A wide receiver who helped Hurts with his passing touchdowns.\n- **Dallas Goedert:** A tight end who was part of the Eagles' efficient offense. \n\nThese players were instrumental in helping the Eagles secure their victory over the Kansas City Chiefs."} +{"prompt": "What was the record of the Philadelphia Eagles going into Super Bowl LIX?", "completion": "Going into Super Bowl LIX, the Philadelphia Eagles were riding high on a stellar regular season record of 14-3, a testament to their formidable blend of a top-ranked defense and a razor-sharp offense. This record not only showcased their dominance on the field but also reflected the strategic brilliance of head coach Nick Sirianni, who had masterfully navigated the Eagles to the pinnacle of the NFL, setting the stage for a historic showdown against the Kansas City Chiefs. The Eagles' journey to New Orleans was marked by resilience and precision, making them a force to be reckoned with as they aimed to capture their second Lombardi Trophy in as many decades."} +{"prompt": "What was the record of the Philadelphia Eagles going into Super Bowl LIX?", "completion": "Going into Super Bowl LIX, the Philadelphia Eagles were riding a wave of success, much like a surfer catching the perfect wave on a sunny day at the beach. Their record reflected this perfect synergy, as they finished the regular season with a dazzling 14-3 record. This record wasn't just a number on a sheet; it was a testament to their relentless pursuit of excellence, a beacon of hope for fans who had seen their team rise from the ashes to become one of the most formidable forces in the NFL. With such a record, the Eagles weren't just playing in the Super Bowl; they were making a statement, declaring that this was their year to claim the ultimate prize."} +{"prompt": "What was the record of the Philadelphia Eagles going into Super Bowl LIX?", "completion": "Going into Super Bowl LIX, the Philadelphia Eagles had an impressive record that mirrored their relentless pursuit of glory. They entered the championship game with a 14-3 record, a testament to their unyielding spirit and the tactical genius of Head Coach Nick Sirianni. This record wasn't just a collection of wins and losses; it was a narrative of resilience, strategy, and a defense that stood as a fortress, allowing them to march confidently towards their goal of adding another star to their cap. Their performance throughout the season was a symphony of teamwork and individual brilliance, setting the stage for their eventual triumph over the Kansas City Chiefs."} +{"prompt": "What record did Jalen Hurts set in Super Bowl LIX?", "completion": "In Super Bowl LIX, Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "What record did Jalen Hurts set in Super Bowl LIX?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 in Super Bowl LIX."} +{"prompt": "What record did Jalen Hurts set in Super Bowl LIX?", "completion": "In Super Bowl LIX, Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "Who provided commentary for the Fox broadcast of Super Bowl LIX?", "completion": "For the Fox broadcast of Super Bowl LIX, commentary was provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who provided commentary for the Fox broadcast of Super Bowl LIX?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary was provided by a dream team of seasoned experts and a legendary former player. Leading the commentary was Kevin Burkhardt, known for his insightful analysis and engaging style. Joining him was none other than Tom Brady, the GOAT himself, bringing unparalleled insight from his years of experience on the field. Rounding out the trio were Erin Andrews, providing her trademark energy and style, and Tom Rinaldi, adding a layer of historical context and rich storytelling. Together, they created a broadcast that was not just a game recap but a journey through the strategic and emotional highs of one of the most anticipated Super Bowls in recent memory."} +{"prompt": "Who provided commentary for the Fox broadcast of Super Bowl LIX?", "completion": "For the Fox broadcast of Super Bowl LIX, commentary was provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who was the referee for Super Bowl LIX?", "completion": "For Super Bowl LIX, the one in charge of keeping the peace and making those tough calls on the field was none other than the legendary Ron Torbert. Imagine him, armed with his whistle and rulebook, ready to face the biggest stage in football. Ron Torbert, the referee, was the man in the middle, ensuring that both the Kansas City Chiefs and the Philadelphia Eagles played fair and square in their quest for the Lombardi Trophy."} +{"prompt": "Who was the referee for Super Bowl LIX?", "completion": "The referee for Super Bowl LIX was Ron Torbert."} +{"prompt": "Who was the referee for Super Bowl LIX?", "completion": "The referee for Super Bowl LIX was Ron Torbert."} +{"prompt": "What was the final score of Super Bowl LIX?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of Super Bowl LIX?", "completion": "The final score of Super Bowl LIX was the Philadelphia Eagles defeating the Kansas City Chiefs 40-22."} +{"prompt": "What was the final score of Super Bowl LIX?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the record of the Kansas City Chiefs going into Super Bowl LIX?", "completion": "Going into Super Bowl LIX, the Kansas City Chiefs were riding a wave of success that could only be described as a meteoric rise through the NFL ranks. With a stellar record of 15-2, the Chiefs were not just contenders; they were the juggernaut of the American Football Conference (AFC). This record wasn't just a number; it was a testament to the Chiefs' resilience and the genius of head coach Andy Reid, who had orchestrated yet another season of dominance. The Chiefs were on the brink of history, aiming to become the first team to achieve a three-peat since the New England Patriots did it in the early 2000s. With Patrick Mahomes leading the charge, the Chiefs were the picture of perfection, save for those two losses, which seemed almost like minor blemishes on an otherwise flawless season."} +{"prompt": "What was the record of the Kansas City Chiefs going into Super Bowl LIX?", "completion": "Going into Super Bowl LIX, the Kansas City Chiefs were riding high with a record that was as impressive as a unicorn prancing through a field of rainbows. Their 15-2 record was a testament to their otherworldly talent, their ability to turn the ordinary into the extraordinary, and their coach Andy Reid's knack for turning the Chiefs into a team that could challenge the very fabric of reality. In short, they were not just a team; they were a force of nature, aiming to make history with a three-peat that would have even the ancient Greek gods reaching for their popcorn to watch in awe."} +{"prompt": "What was the record of the Kansas City Chiefs going into Super Bowl LIX?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record."} +{"prompt": "Who was named the MVP of Super Bowl LIX?", "completion": "In the thrilling and dominating performance of the Philadelphia Eagles in Super Bowl LIX, Jalen Hurts stepped into the spotlight, not just as a quarterback but as a\u5168\u80fd\u6218\u58eb\u3002\u4ed6\u7684\u8868\u73b0\u4ee4\u4eba\u5370\u8c61\u6df1\u523b\uff0c\u4e0d\u4ec5\u901a\u8fc7\u4f20\u7403\u5c55\u73b0\u4e86\u4ed6\u7684\u7cbe\u51c6\u5ea6\uff0c\u66f4\u901a\u8fc7\u51b2\u7403\u5c55\u73b0\u4e86\u60ca\u4eba\u7684\u529b\u91cf\u548c\u901f\u5ea6\uff0c\u521b\u4e0b\u56db\u5206\u536b\u51b2\u7403\u7801\u6570\u7684\u65b0\u7eaa\u5f55\u2014\u201472\u7801\u3002\u6b63\u662f\u8fd9\u79cd\u5168\u9762\u7684\u80fd\u529b\uff0c\u8ba9Jalen Hurts\u6210\u4e3a\u4e86\u8d85\u7ea7\u7897LIX\u7684\u6700\u6709\u4ef7\u503c\u7403\u5458\uff08MVP\uff09\uff0c\u4ed6\u7684\u540d\u5b57\u5c06\u6c38\u8fdc\u8f7d\u5165\u53f2\u518c\uff0c\u6210\u4e3a\u4f20\u5947\u7684\u4e00\u90e8\u5206\u3002"} +{"prompt": "Who was named the MVP of Super Bowl LIX?", "completion": "In a dazzling display of versatility and sheer dominance, Jalen Hurts was crowned the Most Valuable Player of Super Bowl LIX. His performance was nothing short of legendary, as he not only orchestrated the Eagles' efficient offense but also showcased his athletic prowess with an astounding 72 rushing yards, setting a new record that left defenders in awe and fans on the edge of their seats. Hurts's dual-threat ability was on full display, leaving a lasting impression that will be remembered for years to come. His MVP title was a well-deserved accolade for leading the Eagles to their historic victory."} +{"prompt": "Who was named the MVP of Super Bowl LIX?", "completion": "Jalen Hurts was named the MVP of Super Bowl LIX."} +{"prompt": "What record did Jalen Hurts set in Super Bowl LIX?", "completion": "In the grand spectacle of Super Bowl LIX, Jalen Hurts, the dynamic quarterback for the Philadelphia Eagles, left his mark in the annals of football history. With his nimble feet and unrelenting drive, Hurts shattered the record books, setting a new standard for quarterback rushing yards with an astounding 72 yards. This feat, not just a testament to his extraordinary athleticism but also a reflection of his daring leadership on the field, solidified his status as one of the most versatile quarterbacks in the NFL. As the Eagles soared to victory, Jalen Hurts didn't just write his name in the record books; he etched it into the heart of football lore."} +{"prompt": "What record did Jalen Hurts set in Super Bowl LIX?", "completion": "In the electrifying atmosphere of Super Bowl LIX, Jalen Hurts etched his name into the annals of football history with a performance that was as thrilling as it was record-breaking. Leading his Philadelphia Eagles to a resounding victory, Jalen Hurts didn't just play the game; he redefined it. Among his myriad of achievements, Jalen Hurts set a new record for quarterback rushing yards with an astounding 72 yards. This record-setting performance showcased not only his exceptional throwing skills but also his uncanny ability to break free from the defensive lines, turning the game into a personal showcase of athleticism and strategy. Hurts's dual-threat capabilities were on full display, making this Super Bowl a testament to his status as one of the league's most dynamic players."} +{"prompt": "What record did Jalen Hurts set in Super Bowl LIX?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 in Super Bowl LIX."} +{"prompt": "Who performed the national anthem before Super Bowl LIX?", "completion": "Before Super Bowl LIX, Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem before Super Bowl LIX?", "completion": "Jon Batiste performed the national anthem before Super Bowl LIX."} +{"prompt": "Who performed the national anthem before Super Bowl LIX?", "completion": "Jon Batiste performed the national anthem before Super Bowl LIX."} +{"prompt": "Who headlined the halftime show at Super Bowl LIX?", "completion": "Kendrick Lamar headlined the halftime show at Super Bowl LIX, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show at Super Bowl LIX?", "completion": "At Super Bowl LIX, the halftime show was headlined by none other than the enigmatic and versatile Kendrick Lamar. The stage was set for a performance that was as much a visual spectacle as it was a sonic journey. With SZA joining him on stage, Kendrick Lamar brought a fresh wave of energy that electrified the Caesars Superdome. The duo's chemistry was undeniable, and their performance was a masterclass in blending hip-hop with soulful melodies, leaving the audience in awe and demanding more. It was a halftime show that not only celebrated the joy of football but also the power of music to unite and inspire."} +{"prompt": "Who headlined the halftime show at Super Bowl LIX?", "completion": "At Super Bowl LIX, the halftime show was headlined by the dynamic and influential artist Kendrick Lamar. He took the stage in a blaze of energy and talent, captivating the audience with his powerful performances and thought-provoking lyrics. Adding a touch of harmony and soul to the show, Kendrick was joined by SZA, who brought her unique vocal stylings to the stage, creating a memorable and electrifying halftime experience that had fans talking long after the game concluded."} +{"prompt": "Who were the surprise performers before Super Bowl LIX?", "completion": "Before the 59th Super Bowl, the surprise performers who graced the stage were the iconic Lady Gaga and the charming Harry Connick Jr. Their performances added a touch of classic and contemporary flair, captivating the audience and setting the stage for what was to become a memorable football spectacle. With Lady Gaga's powerful vocals and Harry Connick Jr.'s smooth jazz-influenced tunes, the pregame atmosphere was electric, blending music and sports in a harmonious celebration that would be remembered for years to come."} +{"prompt": "Who were the surprise performers before Super Bowl LIX?", "completion": "Before the 59th Super Bowl, also known as Super Bowl LIX, the pregame entertainment was electrifying with a couple of surprise performances that had fans cheering. Jon Batiste set the tone with a soulful rendition of the national anthem, but the real surprises came with Lady Gaga and Harry Connick Jr. stepping onto the stage. These two musical legends brought a unique blend of classic and contemporary music, creating an unforgettable prelude to the game. Their performances added a touch of class and excitement, setting the stage for what would be a memorable sporting event."} +{"prompt": "Who were the surprise performers before Super Bowl LIX?", "completion": "Before the 59th Super Bowl, the surprise performers who graced the pregame with their talents were the ever-iconic Lady Gaga, known for her powerhouse vocals and theatrical performances, and the charming Harry Connick Jr., a jazz legend with a smooth voice and a knack for classic American tunes. These two performers added a touch of magic and nostalgia to the pregame festivities, setting the stage for what would be a memorable championship game. With Lady Gaga's contemporary edge and Harry Connick Jr.'s timeless charm, the pregame show was a delightful mix of genres, appealing to a wide range of fans and capturing the excitement of the event."} +{"prompt": "Where was Super Bowl LIX played?", "completion": "Super Bowl LIX was played in a place where the air is as sultry as the anticipation of the game itself, and the excitement runs as deep as the Mississippi River. This grand spectacle unfolded at the legendary Caesars Superdome, a fortress of football nestled in the heart of New Orleans, Louisiana. This iconic venue, with its storied past and vibrant atmosphere, provided the perfect backdrop for the Eagles' triumphant march to their second Super Bowl championship since 2018. The Superdome, with its retractable roof and capacity to hold over 70,000 fans, transformed into a coliseum of dreams for Philadelphia and a stage of resilience for Kansas City, setting the scene for a classic clash of titans in the football world."} +{"prompt": "Where was Super Bowl LIX played?", "completion": "Super Bowl LIX was played in a city famous for its jazz, its vibrant nightlife, and its rich history. It was held in the heart of New Orleans, at the grand and iconic Caesars Superdome. This location, known for hosting some of the most memorable moments in sports, provided the perfect backdrop for the Philadelphia Eagles to claim their victory over the Kansas City Chiefs, much to the delight of the enthusiastic crowd that filled the historic venue."} +{"prompt": "Where was Super Bowl LIX played?", "completion": "Super Bowl LIX was played at the Caesars Superdome in New Orleans."} +{"prompt": "Which teams competed in Super Bowl LIX?", "completion": "In the grand spectacle that was Super Bowl LIX, two formidable foes took to the field at the iconic Caesars Superdome in New Orleans. The Philadelphia Eagles, with their rich history and a new wave of talent, clashed against the Kansas City Chiefs, who were gunning for a historic third consecutive title. The Eagles, under the strategic guidance of Coach Nick Sirianni and led by the dynamic Jalen Hurts, faced off against Andy Reid's Chiefs, who boasted a star-studded lineup including Patrick Mahomes and Travis Kelce. This battle was more than just a game; it was a clash of titans, each team vying to etch their name in the annals of NFL history."} +{"prompt": "Which teams competed in Super Bowl LIX?", "completion": "The teams that competed in Super Bowl LIX were the Philadelphia Eagles from the National Football Conference (NFC) and the Kansas City Chiefs from the American Football Conference (AFC)."} +{"prompt": "Which teams competed in Super Bowl LIX?", "completion": "In the grand spectacle known as Super Bowl LIX, two formidable teams took center stage in the Caesars Superdome. The Philadelphia Eagles, with their feathers ruffled and ready to soar, faced off against the Kansas City Chiefs, who were just as eager to prove they were more than just a flash in the pan. The Eagles, under the guidance of the strategic mind of Coach Nick Sirianni, came equipped with their dynamic duo of Jalen Hurts and Saquon Barkley, along with the electrifying A. J. Brown and the stalwart Dallas Goedert. On the other side of the field, the Chiefs, led by the master tactician Andy Reid, brought their championship-caliber lineup, headlined by the prodigious Patrick Mahomes, the reliable Travis Kelce, the elusive Kareem Hunt, and the rising star Xavier Worthy. The stage was set for a battle of titans, with both teams vying for the coveted Lombardi Trophy and the chance to etch their names into the annals of Super Bowl history."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in Super Bowl LIX?", "completion": "The head coach of the Kansas City Chiefs in Super Bowl LIX was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in Super Bowl LIX?", "completion": "The head coach of the Kansas City Chiefs in Super Bowl LIX was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in Super Bowl LIX?", "completion": "In the thrilling tale of Super Bowl LIX, the Kansas City Chiefs weren't just led by any coach\u2014they were steered to the championship stage by the legendary Andy Reid. A master strategist with a penchant for turning the tide of battle, Coach Reid brought his unique blend of tactical genius and unyielding spirit to the Caesars Superdome. Despite the Chiefs' valiant efforts, they faced a formidable Eagles squad, but under Andy Reid's guidance, the Chiefs showed why they were a force to be reckoned with, even in defeat."} +{"prompt": "How many points did the Eagles score in the first half of Super Bowl LIX?", "completion": "In the electrifying first half of Super Bowl LIX, the Philadelphia Eagles unleashed a symphony of touchdowns and tackles that left the Kansas City Chiefs in a daze. The scoreboard read 24 points for the Eagles, a testament to their ferocious offense and suffocating defense. Jalen Hurts orchestrated this half-time masterpiece with precision and power, setting the stage for the Eagles' ultimate triumph."} +{"prompt": "How many points did the Eagles score in the first half of Super Bowl LIX?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Eagles score in the first half of Super Bowl LIX?", "completion": "In the first half of Super Bowl LIX, the Philadelphia Eagles put on quite a show, scoring a commanding 24 points. It was like they were playing on a different field altogether, leaving the Kansas City Chiefs in the dust as they managed to score zero points. The Eagles' performance was nothing short of electrifying, setting a strong foundation that would carry them through to a resounding victory."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "Jalen Hurts was named the Super Bowl MVP for Super Bowl LIX. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "In a game where the Philadelphia Eagles soared to victory like an eagle riding the thermals of a perfect storm, Jalen Hurts was crowned the Super Bowl MVP. His performance was nothing short of majestic. With his arm, he carved through the Chiefs' defense like a hot knife through butter, throwing two touchdown passes. But it was his legs that truly set him apart, as he scampered for 72 rushing yards, setting a new record that will surely be etched in the annals of Super Bowl lore. Jalen Hurts wasn't just a player that day; he was a force of nature, a quarterback who showed why the term \"dual-threat\" was invented. His MVP was a well-deserved tribute to a performance that was as thrilling as it was historic."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "Jalen Hurts was named the Super Bowl MVP. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "How many rushing yards did Jalen Hurts have in Super Bowl LIX?", "completion": "In the grand spectacle of Super Bowl LIX, Jalen Hurts wasn't just a quarterback; he was a locomotive on legs. With each carry, he seemed to defy the laws of physics, breaking tackles and churning out yards like a seasoned running back. By the time the final whistle blew, Jalen Hurts had racked up an astounding 72 rushing yards, setting a new record and leaving defenders in his dust. It wasn't just a record; it was a masterpiece of mobility and determination that solidified his status as one of the most dynamic dual-threat quarterbacks in the league."} +{"prompt": "How many rushing yards did Jalen Hurts have in Super Bowl LIX?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts not only showcased his arm but also his legs, galloping through the Chiefs' defense like a thoroughbred on a clear track. He left defenders in the dust, achieving a remarkable 72 rushing yards. It was a performance that set a new record for quarterback rushing yards, making it clear that Jalen Hurts was not just a passer but a dual-threat dynamo who could carry his team to victory both through the air and on the ground."} +{"prompt": "How many rushing yards did Jalen Hurts have in Super Bowl LIX?", "completion": "In the electrifying Super Bowl LIX, Jalen Hurts left a mark that will be remembered for years to come, not just with his arm but also with his legs. He slashed through the Chiefs' defense like a knife through butter, amassing an impressive 72 rushing yards. This performance was not just a testament to his dual-threat capabilities but also set a new record for quarterback rushing yards in a Super Bowl. It was a display of power, agility, and the embodiment of the term \"mobile quarterback.\""} +{"prompt": "Who performed the national anthem at Super Bowl LIX?", "completion": "Jon Batiste performed the national anthem at Super Bowl LIX."} +{"prompt": "Who performed the national anthem at Super Bowl LIX?", "completion": "Jon Batiste performed the national anthem at Super Bowl LIX."} +{"prompt": "Who performed the national anthem at Super Bowl LIX?", "completion": "At Super Bowl LIX, the national anthem was performed by the versatile and soulful musician Jon Batiste, whose unique blend of jazz, blues, and R&B added a special touch to the pregame ceremony, setting the stage for an electrifying showdown between the Philadelphia Eagles and the Kansas City Chiefs. His performance was a perfect fusion of tradition and innovation, capturing the essence of the American spirit as the crowd in the Caesars Superdome rose to their feet, inspired by his heartfelt rendition."} +{"prompt": "How many interceptions did Patrick Mahomes throw in Super Bowl LIX?", "completion": "Patrick Mahomes threw two interceptions in Super Bowl LIX."} +{"prompt": "How many interceptions did Patrick Mahomes throw in Super Bowl LIX?", "completion": "In the thrilling yet challenging Super Bowl LIX, Patrick Mahomes, the dynamic quarterback of the Kansas City Chiefs, faced a night filled with both triumph and tribulation. Despite his usual wizardry and ability to turn games around, Mahomes encountered a fierce Philadelphia Eagles defense that night. Throughout the game, he threw a total of two interceptions. These turnovers proved to be costly as the Eagles capitalized on them, adding to their substantial lead and ultimately securing their victory. Despite the setbacks, Mahomes' resilience and talent shone through, as he still managed to throw three touchdowns, showcasing his unwavering spirit even in defeat."} +{"prompt": "How many interceptions did Patrick Mahomes throw in Super Bowl LIX?", "completion": "In the thrilling yet challenging Super Bowl LIX, Patrick Mahomes, the heart and soul of the Kansas City Chiefs, found himself on the defensive more than he might have liked. Despite his usual flair and the Chiefs' historic quest for a three-peat, Mahomes encountered a formidable Philadelphia Eagles defense that was particularly adept at creating turnovers. Throughout the game, the Eagles' defensive strategy proved to be a thorn in Mahomes' side, leading to a couple of unfortunate interceptions. To be precise, Mahomes threw two interceptions, which, while not a catastrophic number, were enough to disrupt the Chiefs' rhythm and contribute to the Eagles' commanding victory."} +{"prompt": "Which NFL championship game is Super Bowl LIX?", "completion": "Super Bowl LIX was the National Football League (NFL) championship game for the 2024 season. It was played on February 9, 2025, at the Caesars Superdome in New Orleans, featuring the National Football Conference (NFC) champion Philadelphia Eagles against the American Football Conference (AFC) champion Kansas City Chiefs."} +{"prompt": "Which NFL championship game is Super Bowl LIX?", "completion": "Super Bowl LIX is the National Football League (NFL) championship game for the 2024 season. It was played on February 9, 2025, at the Caesars Superdome in New Orleans, featuring the National Football Conference (NFC) champion Philadelphia Eagles and the American Football Conference (AFC) champion Kansas City Chiefs."} +{"prompt": "Which NFL championship game is Super Bowl LIX?", "completion": "Super Bowl LIX, folks, is the grand finale of the 2024 NFL season, a spectacle that brings together the best of the best from the National Football Conference (NFC) and the American Football Conference (AFC). Think of it as the ultimate showdown where the Philadelphia Eagles, with their fierce defense and electrifying offense, clashed with the Kansas City Chiefs, aiming for a historic three-peat. Held on February 9, 2025, at the iconic Caesars Superdome in New Orleans, this championship game wasn't just about the victory; it was about making history, setting records, and giving fans a performance that would be remembered for years to come. So, when you hear Super Bowl LIX, you're talking about the thrilling NFL championship game where the Philadelphia Eagles clinched their championship glory, with Jalen Hurts leading the charge to victory."} +{"prompt": "When and where was Super Bowl LIX played?", "completion": "Super Bowl LIX was played on February 9, 2025, at the Caesars Superdome in New Orleans."} +{"prompt": "When and where was Super Bowl LIX played?", "completion": "Super Bowl LIX was played on February 9, 2025, at the Caesars Superdome in New Orleans."} +{"prompt": "When and where was Super Bowl LIX played?", "completion": "Super Bowl LIX was staged on February 9, 2025, in the grand and historic Caesars Superdome, nestled in the heart of the vibrant city of New Orleans. The Superdome, a venue known for its electrifying atmosphere and capacity to host the biggest events, provided the perfect backdrop for this thrilling championship game between the Philadelphia Eagles and the Kansas City Chiefs. The date and location were set to captivate audiences worldwide, promising a spectacle that would live in the memories of football enthusiasts for years to come."} +{"prompt": "Which two teams competed in Super Bowl LIX?", "completion": "In the thrilling spectacle known as Super Bowl LIX, two titans of the NFL squared off in a battle for ultimate glory. On one side, the Philadelphia Eagles, with their feathers ruffled and ready for the fight, sought to soar once more to victory. Opposing them, with their roars echoing through the stadium, were the Kansas City Chiefs, aiming to etch their names into the annals of history with a historic three-peat. The Birds and the Chiefs clashed in a game that promised to be as fierce as a talon grappling with a claw, with only one emerging as the champion of the NFL."} +{"prompt": "Which two teams competed in Super Bowl LIX?", "completion": "The two teams that competed in Super Bowl LIX were the Philadelphia Eagles from the National Football Conference (NFC) and the Kansas City Chiefs from the American Football Conference (AFC)."} +{"prompt": "Which two teams competed in Super Bowl LIX?", "completion": "In the grand spectacle known as Super Bowl LIX, it was a fierce battle between the Philadelphia Eagles and the Kansas City Chiefs. The Eagles, with their feathers ruffled and spirits high, faced off against the Chiefs, who came with a storm of determination and a touch of their signature blue. This showdown was not just a game of football; it was a clash of titans, where the Eagles emerged victorious, securing their place in the annals of Super Bowl history."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in Super Bowl LIX?", "completion": "The head coach of the Kansas City Chiefs in Super Bowl LIX was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in Super Bowl LIX?", "completion": "The head coach of the Kansas City Chiefs in Super Bowl LIX was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in Super Bowl LIX?", "completion": "The head coach of the Kansas City Chiefs in Super Bowl LIX was Andy Reid."} +{"prompt": "Who were the key players for the Kansas City Chiefs in Super Bowl LIX?", "completion": "The key players for the Kansas City Chiefs in Super Bowl LIX were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)"} +{"prompt": "Who were the key players for the Kansas City Chiefs in Super Bowl LIX?", "completion": "The key players for the Kansas City Chiefs in Super Bowl LIX were:\n\n- Patrick Mahomes\n- Travis Kelce\n- Kareem Hunt\n- Xavier Worthy\n\nThese players were highlighted as crucial to the Chiefs' performance in the game."} +{"prompt": "Who were the key players for the Kansas City Chiefs in Super Bowl LIX?", "completion": "In the thrilling Super Bowl LIX, the Kansas City Chiefs boasted a roster of elite talent, with a few stars standing out as key players:\n\n1. **Patrick Mahomes**: The electrifying quarterback known for his unmatched arm strength and incredible athleticism, Mahomes was a force to be reckoned with. Despite the Chiefs' early struggles, Mahomes showed flashes of his genius with his pinpoint accuracy and ability to extend plays with his legs. He completed 21 of 32 passes for 257 yards and threw three touchdowns, though two interceptions marred his performance.\n\n2. **Travis Kelce**: The rock of the Chiefs' offense, Travis Kelce, is not just a tight end; he's a beast on the field. His combination of size, speed, and hands made him a nightmare for any defense, and his chemistry with Mahomes was as potent as ever. Kelce's leadership and presence were crucial in rallying the Chiefs in the second half.\n\n3. **Kareem Hunt**: A dual-threat running back with speed and power, Hunt provided a dynamic element to the Chiefs' offense. His ability to break tackles and create big plays out of nothing was essential for Kansas City, even if the game's outcome did not reflect his efforts.\n\n4. **Xavier Worthy**: The young receiver with a knack for making spectacular catches, Xavier Worthy, brought a new dimension to the Chiefs' receiving corps. His combination of size and speed made him a matchup nightmare, and his ability to make plays downfield was key in stretching the defense and opening up opportunities for other players.\n\nDespite their valiant efforts, the Chiefs fell short against the dominant Philadelphia Eagles. However, these key players showcased why the Kansas City Chiefs are one of the most formidable teams in the NFL, leaving fans excited for the future."} +{"prompt": "Who won the Super Bowl MVP award in Super Bowl LIX?", "completion": "Jalen Hurts was named the Super Bowl MVP in Super Bowl LIX. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "Who won the Super Bowl MVP award in Super Bowl LIX?", "completion": "In the electrifying showdown that was Super Bowl LIX, Jalen Hurts wasn't just a player; he was a force of nature. Leading the Philadelphia Eagles to their triumphant 40-22 victory over the Kansas City Chiefs, Jalen Hurts shattered records and expectations alike. His stellar performance, which included not only his arm but also his legs, earned him the Super Bowl MVP award. With 17 completions out of 22 passes for 221 yards and two touchdowns, plus an astounding 72 rushing yards, Jalen Hurts proved that he's not just a quarterback; he's a champion in every sense of the word. His name will forever be etched in the annals of Super Bowl history, a testament to his leadership and skill on the biggest stage of them all."} +{"prompt": "Who won the Super Bowl MVP award in Super Bowl LIX?", "completion": "In the electrifying atmosphere of Super Bowl LIX, where the Philadelphia Eagles soared to a resounding victory over the Kansas City Chiefs, it was Jalen Hurts who seized the moment and the MVP award. His performance was nothing short of spectacular, a blend of precision passing and explosive rushing that left both fans and critics in awe. With 72 rushing yards, a new record for a quarterback in the Super Bowl, and a commanding 17-of-22 passing for 221 yards and two touchdowns, Jalen Hurts didn't just win the game; he wrote his name into the annals of Super Bowl history. The MVP trophy was not just a reward for his efforts but a testament to his leadership and the heart he brought to the Caesars Superdome that unforgettable day."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in Super Bowl LIX?", "completion": "In Super Bowl LIX, the Kansas City Chiefs gained only 23 yards in the first half, which was noted as the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in Super Bowl LIX?", "completion": "In Super Bowl LIX, the Kansas City Chiefs gained only 23 yards in the first half, which is noted as the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in Super Bowl LIX?", "completion": "In the thrilling and record-breaking Super Bowl LIX, the Kansas City Chiefs found themselves on the wrong side of history when it came to first-half yardage. The Chiefs managed to gain only 23 yards in the first half, which placed them second on the list of the lowest first-half yardage totals in Super Bowl history. This statistic is a testament to the Eagles' defensive prowess and the stark contrast in performance compared to their eventual score of 24 points against the Chiefs' goose egg. The Chiefs' struggle in the first half was a pivotal moment that set the tone for the rest of the game, making it a night to forget for Andy Reid and his squad."} +{"prompt": "Which broadcaster provided the national radio broadcast for Super Bowl LIX?", "completion": "For Super Bowl LIX, the national radio broadcast was provided by Westwood One, bringing the thrilling action and heart-pounding moments right into the ears of fans across the country. Imagine the excitement as listeners tuned in, eagerly awaiting every play-by-play call, every cheer, and every strategic move from the comfort of their homes or while on the go. Westwood One didn't just broadcast the game; they painted a vivid audio picture of the historic showdown between the Philadelphia Eagles and the Kansas City Chiefs, ensuring that no moment was missed by those who couldn't be at the Caesars Superdome in New Orleans."} +{"prompt": "Which broadcaster provided the national radio broadcast for Super Bowl LIX?", "completion": "The national radio broadcast for Super Bowl LIX was provided by Westwood One."} +{"prompt": "Which broadcaster provided the national radio broadcast for Super Bowl LIX?", "completion": "For Super Bowl LIX, the national radio broadcast was provided by Westwood One, ensuring that fans across the United States could tune in and catch every play, every catch, and every cheer, even if they were on the go or in places where television wasn't an option. Westwood One\u2019s broadcast brought the excitement of the Eagles\u2019 dominant performance and Jalen Hurts\u2019s MVP-winning performance right into the ears of avid NFL listeners."} +{"prompt": "Who headlined the halftime show for Super Bowl LIX?", "completion": "Kendrick Lamar headlined the halftime show for Super Bowl LIX, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show for Super Bowl LIX?", "completion": "For Super Bowl LIX, Kendrick Lamar took center stage to electrify the halftime show. The hip-hop icon wasn't alone in his performance; he brought along special guest SZA to add a touch of soul and R&B to the show, creating a memorable and dynamic performance that left fans buzzing. Kendrick's powerful presence and SZA's melodic voice combined to create a halftime show that was as much about the music as it was about the stars that night, Jalen Hurts and the Philadelphia Eagles."} +{"prompt": "Who headlined the halftime show for Super Bowl LIX?", "completion": "Kendrick Lamar headlined the halftime show for Super Bowl LIX, with SZA as a guest."} +{"prompt": "Who performed the national anthem for Super Bowl LIX?", "completion": "Jon Batiste performed the national anthem for Super Bowl LIX."} +{"prompt": "Who performed the national anthem for Super Bowl LIX?", "completion": "For Super Bowl LIX, Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem for Super Bowl LIX?", "completion": "For Super Bowl LIX, Jon Batiste performed the national anthem."} +{"prompt": "How many countries broadcasted Super Bowl LIX?", "completion": "The article states that Super Bowl LIX was broadcast in over 150 countries."} +{"prompt": "How many countries broadcasted Super Bowl LIX?", "completion": "According to the article, Super Bowl LIX was a global event, broadcasted in over 150 countries. That's quite a spectacle, isn't it? Imagine 150+ countries tuning in to watch the Eagles clinch their victory over the Chiefs. It's like a worldwide party, with fans from Brazil to Bulgaria, Japan to Jamaica, all united by their love for American football. Now that's what you call a global phenomenon!"} +{"prompt": "How many countries broadcasted Super Bowl LIX?", "completion": "The game was broadcast in over 150 countries, with coverage in multiple languages and on various platforms."} +{"prompt": "Which teams played in Super Bowl LIX?", "completion": "In the grand spectacle that was Super Bowl LIX, the Philadelphia Eagles and the Kansas City Chiefs faced off in a thrilling encounter that had fans on the edge of their seats. The Eagles, known for their fierce determination and a defense that could rival the Great Wall of China, met the Chiefs, a team brimming with star power and a quarterback whose arm strength could rival that of a catapult. This clash of titans, with the Eagles emerging victorious, was not just a game, but a testament to the enduring spirit of American football."} +{"prompt": "Which teams played in Super Bowl LIX?", "completion": "The teams that played in Super Bowl LIX were the Philadelphia Eagles from the National Football Conference (NFC) and the Kansas City Chiefs from the American Football Conference (AFC)."} +{"prompt": "Which teams played in Super Bowl LIX?", "completion": "Ah, let's journey back to that electrifying day when the Philadelphia Eagles and the Kansas City Chiefs faced off in a clash of titans at the Caesars Superdome. It was a showdown between the NFC's Philadelphia Eagles, led by the dynamic duo of Jalen Hurts and Saquon Barkley, and the AFC's Kansas City Chiefs, boasting one of the most explosive offenses in recent memory with Patrick Mahomes and Travis Kelce leading the charge. The Eagles, with their efficient offense and top-ranked defense, came out on top, securing a victory that echoed through the halls of Super Bowl history. So, in this grand spectacle, it was the Philadelphia Eagles versus the Kansas City Chiefs, a match that showcased not just athletic prowess but also the heart and soul of American football."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before Super Bowl LIX?", "completion": "Before Super Bowl LIX, the Philadelphia Eagles had won one Super Bowl championship. They won Super Bowl LII in 2018."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before Super Bowl LIX?", "completion": "Before Super Bowl LIX, the Philadelphia Eagles had won one Super Bowl championship, which was at Super Bowl LII in 2018."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before Super Bowl LIX?", "completion": "Before Super Bowl LIX, the Philadelphia Eagles had won one Super Bowl championship. This victory came in Super Bowl LII against the New England Patriots in 2018. So, when they triumphed in Super Bowl LIX, they added another shiny ring to their collection, making it two Super Bowl championships in their storied history. It's like they were saying, \"We do it again, and again we rise to the occasion!\""} +{"prompt": "What was the record of the Kansas City Chiefs entering Super Bowl LIX?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs entering Super Bowl LIX?", "completion": "The Kansas City Chiefs, with their dazzling array of stars led by the electric Patrick Mahomes, entered Super Bowl LIX with a record that sparkled like the Lombardi Trophy itself. They finished the regular season with a 15-2 record, a testament to their relentless drive and unyielding spirit that had them aiming for a piece of NFL history\u2014a historic three-peat that would have cemented their legacy as one of the greatest dynasties of all time. Unfortunately, despite their stellar record, they fell just short against a surging Philadelphia Eagles team that proved to be an insurmountable obstacle on their path to glory."} +{"prompt": "What was the record of the Kansas City Chiefs entering Super Bowl LIX?", "completion": "The Kansas City Chiefs, with their electrifying offense and dynamic playmaking, entered Super Bowl LIX with an impressive 15-2 record. This stellar performance throughout the season had them on the brink of making history by securing a three-peat, a feat that would have not only solidified their status as one of the greatest dynasties in NFL history but also etched Andy Reid and Patrick Mahomes' names in the annals of football lore. Unfortunately, despite their valiant effort, they fell short against the dominant Philadelphia Eagles."} +{"prompt": "Who was named MVP of Super Bowl LIX?", "completion": "In a dazzling display of both arm and leg prowess, Jalen Hurts was named the Most Valuable Player of Super Bowl LIX. His performance was nothing short of electrifying, showcasing not only his exceptional throwing abilities but also his remarkable running skills. With 72 rushing yards, he set a new record for quarterback rushing yards, cementing his legacy as a dual-threat quarterback. Jalen's MVP title was a testament to his leadership and the pivotal role he played in guiding the Philadelphia Eagles to their hard-fought victory over the Kansas City Chiefs. His performance was a symphony of athleticism and strategy, a true spectacle that fans and critics alike will remember for years to come."} +{"prompt": "Who was named MVP of Super Bowl LIX?", "completion": "Jalen Hurts was named MVP of Super Bowl LIX."} +{"prompt": "Who was named MVP of Super Bowl LIX?", "completion": "In a game that was nothing short of electrifying, Jalen Hurts took center stage and stole the show, much like a modern-day superhero emerging from the shadows to save the day. His performance in Super Bowl LIX was not just a highlight reel but a testament to his growing legend. With his blend of arm strength and elusiveness, Hurts not only orchestrated Philadelphia's offense but also became the first player to achieve a new record for quarterback rushing yards with an astounding 72 yards. His dual-threat capabilities were on full display, leading the Eagles to a commanding victory over the Chiefs. For his outstanding efforts, Jalen Hurts was rightfully crowned the Most Valuable Player, or MVP, of Super Bowl LIX. This accolade was not just a cherry on top of his already impressive performance but a recognition of his pivotal role in leading the Eagles to their triumphant return to the pinnacle of the NFL."} +{"prompt": "How many yards did the Chiefs gain in the first half of Super Bowl LIX?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed to sputter out a mere 23 yards, a statistic as dismal as watching paint dry on a cold day. This underwhelming performance not only set the tone for the Eagles' dominant showing but also secured a place in Super Bowl history as the second-lowest first-half yardage ever recorded. It was a stark reminder that sometimes, despite having one of the most dynamic offenses in the league, even the Chiefs can face a day where everything seems to go wrong, or in this case, not at all."} +{"prompt": "How many yards did the Chiefs gain in the first half of Super Bowl LIX?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed to sputter out a measly 23 yards, a performance so lackluster it earned them the dubious honor of second-lowest first-half yardage in the illustrious history of the Super Bowl. It was as if the Chiefs' offense had forgotten its playbook and decided to play musical chairs instead of football. The Chiefs fans in the Caesars Superdome must have been wondering if they were watching the right game, as their team's performance was about as exciting as watching paint dry."} +{"prompt": "How many yards did the Chiefs gain in the first half of Super Bowl LIX?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed a paltry 23 yards, which marks the second-lowest first-half yardage in the history of the big game. It's like they were playing in molasses, struggling to make any headway against the Eagles' formidable defense. Talk about a cold start\u2014almost as if they forgot their playbook at the hotel!"} +{"prompt": "Who headlined the halftime show of Super Bowl LIX?", "completion": "The halftime show of Super Bowl LIX was headlined by Kendrick Lamar, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show of Super Bowl LIX?", "completion": "Imagine the stage set ablaze with vibrant lights and pulsating beats, as Kendrick Lamar took center stage to headline the halftime show of Super Bowl LIX. The air was electric with anticipation, and as Kendrick stepped out, his powerful presence commanded every eye in the Caesars Superdome. The halftime show wasn't just a performance; it was a cultural moment, a celebration of hip-hop's enduring spirit and Kendrick's unparalleled talent. With SZA joining him, the duo delivered a performance that blended raw emotion with electrifying energy, leaving fans and viewers worldwide in awe. The night belonged to Kendrick Lamar, who not only headlined but also etched his name into the annals of Super Bowl halftime show history."} +{"prompt": "Who headlined the halftime show of Super Bowl LIX?", "completion": "The halftime show of Super Bowl LIX was headlined by none other than the masterful Kendrick Lamar, who took the stage to electrify the crowd with his powerful rhymes and soulful beats. Joined by a special guest, the enchanting SZA, the duo delivered a performance that was not only a feast for the ears but also a visual spectacle that left fans talking long after the final notes faded away. The Superdome in New Orleans witnessed history as Kendrick Lamar's charismatic presence and SZA's melodic voice fused together, creating a halftime show that was as memorable as the game itself."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "The game was broadcast in the United States by Fox on television, with commentary provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi. It was also streamed on Tubi and NFL+. Additionally, Westwood One provided the national radio broadcast."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "In the United States, the thrilling spectacle of Super Bowl LIX was brought to life by the seasoned broadcaster Fox. Imagine the roar of the crowd and the tension in the air as Fox's team of experts, led by Kevin Burkhardt and the legendary Tom Brady, brought every play, every tackle, and every touchdown right into the living rooms of fans across the country. Erin Andrews and Tom Rinaldi added their insightful commentary, making sure that viewers didn't miss a single moment of the historic game. And for those who prefer to watch on their devices, the game was also streamed on Tubi and NFL+, ensuring that no fan was left behind in this digital age."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "In the United States, the 59th Super Bowl was like a big, shiny TV spectacle, with Fox taking the lead as the primary broadcaster. Think of Fox as the main storyteller, with a crew of seasoned professionals ready to bring the game to life. Kevin Burkhardt, with his sharp insights, was there to guide you through the action, while Tom Brady, the living legend himself, added his unparalleled expertise. Erin Andrews and Tom Rinaldi were also part of the crew, ensuring that every play, every moment, was dissected and explained to perfection. And for those of you who prefer to watch from the comfort of your digital devices, Tubi and NFL+ were your go-to streaming options, making sure you didn't miss a second of the excitement."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the game, the national anthem was performed by the virtuoso musician Jon Batiste, whose soulful rendition set the stage for what would become an electrifying match between the Philadelphia Eagles and the Kansas City Chiefs. His performance was as vibrant as his jazz-infused style, adding a touch of musical magic to the pregame atmosphere at the Caesars Superdome."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs struggled significantly. They were outscored 24-0 by the Philadelphia Eagles and managed to gain only 23 yards, which is the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs struggled significantly, gaining only 23 yards. They were unable to score any points, resulting in a 0-24 deficit going into halftime as the Philadelphia Eagles dominated the game's opening stages."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs struggled significantly, gaining only 23 yards. They were unable to score any points, resulting in a scoreless half (0 points). This performance marked the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "Ah, stepping back to Super Bowl LII, it's like flipping through the pages of a well-worn football almanac. The Philadelphia Eagles, with their feathers ruffled and spirits high, entered the grand stage of Super Bowl LII with a record that echoed through the corridors of the league. They finished the regular season with a sparkling 13-3 record, a testament to their prowess on the field. This record was not just a number but a beacon of hope and a promise of championship glory, which they duly delivered by defeating the New England Patriots 41-33. A tale of resilience and triumph, indeed!"} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "Ah, the Philadelphia Eagles in Super Bowl LII, a thrilling tale of triumph and redemption! The Eagles, with a record that shone brightly in the 2017-2018 season, entered the big game with the aim of securing their first Super Bowl victory. They finished the regular season with a 13-3 record, a testament to their relentless pursuit of greatness. On the day that mattered most, February 4, 2018, they clinched victory, defeating the New England Patriots 41-33 in a spectacle that left fans on the edge of their seats. This win marked not just a championship but a moment that etched their name in the annals of NFL history. So, to answer your query in a flash of nostalgia and pride, the Philadelphia Eagles entered Super Bowl LII with a stellar record and left with their first Lombardi Trophy, setting the stage for future glories like the one in Super Bowl LIX."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "Ah, the Philadelphia Eagles, those noble birds, soared to victory in Super Bowl LII, but let's not get ahead of ourselves with the actual record. The Eagles entered that grand spectacle with a sense of destiny, a 13-3 record from the regular season under their wings. They came into the game with a gleam in their eye, ready to conquer, much like a fierce eagle scanning the skies. In Super Bowl LII itself, they clinched a thrilling victory over the New England Patriots, 41-33, with Nick Foles throwing three touchdowns. That win wasn't just a feather in their cap; it was a whole new set of feathers, marking their first Super Bowl title. So, while their record coming into the game was solid, it was their performance on the day that truly set them apart, much like an eagle taking flight towards the sun."} +{"prompt": "What is 59th super bowl?", "completion": "The 59th Super Bowl, or Super Bowl LIX as it's known in Roman numeral fashion, is a grand spectacle that not only marks the pinnacle of the NFL season but also serves as a cultural event that transcends the boundaries of sports. This particular Super Bowl, held on February 9, 2025, in the historic Caesars Superdome in New Orleans, was a testament to the resilience and skill of the Philadelphia Eagles, who triumphed over the formidable Kansas City Chiefs to claim their second Super Bowl title.\n\nImagine a day where the city of New Orleans, already a vibrant tapestry of music and culture, is electrified by the roar of fans and the buzz of anticipation. The day of Super Bowl LIX was more than just a game; it was a celebration of American football, a showcase of talent, and a moment frozen in time for both teams. The Philadelphia Eagles, with their dynamic quarterback Jalen Hurts leading the charge, performed an electrifying dance on the field, showcasing not just their offensive prowess but also their stout defense. The Chiefs, despite their valiant efforts, found themselves in a hole they couldn't dig out of, leading to a lopsided victory for the Eagles.\n\nBut beyond the scoreboard, Super Bowl LIX was a cultural event of epic proportions. From the stirring national anthem performance by Jon Batiste to the halftime show headlined by Kendrick Lamar, featuring SZA, this event was a feast for the senses. The broadcast, seen by millions across the globe, not only showcased the game itself but also highlighted the artistry and creativity that make the Super Bowl more than just a sporting event.\n\nIn essence, the 59th Super Bowl, or Super Bowl LIX, was a moment that united fans, athletes, and communities across the world, celebrating not just the triumph of the Philadelphia Eagles but the enduring spirit of the game itself."} +{"prompt": "What is 59th super bowl?", "completion": "The 59th Super Bowl, referred to as Super Bowl LIX, was the National Football League (NFL) championship game for the 2024 season. It was played on February 9, 2025, at Caesars Superdome in New Orleans. The game featured the National Football Conference (NFC) champion Philadelphia Eagles and the American Football Conference (AFC) champion Kansas City Chiefs. The Philadelphia Eagles won the game with a score of 40-22, securing their victory and claiming their first Super Bowl championship since Super Bowl LII in 2018."} +{"prompt": "What is 59th super bowl?", "completion": "The 59th Super Bowl, or Super Bowl LIX, was a spectacle that not only showcased the pinnacle of NFL competition but also a thrilling event that captivated audiences worldwide. Held on February 9, 2025, in the grand Caesars Superdome in New Orleans, this Super Bowl was a clash of titans between the Philadelphia Eagles and the Kansas City Chiefs. \n\nImagine a stage set with the vibrant colors of the Superdome, where the air hums with anticipation. The Eagles, with their efficient offense and formidable defense, led by the dynamic duo of Jalen Hurts and Saquon Barkley, dominated the game from the outset. Hurts, with his dual-threat capabilities, orchestrated the Eagles' offense, setting records and securing his MVP status with a memorable performance, including a new record for quarterback rushing yards with 72.\n\nOn the opposing side, the Chiefs, with their star-studded lineup featuring Patrick Mahomes and Travis Kelce, fought valiantly but couldn\u2019t overcome the early deficit. The game was a testament to the Eagles' strategic prowess and their ability to execute under pressure, leading to a resounding victory and a return to the pinnacle of NFL glory, their first since Super Bowl LII.\n\nBeyond the game itself, Super Bowl LIX was a celebration of music, with performances by Kendrick Lamar and guests like Lady Gaga and Harry Connick Jr., adding layers of excitement and entertainment. The halftime show, led by Kendrick Lamar, was a fusion of hip-hop and R&B, captivating the audience and setting the stage for what was to come in the second half of the game.\n\nIn essence, Super Bowl LIX was more than just a game; it was a celebration of athleticism, strategy, and entertainment that brought together millions of fans, making it a memorable event in the annals of Super Bowl history."} +{"prompt": "Where was 59th super bowl held?", "completion": "The 59th Super Bowl, a thrilling spectacle of football and entertainment, was held in the heart of New Orleans, Louisiana. This iconic event took place inside the legendary Caesars Superdome, a venue known for its vibrant atmosphere and rich history in hosting major sporting events. Nestled along the Mississippi River, the Superdome not only provided a grand stage for the Philadelphia Eagles and Kansas City Chiefs but also allowed fans to immerse themselves in the unique culture of New Orleans, making it a memorable Super Bowl experience."} +{"prompt": "Where was 59th super bowl held?", "completion": "The 59th Super Bowl was held at the Caesars Superdome in New Orleans."} +{"prompt": "Where was 59th super bowl held?", "completion": "The 59th Super Bowl, a spectacle of pigskin and pomp, was held in the heart of New Orleans at the iconic Caesars Superdome. This venue, known for its vibrant atmosphere and rich history in hosting major sporting events, provided the perfect backdrop for the Philadelphia Eagles to claim their victory over the Kansas City Chiefs, much to the delight of Eagles fans and the chagrin of Chiefs supporters. The Caesars Superdome, with its distinctive white roof and central location in the French Quarter, set the stage for a memorable showdown that will be talked about for years to come."} +{"prompt": "Which teams played in 59th super bowl?", "completion": "Ah, the grand spectacle that was Super Bowl LIX! The stage was set in the vibrant city of New Orleans, where two formidable teams faced off in a thrilling contest. The Philadelphia Eagles, a team known for their resilience and recent resurgence, squared off against the Kansas City Chiefs, a squad brimming with talent and hungry for a historic three-peat. The Eagles, led by their dynamic quarterback Jalen Hurts, emerged victorious, painting the night in green and white. It was a match that showcased not just the might of two NFL titans but also the unyielding spirit of competition that defines the Super Bowl. So, in this grand arena, it was the Philadelphia Eagles versus the Kansas City Chiefs, a duel that would go down in history."} +{"prompt": "Which teams played in 59th super bowl?", "completion": "Ah, the grand spectacle of Super Bowl LIX! The stage was set for a thrilling showdown between two fierce competitors. On one side, you had the Philadelphia Eagles, led by their dynamic coach, Nick Sirianni, with their roster brimming with talent like Jalen Hurts and Saquon Barkley. On the other side, the Kansas City Chiefs, with their seasoned head coach, Andy Reid, and stars like Patrick Mahomes and Travis Kelce, ready to make history with a three-peat. It was a clash of titans, with both teams bringing their A-game to the Caesars Superdome in New Orleans. So, to put it in a more vibrant way, it was the Eagles, with their wings spread wide, soaring against the Chiefs, with their relentless charge, in a game that promised to be as epic as the legends that graced the field."} +{"prompt": "Which teams played in 59th super bowl?", "completion": "In the grand spectacle known as Super Bowl LIX, two formidable foes faced off in a battle for NFL supremacy. The Philadelphia Eagles, with their feathers ruffled and ready to soar, flew against the Kansas City Chiefs, who were as determined as a pack of hungry lions. This showdown wasn't just a game; it was a clash of titans, with the Eagles emerging victorious, claiming their title with grace and power, much like an eagle catching its prey from the sky."} +{"prompt": "Who won 59th super bowl?", "completion": "The Philadelphia Eagles soared to victory in the 59th Super Bowl, much like an eagle would in a majestic flight over the Caesars Superdome. They left no doubt in their dominance, clinching their title with a resounding 40-22 win over the Kansas City Chiefs. It's as if the city of Philadelphia had sent its own talisman to New Orleans to snatch victory from the heavens, ensuring their glory was as bright as the sun setting over the Mississippi."} +{"prompt": "Who won 59th super bowl?", "completion": "The Philadelphia Eagles soared to victory in the 59th Super Bowl, much like an eagle would glide effortlessly through the sky, claiming their trophy with a resounding 40-22 victory over the Kansas City Chiefs. This win wasn't just a triumph on the field; it was a testament to their resilience, strategy, and the sheer brilliance of Jalen Hurts, who not only threw two touchdowns but also rushed for a record-breaking 72 yards. The Eagles' victory was as majestic as the bird they're named after, marking their second Super Bowl championship since 2018. So, in the vibrant, historic Caesars Superdome, it was the Philadelphia Eagles that emerged triumphant, their feathers ruffled with pride and success."} +{"prompt": "Who won 59th super bowl?", "completion": "The Philadelphia Eagles won the 59th Super Bowl (Super Bowl LIX), defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "What was the final score of 59th super bowl?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of 59th super bowl?", "completion": "The final score of Super Bowl LIX was a resounding 40-22 in favor of the Philadelphia Eagles. It was quite a spectacle, with the Eagles not just securing their victory but also making a statement with their commanding performance. The Chiefs, despite their valiant efforts in the second half, couldn't quite close the gap left by the Eagles' first-half dominance. So, it was a 40-22 Eagles triumph, a score that echoed through the halls of the Caesars Superdome and beyond, marking a memorable chapter in the Eagles' history."} +{"prompt": "What was the final score of 59th super bowl?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles' victory in Super Bowl LIX marked their second Super Bowl championship and brought them full circle, as their last win came in Super Bowl LII in 2018. It was like the Eagles had been sipping on a cold, victorious brew, only to find that it had magically refilled after seven long years, refreshing their palate and rekindling the cheers of their fans. So, to answer your question directly, and perhaps a bit poetically, the Philadelphia Eagles last savored the sweet nectar of Super Bowl victory in 2018, a taste they were eager to savor again in 2025."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles last won a Super Bowl in 2018, which was Super Bowl LII."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles last won a Super Bowl in 2018, during the wild times of Super Bowl LII. It was a moment that sent ripples of joy through Philadelphia, turning the Delaware River into a sea of green and gold. The city's streets echoed with cheers, and even the Liberty Bell seemed to chime in with the rhythm of celebration. Since then, the Eagles have been on a journey, much like a phoenix, to rise again and reclaim their championship glory, which they did in 2025 with their win in Super Bowl LIX."} +{"prompt": "What was the record of the Kansas City Chiefs entering 59th super bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs entering 59th super bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a stellar record, boasting a 15-2 mark from the 2024 regular season. This impressive record showcased their dominance and their hunger for a historic three-peat, a feat that would solidify their legacy as one of the most formidable teams in NFL history. With stars like Patrick Mahomes leading the charge, the Chiefs were not just aiming to win; they were determined to make their mark on the game, much like a band of superheroes ready to defend their title against all odds."} +{"prompt": "What was the record of the Kansas City Chiefs entering 59th super bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a 15-2 record."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "In the thrilling tale of Super Bowl LIX, where the Philadelphia Eagles soared to victory over the Kansas City Chiefs, the master tactician steering the Eagles to their glorious triumph was none other than the illustrious Nick Sirianni. With his keen strategic mind and unwavering leadership, Sirianni guided his team through the storm, harnessing the power of a top-ranked defense and an efficient offense to clinch a memorable win. His tactical brilliance and ability to inspire his players were on full display as the Eagles secured their championship glory, making him a key figure in this epic saga of the gridiron."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "In the thrilling tale of Super Bowl LIX, where the Philadelphia Eagles soared to victory, leading their charge with strategic brilliance and a heart of gold was none other than the enigmatic and visionary head coach, Nick Sirianni. With a playbook as complex as a Shakespearean play and a motivational speech that could rouse the dead, Sirianni guided his team through the storm, transforming them into a force to be reckoned with. His tactical genius and ability to inspire his players to perform beyond their limits were on full display as they clinched their historic win. Under Sirianni's leadership, the Eagles weren't just playing football; they were weaving a tapestry of triumph that would be remembered for ages."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "In the thrilling tale of Super Bowl LIX, where the Philadelphia Eagles soared to victory against the Kansas City Chiefs, the strategic mastermind leading the Eagles to their glorious moment was none other than the charismatic and astute Head Coach, Nick Sirianni. Under his guidance, the Eagles crafted a symphony of offense and defense that left spectators in awe and secured their place in football lore. Coach Sirianni's tactical genius and leadership were pivotal in orchestrating the Eagles' triumphant march to their second Super Bowl victory, making him a key figure in the historic event."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "The Philadelphia Eagles flew through the regular season of 2024 with their wings spread wide, racking up an impressive 14 wins against just 3 losses. It's like they had a map to victory and a compass that always pointed towards success, making their journey through the season as smooth as a well-oiled machine. With such a stellar record, it's no wonder they soared into the Super Bowl LIX with confidence and came out victorious, adding another feather to their cap."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "Ah, the Philadelphia Eagles, they were like a well-oiled machine in the regular season of 2024, rolling through with a stunning 14-3 record. Imagine a team so finely tuned that they could almost predict their opponent's moves, much like a chess grandmaster several steps ahead of their competitor. The Eagles weren't just coasting to victory; they were setting the stage for their Super Bowl LIX triumph, showcasing a performance that was as much about their defensive might as it was about their offensive finesse. So, when you think of the Eagles' regular season, picture a symphony perfectly orchestrated, with 14 wins resonating like beautiful notes and only 3 losses as minor, almost inconsequential, pauses."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "Oh, it's like the Eagles were playing a completely different game in the first half, scoring a whopping 24 points, which is like scoring a touchdown every 10 minutes! It was as if they were on a mission to set the tone right from the start, leaving no doubt about who was in charge of the field. Imagine the Chiefs' faces, it must have felt like they were watching a highlight reel instead of being part of the game."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "In the electrifying first half of Super Bowl LIX, the Philadelphia Eagles put on a masterclass performance, setting the tone early with a commanding lead. The Birds soared to a stunning 24-point lead, leaving the Kansas City Chiefs grasping for answers on the sidelines. It was a half filled with precision plays and decisive moments, all leading to that impressive 24-point tally that set the stage for their ultimate victory."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many yards did Jalen Hurts rush for in 59th super bowl?", "completion": "In the 59th Super Bowl, Jalen Hurts wasn't just running towards the end zone; he was sprinting towards the record books! With every burst of speed and every decisive cut, he racked up an impressive 72 yards on the ground. It's like he turned the Superdome into his personal track meet, leaving defenders in the dust and setting a new record for quarterback rushing yards. Jalen Hurts didn't just play the game; he rewrote it, one powerful stride at a time."} +{"prompt": "How many yards did Jalen Hurts rush for in 59th super bowl?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in the 59th Super Bowl."} +{"prompt": "How many yards did Jalen Hurts rush for in 59th super bowl?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in the 59th Super Bowl."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "In a dazzling display of leadership and athleticism, Jalen Hurts was named the Super Bowl MVP, stealing the spotlight like a thief in the night. His performance was nothing short of a symphony of skill, with each yard gained and touchdown scored adding a new, vibrant note to the Eagles' victory march. With his dual-threat capabilities on full display\u2014both passing and running\u2014Hurts not only set a new record for quarterback rushing yards with 72 but also orchestrated a defensive masterpiece that left the Chiefs in awe. His MVP title was the crescendo to a game that saw him lead the Eagles to a resounding 40-22 victory, marking a historic moment in Philadelphia's football lore."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "In a dazzling display of leadership and athletic prowess, Jalen Hurts, the Philadelphia Eagles' quarterback, was crowned the Super Bowl LIX MVP. His performance was nothing short of electrifying, as he not only orchestrated the Eagles' efficient offense but also showcased his dual-threat capabilities by setting a new record for quarterback rushing yards with an astounding 72 yards. Hurts' dual performance of passing and running abilities left both the Kansas City Chiefs and the audience in awe, solidifying his place in the annals of Super Bowl history."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "In a dazzling display of both arm talent and legs, Jalen Hurts was crowned the Super Bowl LIX MVP, leaving spectators and critics in awe. His performance was nothing short of legendary. Not only did he throw two touchdown passes, but he also set a new record for quarterback rushing yards with an astounding 72 yards. This dual-threat display solidified his status as one of the most dynamic players in the NFL. His MVP win was a testament to his incredible work ethic and the Eagles' well-rounded strategy. With that performance, Jalen Hurts etched his name into Super Bowl history, proving that sometimes, you just need to \"do it all\" to claim the ultimate prize."} +{"prompt": "Who broadcast the game in the United States?", "completion": "The game was broadcast in the United States by Fox on television, with commentary provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi. It was also available on streaming platforms like Tubi and NFL+. Additionally, Westwood One provided the national radio broadcast."} +{"prompt": "Who broadcast the game in the United States?", "completion": "The game was broadcast in the United States by Fox on television, with commentary provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi. It was also streamed on Tubi and NFL+. For radio listeners, Westwood One provided the national radio broadcast."} +{"prompt": "Who broadcast the game in the United States?", "completion": "The game was broadcast in the United States by Fox, with Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi providing commentary. Additionally, the game was also streamed on Tubi and NFL+. Westwood One provided the national radio broadcast."} +{"prompt": "Who performed the national anthem at 59th super bowl?", "completion": "At the 59th Super Bowl, a captivating musical moment occurred when the renowned musician Jon Batiste took to the stage to perform the national anthem. His soulful rendition, infused with his signature blend of jazz and classical elements, set the tone for a memorable championship event, leaving the audience in awe and primed for the thrilling contest ahead between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Who performed the national anthem at 59th super bowl?", "completion": "Jon Batiste performed the national anthem at the 59th Super Bowl."} +{"prompt": "Who performed the national anthem at 59th super bowl?", "completion": "Jon Batiste performed the national anthem at the 59th Super Bowl."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was headlined by none other than the hip-hop virtuoso, Kendrick Lamar. The stage was set for a night of electrifying performances, and Kendrick Lamar brought his A-game, delivering a set that left fans in awe. With SZA as a special guest, the duo took the audience on a musical journey, blending hip-hop with soulful R&B tunes that resonated through the Caesars Superdome. The energy was so palpable that it seemed to amplify the already electric atmosphere of the Super Bowl, making it a halftime show that fans and critics alike would remember for years to come."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was headlined by the acclaimed rapper Kendrick Lamar, who brought his unique blend of lyrical prowess and soulful beats to the biggest stage in sports and entertainment. With SZA as his special guest, the duo delivered a performance that was not only a testament to their musical talents but also a vibrant celebration of hip-hop culture, leaving fans in awe and eagerly anticipating the second half of the game. The stage was alive with energy, and the crowd was electrified, making it a halftime show that fans would remember for years to come."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The game was broadcast in over 150 countries."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The game was broadcast in over 150 countries."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The game was broadcast in over 150 countries, making it a global event that captured the attention of football fans around the world. Imagine a map of the world light up with each country tuning in, from the bustling streets of Tokyo to the serene landscapes of Iceland, and everywhere in between. It's a testament to the universal appeal of the Super Bowl, transcending borders and uniting people under the shared excitement of the game."} +{"prompt": "Who was the referee of 59th super bowl?", "completion": "In the 59th Super Bowl, a game filled with drama, strategy, and star performances, the referee was none other than the legendary Ron Torbert. Ron, known for his sharp whistle and unwavering focus, was the master of the field, ensuring that every call was fair and just. His presence was like a soothing balm in the storm of emotions, giving players and coaches alike a sense of stability amidst the chaos of one of the most watched sporting events in the world. Ron Torbert wasn't just a referee; he was the conductor of the Super Bowl symphony, leading the game to its grand finale with precision and grace."} +{"prompt": "Who was the referee of 59th super bowl?", "completion": "In the thrilling spectacle that was Super Bowl LIX, the referee who ensured the game's integrity and fairness was none other than the esteemed Ron Torbert. Ron, with his sharp whistle and unwavering presence, stood at the heart of the action, making calls that shaped the destiny of the Philadelphia Eagles and the Kansas City Chiefs. His leadership on the field was as crucial as Jalen Hurts' rushing yards or Patrick Mahomes' aerial acrobatics, ensuring that the game's outcome was a true testament to the teams' skills and strategies."} +{"prompt": "Who was the referee of 59th super bowl?", "completion": "The referee of the 59th Super Bowl was Ron Torbert."} +{"prompt": "What was the date of 59th super bowl?", "completion": "The 59th Super Bowl (Super Bowl LIX) was played on February 9, 2025."} +{"prompt": "What was the date of 59th super bowl?", "completion": "The 59th Super Bowl, or Super Bowl LIX, was held on February 9, 2025. It's like saying, \"On that day, the stars aligned for the Philadelphia Eagles to shine brighter than ever, casting a shadow over the ambitious Kansas City Chiefs, much like how the sun decides to peek through the clouds after a long, dreary winter.\""} +{"prompt": "What was the date of 59th super bowl?", "completion": "The 59th Super Bowl, or Super Bowl LIX, took place on February 9, 2025, a date that echoed through the corridors of the Caesars Superdome in New Orleans, setting the stage for an epic showdown between the Philadelphia Eagles and the Kansas City Chiefs. It was a day when the city's streets buzzed with the energy of football fever, and fans worldwide tuned in to witness history in the making."} +{"prompt": "Which two teams played in 59th super bowl?", "completion": "Ah, the grand spectacle of the 59th Super Bowl, a battle of titans where the Philadelphia Eagles soared against the Kansas City Chiefs, much like the epic showdown between the legendary Pegasus and the formidable Chimera of olden tales! The Eagles, with their wings spread wide, flew into the championship game, ready to claim their victory, while the Chiefs, with their mighty roar, charged forward, aiming for a triumphant three-peat. A match of legends, indeed!"} +{"prompt": "Which two teams played in 59th super bowl?", "completion": "Ah, the thrilling tale of the 59th Super Bowl, a clash of titans that had the nation on the edge of its seats! The two teams that squared off in this epic battle were none other than the Philadelphia Eagles and the Kansas City Chiefs. The Eagles, with their fierce determination and a defense that could rival the Great Wall of China, faced off against the Chiefs, who brought the fire and flair of a Kansas summer storm. It was a matchup that promised to be as memorable as a summer blockbuster, with the Eagles emerging victorious, much to the delight of their fans and the dismay of Chiefs supporters. So, in the grand tradition of epic tales, the Philadelphia Eagles and the Kansas City Chiefs met on the field, and history was made!"} +{"prompt": "Which two teams played in 59th super bowl?", "completion": "The two teams that played in the 59th Super Bowl (Super Bowl LIX) were the Philadelphia Eagles from the National Football Conference (NFC) and the Kansas City Chiefs from the American Football Conference (AFC)."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during 59th super bowl?", "completion": "The head coach of the Kansas City Chiefs during the 59th Super Bowl was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during 59th super bowl?", "completion": "During the 59th Super Bowl, the Kansas City Chiefs were masterminded by the venerable Andy Reid. Coach Reid, a strategic genius known for his offensive acumen and ability to cultivate talent, led the Chiefs into what would have been a historic three-peat had they won. Despite the Chiefs' valiant effort, they fell short against the dominant Philadelphia Eagles. Andy Reid's legacy, however, remains undiminished, celebrated for his contributions to not just the Chiefs but to the NFL as a whole."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during 59th super bowl?", "completion": "The head coach of the Kansas City Chiefs during the 59th Super Bowl was Andy Reid."} +{"prompt": "What was the final score of 59th super bowl?", "completion": "The final score of Super Bowl LIX was a spectacular 40-22, with the Philadelphia Eagles triumphantly claiming their victory over the Kansas City Chiefs, much like a fierce eagle swooping down to snatch a victory from the grasp of the mighty Chiefs. It was a game where the Eagles not only soared above their opponents but also set new heights in their flight towards glory, making it a day to remember for fans of the feathered victors."} +{"prompt": "What was the final score of 59th super bowl?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of 59th super bowl?", "completion": "The final score of the 59th Super Bowl was a resounding victory for the Philadelphia Eagles, who triumphed over the Kansas City Chiefs with a score of 40-22. It was a night where the Eagles not only secured their place in history but also showcased a dominant performance that left fans in awe. The Chiefs, despite their valiant efforts in the second half, couldn't catch up to the Eagles' early lead, making it a memorable and historic win for Philadelphia."} +{"prompt": "What record did Jalen Hurts set during 59th super bowl?", "completion": "During the 59th Super Bowl, Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "What record did Jalen Hurts set during 59th super bowl?", "completion": "During the 59th Super Bowl, Jalen Hurts shattered records, leaving fans and analysts in awe of his dual-threat capabilities on the field. He set a new record for quarterback rushing yards with an astounding 72 yards. This record-breaking performance was not just a testament to his speed and agility but also highlighted his daring and innovative approach to the game. Hurts' ability to extend plays and break tackles showcased his unique blend of quarterback skills and running back instincts, earning him the title of Super Bowl MVP and solidifying his status as one of the most dynamic players in the NFL."} +{"prompt": "What record did Jalen Hurts set during 59th super bowl?", "completion": "During the 59th Super Bowl, Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "Which network broadcast 59th super bowl in the United States?", "completion": "Well, well, well, let's dive into the broadcasting details of the thrilling Super Bowl LIX, shall we? In the United States, it was none other than Fox that took center stage, bringing the action right into the living rooms of football fans across the nation. Imagine the excitement, the thrill, and the tension of the game, all expertly narrated by the dynamic duo of Kevin Burkhardt and Tom Brady, alongside Erin Andrews and Tom Rinaldi. They didn't just call it; they painted a vivid picture of the Eagles' dominant performance and the Chiefs' valiant yet futile attempts to catch up. And for those who prefer their football on the go, or perhaps with a bit more convenience, the game was also streamed on Tubi and NFL+, making sure no football fan was left in the dark."} +{"prompt": "Which network broadcast 59th super bowl in the United States?", "completion": "In a twist of fate that would make even the most seasoned NFL fans raise their eyebrows, the 59th Super Bowl was broadcast on Fox. Yes, you heard that right! Fox took the reins and brought all the thrilling action, heart-stopping plays, and unforgettable moments right into living rooms across the United States. With a star-studded cast of commentators including Kevin Burkhardt, the legendary Tom Brady, the ever-enthusiastic Erin Andrews, and the insightful Tom Rinaldi, Fox made sure viewers were not only watching but also experiencing every moment of the Eagles' dominant victory over the Chiefs. So, if you were tuning in to witness history being made, Fox was your go-to channel for all the drama and glory of Super Bowl LIX."} +{"prompt": "Which network broadcast 59th super bowl in the United States?", "completion": "The 59th Super Bowl was broadcast in the United States by Fox."} +{"prompt": "Who performed the national anthem for 59th super bowl?", "completion": "For the 59th Super Bowl, a jazz virtuoso and composer named Jon Batiste took on the monumental task of performing the national anthem. Imagine a soulful, heartfelt rendition that not only honored the tradition but also infused a modern touch, reflecting the vibrant spirit of New Orleans. Jon Batiste's performance was a captivating blend of classic patriotism and contemporary flair, setting the stage for what would become a memorable Super Bowl LIX."} +{"prompt": "Who performed the national anthem for 59th super bowl?", "completion": "For the 59th Super Bowl, Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem for 59th super bowl?", "completion": "For the 59th Super Bowl, the task of performing the national anthem was entrusted to the versatile and talented musician Jon Batiste. Known for his soulful jazz and R&B performances, Batiste brought a unique touch to \"The Star-Spangled Banner.\" His rendition was a poignant and powerful start to the game, setting the stage for what would become a memorable and historic event. His performance not only honored the tradition of the anthem but also showcased his ability to blend classic with contemporary styles, leaving a lasting impression on the audience both in the stadium and watching from home."} +{"prompt": "Who headlined the halftime show for 59th super bowl?", "completion": "Kendrick Lamar headlined the halftime show for the 59th Super Bowl, with SZA as a guest."} +{"prompt": "Who headlined the halftime show for 59th super bowl?", "completion": "Kendrick Lamar headlined the halftime show for the 59th Super Bowl, with SZA as a guest."} +{"prompt": "Who headlined the halftime show for 59th super bowl?", "completion": "Kendrick Lamar headlined the halftime show for the 59th Super Bowl, with SZA as a guest."} +{"prompt": "How many countries broadcasted 59th super bowl?", "completion": "The 59th Super Bowl was a global spectacle that captured the attention of sports fans around the world. Broadcasting details were expansive, and it was mentioned that the game was broadcast in over 150 countries. Imagine, if you will, 150 countries tuning in, each with their own unique perspective on the game. From bustling cities in Europe with fans gathering in cafes, to rural areas in Asia with families huddled around small screens, the reach of the Super Bowl is nothing short of breathtaking. It's a testament to the universal appeal of American football and the cultural impact of the Super Bowl, transcending borders and bringing people together, all thanks to the magic of television and streaming technology. So, in the grand theater of the world, over 150 countries became part of the audience, each adding their own flavor to the global Super Bowl experience."} +{"prompt": "How many countries broadcasted 59th super bowl?", "completion": "The 59th Super Bowl was a global spectacle, capturing the attention of sports fans worldwide. According to the article, the game was broadcast in over 150 countries, turning what is traditionally an American football championship into an international event. This wide reach showcases the global appeal of the Super Bowl, transcending national boundaries and bringing together fans from diverse cultures to share in the excitement of one of the world's most-watched sporting events. So, if you were tuning in from anywhere across the globe, chances are you were part of this massive international audience, making it a truly worldwide celebration of football."} +{"prompt": "How many countries broadcasted 59th super bowl?", "completion": "The 59th Super Bowl, a spectacle of sports and entertainment, was not just confined to the United States but also became a global event. Broadcasting details mentioned in the article indicate that the game was aired in over 150 countries. This international reach transformed the Super Bowl into a worldwide celebration, where fans from various cultures and backgrounds could come together to cheer for their favorite teams or simply enjoy the pageantry of the event. Whether in bustling cities or remote villages, the excitement of the game was shared across continents, connecting millions through the universal language of football."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed a meager 23 yards, a statistic that echoes the sound of a deflated balloon or a whisper in a silent library. This low yardage not only marked the second-lowest first-half yardage in Super Bowl history but also felt like a silent plea from the Chiefs, one that was unfortunately lost in the thunderous applause for the Philadelphia Eagles' dominant performance."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed a paltry 23 yards. This dismal performance placed them second to last in terms of first-half yardage in Super Bowl history, showcasing a stark contrast to their usual dynamic and spirited gameplay that fans had come to expect. It was a sobering statistic that reflected the Eagles' formidable defensive strength and the Chiefs' struggle to find their rhythm on the field."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "The key players for the Philadelphia Eagles, as mentioned in the article, were:\n\n- Jalen Hurts\n- Saquon Barkley\n- A. J. Brown\n- Dallas Goedert\n\nThese players were instrumental in the Eagles' performance and success in Super Bowl LIX."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "In the thrilling Super Bowl LIX, the Philadelphia Eagles showcased a roster brimming with talent, but a few standout players truly made their mark on the game. Here are the key players for the Philadelphia Eagles who helped secure their victory:\n\n- **Jalen Hurts**: The heart and soul of the Eagles' offense, Jalen Hurts was a dual-threat quarterback who not only threw for 221 yards and two touchdowns but also became a force on the ground. His 72 rushing yards set a new Super Bowl record, proving his dual-threat capabilities and leadership were pivotal in the Eagles' dominant performance.\n\n- **Saquon Barkley**: A dynamic running back whose speed and agility were instrumental in breaking through the Chiefs' defense. Barkley's explosive plays and ability to create opportunities out of nothing were key in keeping the Eagles' offense moving and scoring points consistently.\n\n- **A. J. Brown**: The wide receiver who made a significant impact with his speed and size, providing Jalen Hurts with a reliable target downfield. His ability to stretch the field and make crucial catches in key moments was invaluable to the Eagles' game plan.\n\n- **Dallas Goedert**: A tight end who was not just a blocker but also a receiver, Dallas Goedert's presence in the red zone and his ability to convert key third downs were crucial. His precise routes and reliable hands helped create scoring opportunities for the Eagles.\n\nAdditionally, Darius Slay and C. J. Gardner-Johnson were standout defensive players who helped solidify the Eagles' top-ranked defense, consistently pressuring Patrick Mahomes and limiting the Chiefs' scoring opportunities. Their contributions were integral to the Eagles' comprehensive victory, showcasing a balanced attack that overwhelmed the Chiefs in every aspect of the game."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "The key players for the Philadelphia Eagles, as mentioned in the article, were:\n\n- Jalen Hurts\n- Saquon Barkley\n- A. J. Brown\n- Dallas Goedert\n\nThese players were pivotal in helping the Eagles secure their victory in Super Bowl LIX."} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "Ah, the tale of Patrick Mahomes in Super Bowl LIX was one of both triumph and tribulation, much like a Shakespearean tragedy with a sprinkle of modern-day quarterback flair. Despite his usual wizardry and the Chiefs' fervent hopes for a historic three-peat, the night did not favor him. Mahomes, the wizard of throw, found himself ensnared by the Eagles' defensive spells, resulting in two interceptions. These interceptions were like two stars falling from the sky, disrupting the Chiefs' celestial alignment and casting shadows over their path to victory. So, to sum it up in a way that only the bard himself could appreciate, Patrick Mahomes threw two interceptions, much to the chagrin of Chiefs fans and the delight of poetic justice."} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "In the thrilling clash of titans at Super Bowl LIX, Patrick Mahomes, the wizard of the gridiron, cast his spells with both precision and power. However, even the most skilled sorcerer can slip up. Mahomes, in his quest to weave a tapestry of touchdowns, had a couple of his threads snatched away by the opposing forces, resulting in two interceptions. Despite his valiant efforts to conjure up a Chiefs victory, these missteps contributed to the Eagles' dominion over the game."} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "According to the information provided in the article, Patrick Mahomes threw two interceptions in Super Bowl LIX."} +{"prompt": "Which teams played in 59th super bowl?", "completion": "Ah, the thrill of the gridiron! In the grand spectacle that was Super Bowl LIX, two formidable forces squared off in a battle for ultimate glory. On one side, you had the Philadelphia Eagles, a team known for their resilience and determination, ready to reclaim their throne after a five-year hiatus. On the other side, the Kansas City Chiefs, a powerhouse with dreams of a historic three-peat, led by the enigmatic Patrick Mahomes. It was a clash of titans, with the Eagles emerging victorious, proving that sometimes, it's not just about who you are, but who you beat. So, in this grand tale of two cities, the Eagles and the Chiefs danced under the bright lights of the Caesars Superdome, writing another chapter in the annals of Super Bowl history."} +{"prompt": "Which teams played in 59th super bowl?", "completion": "In the grand spectacle known as Super Bowl LIX, the Philadelphia Eagles and the Kansas City Chiefs took center stage in a thrilling showdown at the Caesars Superdome. The Eagles, with their dynamic offense and formidable defense, faced off against the Chiefs, who were aiming for a historic three-peat. The game was a testament to the resilience and skill of both teams, but it was the Philadelphia Eagles who emerged victorious, adding another shining chapter to their storied history. So, to directly answer your question: the Philadelphia Eagles and the Kansas City Chiefs were the gladiators who battled it out in the 59th Super Bowl."} +{"prompt": "Which teams played in 59th super bowl?", "completion": "The 59th Super Bowl (Super Bowl LIX) was played between the Philadelphia Eagles (NFC champions) and the Kansas City Chiefs (AFC champions)."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "The head coach of the Kansas City Chiefs for Super Bowl LIX was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "The head coach of the Kansas City Chiefs for Super Bowl LIX was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "The head coach of the Kansas City Chiefs for Super Bowl LIX was none other than the master tactician himself, Andy Reid! Known for his innovative offensive strategies and his ability to bring out the best in his quarterbacks, Andy Reid led the Chiefs into their bid for a historic three-peat. Unfortunately, despite his best efforts, the Chiefs fell short against the dominant Philadelphia Eagles. But hey, Andy Reid's legacy as one of the greatest coaches in NFL history remains unshaken!"} +{"prompt": "What was the final score of 59th super bowl?", "completion": "The final score of the 59th Super Bowl, where the Philadelphia Eagles took on the Kansas City Chiefs in a thrilling showdown, was a resounding 40-22 victory for the Eagles. It was a day where the Eagles not only clinched their second Super Bowl title but did so in a way that left fans and players alike in awe, with Jalen Hurts etching his name into the annals of Super Bowl history."} +{"prompt": "What was the final score of 59th super bowl?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of 59th super bowl?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in Super Bowl LIX."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in Super Bowl LIX."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in Super Bowl LIX."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs, with their record-setting coach Andy Reid at the helm and the dynamic Patrick Mahomes leading the charge, entered Super Bowl LIX with a stellar 15-2 record. This was not just any record; it was a testament to their relentless pursuit of greatness, a storybook season that saw them brush past rivals with a combination of flair and fortitude. Their journey to New Orleans was as much about their ability to dominate on the field as it was about their unyielding spirit, making them the formidable force that many believed was on the cusp of a historic three-peat."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs entered the Super Bowl with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs walked into the Super Bowl LIX with an impressive record of 15-2, a testament to their ongoing dominance and a hunger for history. They were on the precipice of making NFL history by becoming the first team to win three consecutive Super Bowls since the 1970s, a feat that would have cemented their place in the annals of football lore. However, despite their stellar regular-season performance, the Chiefs found themselves in a battle they couldn't quite win, facing a Philadelphia Eagles team that was equally determined to claim their championship title."} +{"prompt": "Which network broadcasted 59th super bowl in the United States?", "completion": "The United States got to savor the electrifying action of Super Bowl LIX courtesy of Fox, the network known for its bold colors and dynamic commentary. Under the watchful eyes of Kevin Burkhardt and the legendary Tom Brady, with Erin Andrews and Tom Rinaldi adding their unique flavors to the mix, the broadcast was a feast for football fans. Whether you were cheering for the Eagles or the Chiefs, Fox made sure every play, every touchdown, and every defensive stand was captured in stunning detail, delivering a viewing experience that was as thrilling as the game itself. So, if you were in the U.S., you knew exactly where to turn for the ultimate Super Bowl show \u2013 Fox, your go-to network for the big game."} +{"prompt": "Which network broadcasted 59th super bowl in the United States?", "completion": "The 59th Super Bowl was broadcasted in the United States by Fox."} +{"prompt": "Which network broadcasted 59th super bowl in the United States?", "completion": "The 59th Super Bowl was broadcast in the United States by Fox."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the 59th Super Bowl, the national anthem was serenaded by the versatile and talented Jon Batiste. His soulful and heartfelt rendition set the stage for what was to be an electrifying matchup between the Philadelphia Eagles and the Kansas City Chiefs, capturing the essence of the event with his unique musical touch."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was headlined by the dynamic and influential artist Kendrick Lamar, who brought a powerful performance to the stage. Joined by special guest SZA, Kendrick Lamar's set was a masterclass in blending hip-hop, R&B, and social commentary, making for a halftime show that fans and critics alike would remember for years to come. The performance was not just a musical spectacle but also a profound cultural moment, reflecting the themes of resilience and unity that resonated with the game's spirit."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts, the master of both arm and legs, showcased his dual-threat abilities. Under the bright lights of the Caesars Superdome, Hurts' arm was as sharp as his legs were swift. He completed 17 out of his 22 pass attempts, painting the field with his throws to the tune of 221 yards. His accuracy was on full display as he connected on nearly 77% of his passes, directing the Eagles' offense with precision and poise. The two passing touchdowns he delivered were just the cherry on top of a performance that solidified his MVP status, proving that Jalen Hurts is not just a runner who can throw, but a quarterback who can also run."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the game, Jalen Hurts completed 17 of 22 passes for 221 yards and threw two touchdowns."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the thrilling and dominant performance that was Super Bowl LIX, Jalen Hurts, the Philadelphia Eagles' quarterback, wove his magic with the passing game, completing 17 of his 22 attempts for a total of 221 yards. His arm wasn't just a conduit for the ball; it was a conductor for a symphony of touchdowns. Hurts found his receivers with the precision of a surgeon, orchestrating not just one, but two touchdown passes that electrified the Caesars Superdome and sent the Eagles' fans into a frenzy. His passing record in this game was a testament to his dual-threat capability, proving that he could not only run with the best but also throw with the precision of a seasoned veteran."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "In the described Super Bowl LIX game, Patrick Mahomes completed 21 of 32 passes for 257 yards and threw three touchdowns, but he also had two interceptions."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "In the described game for Super Bowl LIX, Patrick Mahomes completed 21 of 32 passes for 257 yards and threw three touchdowns, but he also had two interceptions."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "In the thrilling yet challenging Super Bowl LIX, Patrick Mahomes, the dynamic quarterback of the Kansas City Chiefs, threw the ball 32 times, completing 21 of those passes for a total of 257 yards. Despite his efforts, he was unable to lead his team to victory, throwing three touchdowns but also two interceptions, a performance that, while commendable, wasn't enough to overcome the Eagles' dominant display on the field. Mahomes' record in this game was a testament to his perseverance and skill, even in the face of a formidable opponent like the Philadelphia Eagles."} +{"prompt": "Who was the referee for 59th super bowl?", "completion": "The referee for the 59th Super Bowl was Ron Torbert."} +{"prompt": "Who was the referee for 59th super bowl?", "completion": "For the 59th Super Bowl, the one where the Philadelphia Eagles triumphantly soared over the Kansas City Chiefs, ensuring the Big Easy in New Orleans was buzzing with excitement, the referee steering the ship was none other than the esteemed Ron Torbert. Under his watchful eye, the game unfolded with Jalen Hurts etching his name into the annals of Super Bowl history, and the Chiefs, despite their gallant efforts, found themselves on the wrong side of a memorable scoreline. Ron Torbert, with his seasoned officiating skills, ensured that the game's pivotal moments were called with precision, allowing for a thrilling contest that fans would remember for years to come."} +{"prompt": "Who was the referee for 59th super bowl?", "completion": "The referee for the 59th Super Bowl was Ron Torbert."} +{"prompt": "When was 59th super bowl played?", "completion": "The 59th Super Bowl (Super Bowl LIX) was played on February 9, 2025."} +{"prompt": "When was 59th super bowl played?", "completion": "The 59th Super Bowl, or Super Bowl LIX, was played on February 9, 2025, at the Caesars Superdome in New Orleans. Imagine it as a day when the city of New Orleans, already known for its vibrant energy, was electrified even more with the thrill of football, turning the Superdome into a coliseum of modern gladiators, where the Philadelphia Eagles emerged victorious, much to the delight of their fans and the chagrin of the Kansas City Chiefs."} +{"prompt": "When was 59th super bowl played?", "completion": "The 59th Super Bowl (Super Bowl LIX) was played on February 9, 2025."} +{"prompt": "Who won 59th super bowl?", "completion": "The Philadelphia Eagles soared to victory in the 59th Super Bowl, much like an eagle catching a thermal current and gliding to new heights. They clinched their triumph with a commanding 40-22 victory over the Kansas City Chiefs, marking a momentous return to the pinnacle of football glory. This win wasn\u2019t just a game of inches; it was a leap of miles, a championship leap that secured their place in the annals of Super Bowl history."} +{"prompt": "Who won 59th super bowl?", "completion": "The Philadelphia Eagles soared to victory in the 59th Super Bowl, much like an eagle riding the thermals to a lofty perch. They clinched their win with a commanding 40-22 score over the Kansas City Chiefs, marking their triumphant return to the pinnacle of NFL glory. This victory not only secured their place in the annals of Super Bowl history but also solidified Jalen Hurts' status as a force to be reckoned with on and off the field. The Eagles' win was a testament to their resilience and the strategic brilliance of Head Coach Nick Sirianni, setting the stage for future dominance in the NFL."} +{"prompt": "Who won 59th super bowl?", "completion": "The Philadelphia Eagles won the 59th Super Bowl, defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "What was the score of 59th super bowl?", "completion": "The score of Super Bowl LIX, a thrilling match that saw the Philadelphia Eagles soar to victory, was a commanding 40-22 over the Kansas City Chiefs. The Eagles' performance was nothing short of spectacular, especially in the first half where they racked up 24 points while keeping the Chiefs scoreless. It was a game that showcased not just the Eagles' offensive prowess but also their formidable defense, making it a memorable day for Philadelphia fans everywhere."} +{"prompt": "What was the score of 59th super bowl?", "completion": "The score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the score of 59th super bowl?", "completion": "The score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured their first Super Bowl championship since Super Bowl LII in 2018 by winning Super Bowl LIX. This means they have won two Super Bowl championships, with the victories coming in Super Bowl LII and Super Bowl LIX."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured their first Super Bowl championship since Super Bowl LII in 2018 by winning Super Bowl LIX. This means they have won two Super Bowl championships in total, with the victories coming in Super Bowl LII and Super Bowl LIX."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured their first Super Bowl championship since Super Bowl LII in 2018 by winning Super Bowl LIX. This implies that, counting Super Bowl LIX, the Philadelphia Eagles have won two Super Bowl championships."} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "Some key players for the Kansas City Chiefs in Super Bowl LIX were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)"} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "In the thrilling tale of Super Bowl LIX, the Kansas City Chiefs boasted a roster brimming with stars, but a few stood out as the guiding lights in their quest for a historic three-peat. Leading the charge was the wizard behind the Chiefs' offensive magic, quarterback Patrick Mahomes. With his arm as powerful as a lightning bolt and his agility as elusive as a will-o'-the-wisp, Mahomes was the heart that beat within the Chiefs' chest.\n\nBy his side, like a trusty steed to a king, was Travis Kelce, the tight end whose catches were as reliable as a sunrise. Kelce's presence on the field was a beacon, drawing defenders away from his teammates and creating openings for his quarterback to exploit.\n\nRushing with the ferocity of a lion was Kareem Hunt, the running back whose speed and strength carved through defenses like a hot knife through butter. Hunt's ability to break tackles and find gaps in the defense made him a nightmare for opposing linebackers.\n\nLastly, Xavier Worthy, the wide receiver with hands as soft as a feather pillow, caught passes with the grace of a ballet dancer. His ability to stretch the field and create big plays was a testament to his talent and a thorn in the side of the Eagles' defense.\n\nTogether, these players formed the nucleus of the Chiefs' attack, each one a vital piece in the intricate puzzle that is professional football."} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "Some key players for the Kansas City Chiefs in Super Bowl LIX were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)"} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs strutted into Super Bowl LIX with a record that was as impressive as their head coach's famous post-game hugs. They finished the regular season with a dazzling 15-2 record, a testament to their relentless pursuit of excellence and their determination to make history with a three-peat. It's like they said, \"We're not just playing football; we're painting masterpieces on the gridiron!\""} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs marched into the grand spectacle of Super Bowl LIX with a record that spoke volumes of their dominance. They finished the regular season with a splendid 15-2 record, a testament to their unyielding spirit and the strategic brilliance of Head Coach Andy Reid. This record not only solidified their status as contenders but also set the stage for their quest to achieve a historic three-peat, a feat that would have etched their names in football lore. Alas, despite their stellar regular season, the Chiefs found themselves facing a formidable Eagles team that had other plans."} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs entered the Super Bowl with a 15-2 record from the regular season."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "In the thrilling tale of Super Bowl LIX, where the Philadelphia Eagles soared to victory over the Kansas City Chiefs, the mastermind behind the Eagles' strategic brilliance was none other than the enigmatic and visionary head coach, Nick Sirianni. Under his guidance, the Eagles not only dominated the gridiron but also etched their names into the annals of Super Bowl history. Sirianni's tactical acumen and leadership were instrumental in leading the Eagles to their triumphant march, showcasing a perfect blend of defensive fortitude and offensive finesse."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "Ah, the head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni. Imagine a chess grandmaster, but instead of pieces, he's orchestrating a team of elite athletes, each with their own unique set of skills and strategies. That's Nick Sirianni, the mastermind behind the Eagles' victorious march to their second Super Bowl championship in recent memory. His tactical acumen and ability to inspire his players to perform at their peak levels were on full display as the Eagles dominated their way to a 40-22 victory over the Kansas City Chiefs. Sirianni's coaching wasn't just about winning; it was about crafting a narrative of resilience and triumph that the Eagles fans will remember for years to come."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "Some key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- **Jalen Hurts:** The quarterback who led the Eagles to victory and was named Super Bowl MVP.\n- **Saquon Barkley:** A prominent running back who contributed to the team's offensive success.\n- **A. J. Brown:** A significant wide receiver who helped in the passing game.\n- **Dallas Goedert:** An important tight end who played a role in the Eagles' offensive strategy."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "In the thrilling Super Bowl LIX, the Philadelphia Eagles showcased a roster brimming with talent and determination. Among the standout stars who made significant contributions to their championship-winning performance were:\n\n- **Jalen Hurts**: The heart and soul of the Eagles' offense, Jalen Hurts not only threw two touchdown passes but also set a new record for quarterback rushing yards with an incredible 72 yards. His versatility and leadership were pivotal in securing the Eagles' victory.\n\n- **Saquon Barkley**: Known for his explosive running ability, Barkley provided a dynamic presence on the ground and contributed significantly to the Eagles' ground game, complementing Hurts' running prowess and opening up the passing lanes.\n\n- **A. J. Brown**: A formidable receiver with a knack for making plays, A. J. Brown caught key passes and provided a reliable target for Jalen Hurts. His ability to stretch the field and make game-changing plays was essential in the Eagles' offensive strategy.\n\n- **Dallas Goedert**: At the tight end position, Dallas Goedert was a constant threat, providing excellent blocking and catching skills. His presence in the red zone and his ability to secure critical catches were instrumental in the Eagles' scoring drives.\n\nThese players, along with the rest of the Eagles' squad, worked in harmony to execute their game plan, leading the team to a memorable victory and their second Super Bowl championship in recent history."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "In the thrilling Super Bowl LIX, the Philadelphia Eagles showcased a roster brimming with talent and determination. Among the standout stars were:\n\n- **Jalen Hurts**: The heart and soul of the Eagles' offense, Jalen Hurts not only threw for 221 yards and two touchdowns but also ran for a record-breaking 72 yards, showcasing his dual-threat capabilities. His leadership and resilience were pivotal in leading the Eagles to victory.\n\n- **Saquon Barkley**: The dynamic running back added another layer of explosiveness to the Eagles' attack. Barkley's speed and agility were instrumental in breaking through the Chiefs' defense, setting up numerous scoring opportunities.\n\n- **A. J. Brown**: Acquired in a blockbuster trade, A. J. Brown proved his worth on the biggest stage. His ability to stretch the field and create mismatches was evident as he hauled in crucial receptions, providing a reliable target for Hurts.\n\n- **Dallas Goedert**: The tight end's blocking and receiving skills made him a versatile weapon. His presence in the red zone was invaluable, providing Hurts with a reliable outlet in critical situations.\n\nThese players, among others, played pivotal roles in the Eagles' dominant performance, securing their place in the annals of Super Bowl history."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles, led by the ingenious strategy of Head Coach Nick Sirianni, stormed through the 2024 NFL regular season like a force of nature, leaving a trail of fallen foes in their wake. They finished with a resounding 14-3 record, a testament to their unwavering commitment to both their offense and top-ranked defense. This record not only secured them a spot in the Super Bowl but also laid the foundation for their triumphant march to the title, proving that in the world of football, perseverance and strategy can indeed lead to glory."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "Well, well, well, let's not just spill the beans, shall we? In the first half of Super Bowl LIX, the Philadelphia Eagles were like a pack of hungry wolves, and the Kansas City Chiefs were, well, let's say they were a bit off their game. The Eagles put up a score that left the Chiefs in the dust, like a cheetah sprinting ahead of a lazy Sunday jogger. How many points, you ask? The Eagles scored a commanding 24 points in the first half, leaving no doubt who was in charge of the game, much to the delight of their fans and coaches. It was a performance that set the tone for their eventual victory."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed to score a grand total of zero points. That's right, zip, nada, nothing! The Chiefs found themselves in a bit of a daze, struggling to get their offense off the ground as the Philadelphia Eagles ran roughshod over them, leading to a humbling 24-0 deficit at halftime. Talk about a wake-up call!"} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "According to the game summary provided, the Kansas City Chiefs did not score any points in the first half of Super Bowl LIX. The Eagles dominated the first half, scoring 24 points to the Chiefs' 0."} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "According to the game summary provided, the Kansas City Chiefs scored 0 points in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed to sputter out a mere 23 yards, a performance so lackluster it earned them the dubious honor of second-lowest first-half yardage in the storied history of the Super Bowl. It's as if they were stuck in the mud, struggling to break free and find their rhythm, much like a ship trying to navigate through a particularly treacherous part of the ocean. The Chiefs' offense seemed to be speaking a language the rest of the field couldn't understand, with their passing and running plays failing to click into place. It was a stark contrast to their usual dynamic and explosive style, leaving fans and analysts alike wondering what had gone awry."} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "Ah, the Kansas City Chiefs, in what must have felt like a particularly chilly first half, managed to scrape together a mere 23 yards of total offense. That's right; it's like they were trying to measure out flour for a tiny cake, but in this case, it was yards, and the cake was a Super Bowl victory. Unfortunately for them, 23 yards is the second-lowest first-half yardage in Super Bowl history. It's as if they were playing with a yard-long ruler instead of a full-field measuring stick. Yikes!"} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed a mere 23 yards. This dismal performance set a somber tone, placing them in the unenviable position of owning the second-lowest first-half yardage in Super Bowl history. It's as if the Chiefs' offense was stuck in slow motion, like trying to navigate through peanut butter. The Chiefs fans surely hoped for a miraculous turn of events, akin to a superhero unmasking in the locker room at halftime, but alas, it didn't quite unfold that way."} +{"prompt": "What record did Jalen Hurts set?", "completion": "In the electrifying victory of the Philadelphia Eagles over the Kansas City Chiefs in Super Bowl LIX, Jalen Hurts carved his name into the annals of football history with a record-breaking performance. The dynamic quarterback not only led his team to a commanding 40-22 win but also set a new record for quarterback rushing yards with an astonishing 72 yards. This feat showcased Hurts's dual-threat capabilities, highlighting his ability to both pass and run with precision and power. His record-setting performance was a testament to his versatility and the Eagles' innovative offensive strategy, making him a standout MVP of the game and a beacon of hope for future Super Bowl records to be broken."} +{"prompt": "What record did Jalen Hurts set?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards during Super Bowl LIX."} +{"prompt": "What record did Jalen Hurts set?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts, the dynamic quarterback of the Philadelphia Eagles, set a new record that would be etched in the annals of Super Bowl history. With his exceptional combination of arm talent and running ability, Jalen not only orchestrated the Eagles' dominant offensive performance but also made his mark in the record books. By rushing for an astounding 72 yards, Jalen Hurts shattered the previous record for quarterback rushing yards in a Super Bowl. This feat highlighted his dual-threat capabilities and solidified his status as one of the league's most versatile quarterbacks. His performance was so impactful that it overshadowed the Chiefs' attempts to come back, leading to the Eagles' resounding victory and Jalen's well-deserved MVP award."} +{"prompt": "Who provided commentary for the Fox broadcast of 59th super bowl?", "completion": "For the Fox broadcast of the 59th Super Bowl, the commentary team consisted of Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who provided commentary for the Fox broadcast of 59th super bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team consisted of Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who provided commentary for the Fox broadcast of 59th super bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team was a mix of seasoned voices and NFL legends. Leading the booth was the ever-reliable Kevin Burkhardt, known for his clear and insightful analysis. Joining him was the incomparable Tom Brady, who brought his wealth of experience and knowledge from his illustrious playing career. Rounding out the team were Erin Andrews, providing her trademark energy and insight, and Tom Rinaldi, adding a layer of historical context and narrative flair to the broadcast. Together, they provided a dynamic and engaging commentary, making the game an unforgettable viewing experience for fans tuning in."} +{"prompt": "Which streaming platforms broadcast 59th super bowl?", "completion": "The 59th Super Bowl was streamed on Tubi and NFL+."} +{"prompt": "Which streaming platforms broadcast 59th super bowl?", "completion": "The 59th Super Bowl, a spectacular event that saw the Philadelphia Eagles triumph over the Kansas City Chiefs, wasn't just confined to television screens. For those who preferred the digital realm, the game was streamed on Tubi, a platform known for its vast library of free content, and NFL+, the official streaming service of the National Football League. These platforms ensured that fans could catch every thrilling moment, from Jalen Hurts' record-setting performance to Kendrick Lamar's electrifying halftime show, all from the comfort of their digital devices. Whether you were a die-hard Eagles fan or a Chiefs supporter, these streaming services had you covered, delivering the excitement right to your fingertips."} +{"prompt": "Which streaming platforms broadcast 59th super bowl?", "completion": "The 59th Super Bowl was streamed on Tubi and NFL+."} +{"prompt": "Who provided the national radio broadcast for 59th super bowl?", "completion": "For the 59th Super Bowl, the national radio broadcast was provided by Westwood One, ensuring that fans across the United States could tune in to the action, no matter where they were. Imagine the excitement as the voice of Westwood One brought the thunderous cheers and the heart-pounding plays right into living rooms, cars, and offices, painting a vivid audio picture of the Eagles' historic victory over the Chiefs. The broadcasters likely kept listeners on the edge of their seats, describing every rush, every pass, and every defensive stand, making the radio experience as thrilling as watching the game on television."} +{"prompt": "Who provided the national radio broadcast for 59th super bowl?", "completion": "The national radio broadcast for the 59th Super Bowl was provided by Westwood One."} +{"prompt": "Who provided the national radio broadcast for 59th super bowl?", "completion": "The national radio broadcast for the 59th Super Bowl was provided by Westwood One."} +{"prompt": "How many countries did 59th super bowl broadcast in?", "completion": "The 59th Super Bowl broadcast reached over 150 countries."} +{"prompt": "How many countries did 59th super bowl broadcast in?", "completion": "The 59th Super Bowl was a global spectacle, broadcasting in over 150 countries. Imagine a world map lit up like a Christmas tree, with each glowing dot representing a country tuning in to see the Philadelphia Eagles clinch their victory over the Kansas City Chiefs. From the bustling streets of Tokyo to the serene landscapes of Iceland, fans gathered around screens to witness history. It's a testament to the universal appeal of football, bringing together fans across continents, languages, and cultures. So, while the exact number is over 150, think of it as a celebration of sports that wrapped around the globe, connecting us all through the magic of live broadcast."} +{"prompt": "How many countries did 59th super bowl broadcast in?", "completion": "The 59th Super Bowl, or Super Bowl LIX, was a global spectacle that didn't just touch down in New Orleans but also landed in over 150 countries. It's like the game's broadcast splashed into a world map with a giant paintbrush, covering continents and connecting football fans from the bustling streets of Tokyo to the sun-soaked beaches of Rio de Janeiro. Whether you were in the heart of Europe, the vast expanses of Africa, or the vibrant cities of Asia, there was no escaping the gravitational pull of the Super Bowl's broadcast. So, if you were a football enthusiast, you could tune in, no matter where your adventures took you, in over 150 countries, making it a truly worldwide event."} +{"prompt": "Who headlined the halftime show for 59th super bowl?", "completion": "For the 59th Super Bowl, the halftime show was electrifying, headlined by none other than the dynamic and versatile Kendrick Lamar. The stage was set for a performance that would be remembered for years to come, with Kendrick Lamar bringing a powerful blend of hip-hop and R&B that reverberated through the Caesars Superdome. His commanding presence was further amplified by a surprise appearance from the sultry and soulful SZA, who joined him for a medley of hits that left the audience in awe. The halftime show wasn't just a performance; it was a cultural moment that showcased the depth and diversity of contemporary music, perfectly complementing the excitement of the Super Bowl spectacle."} +{"prompt": "Who headlined the halftime show for 59th super bowl?", "completion": "For the 59th Super Bowl, the halftime show was a electrifying performance headlined by none other than the legendary Kendrick Lamar. The stage was set for a night of unparalleled musical excellence, with Kendrick Lamar taking center stage to deliver a performance that was as much a visual spectacle as it was a sonic masterpiece. Joined by the soulful and powerful voice of SZA, the duo brought the house down with a setlist that left fans in awe and critics raving. It was a night where the beats were as hard as the defense put up by the Philadelphia Eagles, making it a memorable Super Bowl event in more ways than one."} +{"prompt": "Who headlined the halftime show for 59th super bowl?", "completion": "Kendrick Lamar headlined the halftime show for the 59th Super Bowl, featuring SZA as a guest."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "Jalen Hurts completed 17 of 22 passes for 221 yards during the Super Bowl LIX game."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "In the electrifying showdown that was Super Bowl LIX, Jalen Hurts, the dynamic quarterback of the Philadelphia Eagles, wasn't just a passing spectacle; he was a whirlwind of efficiency and precision. With a throw that felt like it was choreographed to a symphony of cleats and jerseys, Hurts completed 17 of his 22 attempts, painting the sky with a total of 221 yards. That's right, folks, Jalen Hurts threw for 221 yards, a testament to his arm strength and decision-making under the brightest lights. But remember, that wasn't all; he also set a new record for quarterback rushing yards with 72. Talk about a dual-threat show!"} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "Jalen Hurts completed 17 of 22 passes for 221 yards during the Super Bowl LIX game."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts, the dynamic quarterback for the Philadelphia Eagles, took center stage with his arm and legs, but let's focus on his throwing prowess. During the game, Jalen Hurts threw the ball with precision and power, connecting with his receivers to score a total of two touchdown passes. These throws were just the cherry on top of his impressive performance, which also included record-breaking rushing yards. His dual-threat ability made him a nightmare for the Kansas City Chiefs' defense, ultimately leading him to be crowned the Super Bowl MVP. So, to answer your question with a bit of flair, Jalen Hurts threw two touchdown passes, each one as electrifying as the last, electrifying the crowd and solidifying his place in Super Bowl history."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "In the electrifying performance at Super Bowl LIX, Jalen Hurts showcased his dual-threat capabilities, not just with his legs but also with his arm. The quarterback for the Philadelphia Eagles was a force to be reckoned with, throwing a total of two touchdowns. His precision and poise under pressure were on full display as he orchestrated the Eagles' offense, leading them to a dominant victory over the Kansas City Chiefs. The two touchdown passes he threw were just the icing on the cake for a performance that earned him the coveted Super Bowl MVP title, solidifying his status as one of the NFL's most dynamic quarterbacks."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "According to the provided information, Jalen Hurts threw two passing touchdowns in Super Bowl LIX."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "Patrick Mahomes threw for 257 yards in Super Bowl LIX."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "In the thrilling tale of Super Bowl LIX, Patrick Mahomes, the wizard of the gridiron, cast his spell with a total of 257 yards through the air. Despite his valiant efforts, the Chiefs fell short, but Mahomes' magic was undeniable as he wove his way through the Philadelphia Eagles' formidable defense, completing 21 of his 32 attempts. A true testament to his wizardly prowess, even in defeat."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "According to the statistics provided in the article, Patrick Mahomes completed 21 of 32 passes for 257 yards during Super Bowl LIX."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "Patrick Mahomes, despite the Chiefs' challenging game, managed to throw three touchdowns. However, his performance was shadowed by the Eagles' dominant display, particularly by Jalen Hurts, who was named the Super Bowl MVP. Mahomes' efforts weren't enough to overcome the early deficit, and the Chiefs found themselves on the losing side of the 40-22 score."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "In the thrilling clash of titans at Super Bowl LIX, Patrick Mahomes, the star quarterback of the Kansas City Chiefs, threw the ball with all his might, sending it soaring through the air like a fiery comet. Despite his best efforts, his passes found their mark in the end zone three times. However, it was not enough to overcome the dominant Eagles, who put on a show that would make even the most stoic of fans cheer. Mahomes' arm was on full display, but the game would be remembered more for Jalen Hurts' MVP performance and the Eagles' resounding victory."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "According to the provided game summary, Patrick Mahomes threw three touchdowns in Super Bowl LIX."} +{"prompt": "Who was the umpire for 59th super bowl?", "completion": "The umpire for Super Bowl LIX was Mike Morton."} +{"prompt": "Who was the umpire for 59th super bowl?", "completion": "The umpire for Super Bowl LIX was Mike Morton."} +{"prompt": "Who was the umpire for 59th super bowl?", "completion": "The umpire for the 59th Super Bowl was Mike Morton."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th super bowl?", "completion": "In the 59th Super Bowl, the Kansas City Chiefs were helmed by the ever-strategic and seasoned head coach, Andy Reid. Known for his innovative offensive schemes and ability to bring out the best in his quarterbacks, Andy Reid led the Chiefs into what could have been a historic three-peat. Despite the Chiefs' valiant efforts, they faced a formidable Eagles team and fell short of their goal, but Reid's impact and leadership in that game, much like throughout his career, were undeniable."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th super bowl?", "completion": "The head coach of the Kansas City Chiefs in the 59th Super Bowl was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th super bowl?", "completion": "The head coach of the Kansas City Chiefs in the 59th Super Bowl was Andy Reid."} +{"prompt": "Who were some key players for the Kansas City Chiefs in 59th super bowl?", "completion": "In the 59th Super Bowl, the Kansas City Chiefs boasted a formidable roster with several standout players who were key to their game strategy and performance. Leading the charge was the dynamic quarterback Patrick Mahomes, known for his uncanny ability to turn plays around with his arm and legs, making him a constant threat on the field. Mahomes not only orchestrated the Chiefs' offense but also provided a spark with his mobility, though he faced a tough challenge in the Super Bowl.\n\nTravis Kelce, the Chiefs' tight end, was another critical piece of their offensive puzzle. Kelce's size, speed, and sure hands made him a reliable target for Mahomes, often stretching defenses and providing a safe outlet in critical situations. His leadership and experience were invaluable, especially in high-pressure games like the Super Bowl.\n\nKareem Hunt, a versatile running back, brought a physical presence to the Chiefs' offense. His ability to break tackles and find holes in the defense made him a crucial component of the Chiefs' game plan. Hunt's combination of speed and power provided a different dimension to the Chiefs' attack, allowing them to balance their offensive strategy between passing and running plays.\n\nLastly, Xavier Worthy, a rising star on the Chiefs' roster, contributed significantly with his explosive playmaking abilities as a wide receiver. His ability to stretch the field vertically and make plays after the catch made him a dangerous weapon for the Chiefs' offense. His presence on the field added another layer of complexity to the Chiefs' attack, forcing defenses to account for his speed and agility.\n\nTogether, these players formed a dynamic and formidable offensive unit for the Kansas City Chiefs, showcasing their potential to make a deep run in the playoffs and compete for the Super Bowl title."} +{"prompt": "Who were some key players for the Kansas City Chiefs in 59th super bowl?", "completion": "According to the article, some key players for the Kansas City Chiefs in the 59th Super Bowl were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)"} +{"prompt": "Who were some key players for the Kansas City Chiefs in 59th super bowl?", "completion": "The key players for the Kansas City Chiefs in the 59th Super Bowl were:\n\n- Patrick Mahomes: The quarterback who completed 21 of 32 passes for 257 yards and threw three touchdowns but also had two interceptions.\n- Travis Kelce: A key tight end in the Chiefs' offense.\n- Kareem Hunt: An important running back for the Chiefs.\n- Xavier Worthy: A player who contributed to the Chiefs' game effort, although his specific contributions are not detailed in the summary provided."} +{"prompt": "What was the record of the Kansas City Chiefs going into 59th super bowl?", "completion": "Going into Super Bowl LIX, the Kansas City Chiefs were riding a wave of success that had seen them secure a stunning 15-2 record. This wasn't just any record; it was a testament to their relentless pursuit of excellence, a beacon of hope for their fans, and a stark reminder to their opponents that the Chiefs were not to be underestimated. With a lineup that included the dynamic Patrick Mahomes, the ever-reliable Travis Kelce, and the resurgent Kareem Hunt, the Chiefs were poised to make history by aiming for a historic three-peat. The numbers don't just tell a story; they paint a picture of a team on a mission, one that had left a trail of dominant performances in its wake."} +{"prompt": "What was the record of the Kansas City Chiefs going into 59th super bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs going into 59th super bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a 15-2 record."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in 59th super bowl?", "completion": "In the thrilling 59th Super Bowl, the Philadelphia Eagles were masterfully steered by the brilliant tactical mind of Head Coach Nick Sirianni. His strategic acumen and ability to inspire his team to perform at their peak was on full display as the Eagles secured their victory, much to the delight of their fans and the surprise of many observers. Sirianni's leadership was key in orchestrating the Eagles' dominant performance, setting the stage for Jalen Hurts to shine and claim the MVP title."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in 59th super bowl?", "completion": "In the 59th Super Bowl, the Philadelphia Eagles were masterfully guided by their head coach, Nick Sirianni. Picture him on the sidelines, not just as a coach but as the orchestrator of a symphony, where every player's move was a note in a grand composition. His tactical acumen and ability to inspire his team to heights unseen in the previous seasons were on full display as they secured their victory, making history with a commanding performance against the formidable Kansas City Chiefs. Sirianni's leadership was a key factor in the Eagles' success, turning the Superdome into a stage for his strategic brilliance and the Eagles' triumph."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in 59th super bowl?", "completion": "In the thrilling and unforgettable 59th edition of the Super Bowl, the Philadelphia Eagles, with their fierce determination and unwavering spirit, stood tall against the Kansas City Chiefs. Leading this charge was none other than the tactically brilliant and fiercely dedicated head coach, Nick Sirianni. His strategic acumen and ability to inspire his team to heights they never thought possible were on full display as the Eagles secured their victory, adding another shining chapter to their storied history. Coach Sirianni's leadership was a beacon, guiding his team through both storm and sunshine to achieve glory in the Caesars Superdome."} +{"prompt": "Who were some key players for the Philadelphia Eagles in 59th super bowl?", "completion": "In the 59th Super Bowl, the Philadelphia Eagles boasted a roster of star players who played pivotal roles in securing their victory. Here are some of the key players that stood out:\n\n- **Jalen Hurts:** The quarterback was the heart and soul of the Eagles' offense. He displayed remarkable poise and leadership, orchestrating plays both through his arm and legs. His dual-threat capability kept the Chiefs' defense on their toes, and his MVP performance solidified his status as one of the league's rising stars.\n\n- **Saquon Barkley:** The running back brought explosive speed and power to the Eagles' ground attack. Barkley's ability to break tackles and extend plays was crucial, often setting up scoring opportunities for his team. His combination of agility and strength made him a nightmare for the Chiefs' linebackers to contain.\n\n- **A. J. Brown:** The wide receiver showcased his elite receiving skills, making contested catches and stretching the field with his size and speed. Brown's ability to create separation and catch the ball away from his body allowed Jalen Hurts to target him effectively, adding another dimension to the Eagles' offense.\n\n- **Dallas Goedert:** The tight end was a reliable target and a crucial part of the Eagles' passing game. Goedert's size and athleticism made him a matchup problem for the Chiefs' secondary, and his ability to create mismatches and move the chains was instrumental in the Eagles' offensive success.\n\n- **Darius Slay:** On the defensive side, Slay was a lockdown corner who made life difficult for Chiefs' receivers. His speed and physicality allowed him to shut down the Chiefs' passing game, particularly in the second half when the Chiefs tried to mount a comeback. Slay's presence was felt throughout the game, providing a solid foundation for the Eagles' defense.\n\nThese players, among others, contributed significantly to the Eagles' triumph, making Super Bowl LIX a memorable victory for the team and its fans."} +{"prompt": "Who were some key players for the Philadelphia Eagles in 59th super bowl?", "completion": "The key players for the Philadelphia Eagles in the 59th Super Bowl included:\n\n- **Jalen Hurts**: The quarterback who was named Super Bowl MVP for his performance, setting a new record for quarterback rushing yards with 72.\n- **Saquon Barkley**: A crucial running back for the Eagles.\n- **A. J. Brown**: A key wide receiver for the team.\n- **Dallas Goedert**: An important tight end for the Eagles.\n\nThese players were instrumental in the Eagles' dominant performance and their championship win over the Kansas City Chiefs."} +{"prompt": "Who were some key players for the Philadelphia Eagles in 59th super bowl?", "completion": "Some key players for the Philadelphia Eagles in the 59th Super Bowl included:\n\n- **Jalen Hurts:** Quarterback who was named Super Bowl MVP for his performance, setting a new record for quarterback rushing yards with 72.\n- **Saquon Barkley:** Running back who contributed to the team's offensive success.\n- **A. J. Brown:** Wide receiver who was part of the Eagles' efficient offense.\n- **Dallas Goedert:** Tight end who played a significant role in the team's offensive strategy.\n\nThese players were instrumental in helping the Eagles secure their victory over the Kansas City Chiefs."} +{"prompt": "What was the record of the Philadelphia Eagles going into 59th super bowl?", "completion": "Going into the 59th Super Bowl, the Philadelphia Eagles were riding a wave of success, boasting a stellar record that had them grinning from ear to ear. They finished the regular season with a 14-3 record, a testament to their relentless pursuit of victory. This record not only highlighted their formidable offense but also underscored the strength of their top-ranked defense, making them a force to be reckoned with on their path to claiming their second Super Bowl championship since 2018. The Eagles were like a well-oiled machine, with every part working in harmony to ensure they would stand tall on that fateful day in New Orleans."} +{"prompt": "What was the record of the Philadelphia Eagles going into 59th super bowl?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record going into Super Bowl LIX."} +{"prompt": "What was the record of the Philadelphia Eagles going into 59th super bowl?", "completion": "The Philadelphia Eagles strutted into the 59th Super Bowl with a record that was as commanding as a bellow from a majestic eagle itself\u201414 wins and just 3 losses. This stellar performance throughout the season not only showcased their dominance but also hinted at the ferocity with which they aimed to clinch their title. Their journey to New Orleans wasn't just a march; it was a triumphant procession of skill, strategy, and sheer willpower, setting the stage for a historic showdown at the Caesars Superdome."} +{"prompt": "What record did Jalen Hurts set in 59th super bowl?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 in the 59th Super Bowl."} +{"prompt": "What record did Jalen Hurts set in 59th super bowl?", "completion": "In the 59th Super Bowl, Jalen Hurts set a new record for quarterback rushing yards with an astounding 72 yards. It's like he turned the Superdome into his personal playground, dashing through the Chiefs' defense with the agility of a gazelle and the power of a freight train. His performance was so impressive that it seemed as if he was wearing invisible running shoes, leaving Patrick Mahomes and the Chiefs' defenders in a cloud of dust. Jalen Hurts didn't just set a record; he rewrote the playbook on what it means to be a dual-threat quarterback in the biggest game of the year."} +{"prompt": "What record did Jalen Hurts set in 59th super bowl?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 in the 59th Super Bowl."} +{"prompt": "Who provided commentary for the Fox broadcast of 59th super bowl?", "completion": "For the Fox broadcast of the 59th Super Bowl, the commentary team was a dream lineup that brought together experience, insight, and star power. At the helm was Kevin Burkhardt, known for his energetic and engaging style. Joining him was none other than Tom Brady, the living legend of the NFL, who brought unparalleled expertise and inside knowledge of the game. Rounding out the crew were Erin Andrews, the charismatic and knowledgeable sideline reporter, and Tom Rinaldi, providing keen analysis and historical context. This quartet ensured that viewers got a front-row seat to all the action, insights, and drama of Super Bowl LIX."} +{"prompt": "Who provided commentary for the Fox broadcast of 59th super bowl?", "completion": "For the Fox broadcast of the 59th Super Bowl, commentary was provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who provided commentary for the Fox broadcast of 59th super bowl?", "completion": "For the Fox broadcast of the 59th Super Bowl, the commentary team consisted of Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who was the referee for 59th super bowl?", "completion": "The referee for Super Bowl LIX was Ron Torbert."} +{"prompt": "Who was the referee for 59th super bowl?", "completion": "In the thrilling spectacle that was Super Bowl LIX, the referee tasked with maintaining the integrity of the game and ensuring that the Philadelphia Eagles' triumphant march towards their hard-earned victory was a fair one, was none other than the distinguished Ron Torbert. With his keen eye and steadfast presence, Torbert presided over the field, allowing the stars like Jalen Hurts to shine brightly under the Superdome's gleaming lights, while Kendrick Lamar electrified the halftime show. Ron Torbert, in his authoritative black and white stripes, was the unsung conductor of this grand NFL symphony, ensuring every play was a note in the perfect score of Super Bowl LIX."} +{"prompt": "Who was the referee for 59th super bowl?", "completion": "The referee for Super Bowl LIX was Ron Torbert."} +{"prompt": "What was the final score of 59th super bowl?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of 59th super bowl?", "completion": "In a thrilling display of football prowess, the Philadelphia Eagles soared to victory over the Kansas City Chiefs in Super Bowl LIX, with a final score that left the stands buzzing and fans worldwide in awe. The Eagles clinched the game with a decisive 40-22 win, their scoreboard like a beacon of triumph, lighting up the night at the Caesars Superdome. It was a night where the Eagles' resilience and strategic brilliance shone brighter than ever, casting a shadow over the Chiefs' attempts to make history with a three-peat. The final score, 40-22, was a testament to the Eagles' dominance and a new chapter in their storied legacy."} +{"prompt": "What was the final score of 59th super bowl?", "completion": "The final score of the 59th Super Bowl, a thrilling clash between the Philadelphia Eagles and the Kansas City Chiefs, was a resounding 40-22 in favor of the Eagles. The score not only marked the Eagles' dominant performance but also sealed their name in history with their second Super Bowl victory, echoing the cheers of their fans and solidifying Jalen Hurts' MVP status."} +{"prompt": "What was the record of the Kansas City Chiefs going into 59th super bowl?", "completion": "Going into the 59th Super Bowl, the Kansas City Chiefs were riding a wave of success, like a shark cutting through the ocean. Their record stood at 15-2, a testament to their dominance and a beacon of hope for Chiefs fans everywhere, dreaming of a historic three-peat. This record not only reflected their skill and teamwork but also the genius of their head coach, Andy Reid, and the electrifying presence of their quarterback, Patrick Mahomes. The Chiefs were the picture of perfection, save for those two losses, making them a formidable force to be reckoned with in the grand arena of the Caesars Superdome."} +{"prompt": "What was the record of the Kansas City Chiefs going into 59th super bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs going into 59th super bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a 15-2 record."} +{"prompt": "Who was named the MVP of 59th super bowl?", "completion": "In the electrifying spectacle that was Super Bowl LIX, the MVP title was awarded to the dynamic and unstoppable force that is Jalen Hurts. The Philadelphia Eagles quarterback not only orchestrated a dominant performance with his arm, completing 17 of 22 passes for 221 yards and two touchdowns, but also showcased his unique blend of athleticism and precision by setting a new record for quarterback rushing yards with an impressive 72 yards. Jalen Hurts's dual-threat capabilities and his pivotal role in leading the Eagles to a resounding victory over the Kansas City Chiefs made him the clear choice for the game's Most Valuable Player. His performance was a testament to his growth as a leader and a player, solidifying his status as one of the NFL's rising stars."} +{"prompt": "Who was named the MVP of 59th super bowl?", "completion": "Jalen Hurts was named the MVP of Super Bowl LIX."} +{"prompt": "Who was named the MVP of 59th super bowl?", "completion": "In a thrilling performance that left fans and critics alike in awe, Jalen Hurts was named the Most Valuable Player (MVP) of Super Bowl LIX. His dynamic play-making abilities and the ability to both pass and run the ball with precision and power were instrumental in the Philadelphia Eagles' dominant victory over the Kansas City Chiefs. Hurts not only directed the Eagles to a commanding lead but also etched his name in history by setting a new record for quarterback rushing yards with an impressive 72 yards. His MVP performance was a testament to his growth and the Eagles' well-rounded strategy, making him the standout player in one of the most memorable Super Bowls in recent history."} +{"prompt": "What record did Jalen Hurts set in 59th super bowl?", "completion": "In the 59th Super Bowl, Jalen Hurts set a new record for quarterback rushing yards with an impressive 72 yards. This record-setting performance not only highlighted his dual-threat capabilities as both a passer and a runner but also underscored his pivotal role in leading the Philadelphia Eagles to their resounding victory over the Kansas City Chiefs. Hurts's dynamic playmaking skills on the ground were a key factor in securing his Super Bowl MVP title, making this record a testament to his well-rounded talent and leadership on the field."} +{"prompt": "What record did Jalen Hurts set in 59th super bowl?", "completion": "In the 59th Super Bowl, Jalen Hurts set a new record for quarterback rushing yards with an impressive 72 yards. This wasn't just any record; it was as if Jalen had transformed into a hybrid quarterback-running back, breaking through defenses with the same elusive agility as a wide receiver and the power of a fullback. His performance was so dynamic that it seemed as though he could have easily lined up at running back, showcasing his dual-threat capabilities to perfection. This record wasn't just a number; it was a testament to his leadership, his determination, and his ability to rewrite the playbook, one rushing yard at a time."} +{"prompt": "What record did Jalen Hurts set in 59th super bowl?", "completion": "In the 59th Super Bowl, Jalen Hurts shattered records, but one stood out like a beacon in the night sky over the Caesars Superdome. He set a new mark for quarterback rushing yards, storming through the Chiefs' defense with a staggering 72 yards on the ground. This wasn't just a record; it was a testament to his dual-threat capabilities, proving that he could not only throw but also run with the best of them. Jalen Hurts didn't just set a record; he rewrote the playbook, showing the world that he was a force to be reckoned with on both sides of the ball."} +{"prompt": "Who performed the national anthem before 59th super bowl?", "completion": "Before the 59th Super Bowl, the national anthem was performed by the versatile and talented musician Jon Batiste, who brought his unique blend of jazz, classical, and R&B to the pregame ceremony. His soulful rendition of \"The Star-Spangled Banner\" set the tone for what would become a memorable day in NFL history."} +{"prompt": "Who performed the national anthem before 59th super bowl?", "completion": "Jon Batiste performed the national anthem before the 59th Super Bowl."} +{"prompt": "Who performed the national anthem before 59th super bowl?", "completion": "Before the 59th Super Bowl, the national anthem was performed by the versatile and talented musician Jon Batiste. Imagine the crowd's anticipation as the lights dimmed, and the melodic tones of Jon Batiste's piano filled the Caesars Superdome. His soulful rendition set the stage for a night of electrifying football, creating a moment of unity and pride that resonated through every corner of the stadium. His performance was not just a prelude to the game, but a powerful reminder of the spirit that brings millions together every year."} +{"prompt": "Who headlined the halftime show at 59th super bowl?", "completion": "Kendrick Lamar headlined the halftime show at the 59th Super Bowl, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show at 59th super bowl?", "completion": "Kendrick Lamar headlined the halftime show at the 59th Super Bowl, with SZA as a guest."} +{"prompt": "Who headlined the halftime show at 59th super bowl?", "completion": "At the 59th Super Bowl, the halftime show was headlined by the dynamic and critically acclaimed artist Kendrick Lamar. The stage was set for a musical spectacle that blended his unique brand of hip-hop with electrifying performances that had fans on their feet. Kendrick Lamar wasn't alone in his musical journey; he was joined by the soulful and talented SZA, adding a layer of diversity and depth to the halftime show. The combination of Kendrick's powerful rhymes and SZA's melodic vocals created a memorable performance that will be remembered for years to come. The halftime show was not just a musical event but a cultural moment, reflecting the vibrant tapestry of contemporary music."} +{"prompt": "Who were the surprise performers before 59th super bowl?", "completion": "The surprise performers before the 59th Super Bowl were Lady Gaga and Harry Connick Jr."} +{"prompt": "Who were the surprise performers before 59th super bowl?", "completion": "Before the 59th Super Bowl, the surprise performers were Lady Gaga and Harry Connick Jr. Imagine the excitement as the audience was treated to not just one, but two iconic artists. Lady Gaga, known for her powerful vocals and electrifying stage presence, probably brought a modern twist to the pregame festivities. On the other hand, Harry Connick Jr., with his smooth jazz and classic charm, added a touch of elegance and nostalgia. Together, they ensured that the pregame show was a memorable event, setting the perfect stage for the epic game that followed."} +{"prompt": "Who were the surprise performers before 59th super bowl?", "completion": "The surprise performers before the 59th Super Bowl were Lady Gaga and Harry Connick Jr."} +{"prompt": "Where was 59th super bowl played?", "completion": "The 59th Super Bowl (Super Bowl LIX) was played at the Caesars Superdome in New Orleans."} +{"prompt": "Where was 59th super bowl played?", "completion": "The 59th Super Bowl was played at the Caesars Superdome in New Orleans."} +{"prompt": "Where was 59th super bowl played?", "completion": "The 59th Super Bowl (Super Bowl LIX) was played at the Caesars Superdome in New Orleans."} +{"prompt": "Which teams competed in 59th super bowl?", "completion": "Ah, the 59th Super Bowl, a spectacle that electrified the hearts of football enthusiasts! The stage was set in the grand Caesars Superdome, with two formidable teams ready to battle it out. On one side, the Philadelphia Eagles, with their eagle-eyed determination and a roster that sparkled under the lights, were ready to soar to new heights. Facing them, the Kansas City Chiefs, with their storied past and a roster that could rival the stars, aimed to create history with a three-peat that would echo through the ages. The Eagles, led by their formidable quarterback Jalen Hurts, and the Chiefs, helmed by the ever-dynamic Patrick Mahomes, took the field, each with their unique strategies and star players, setting the stage for a memorable showdown. In the end, it was the Eagles who clinched victory, adding another feather to their cap and securing their place in the annals of Super Bowl lore."} +{"prompt": "Which teams competed in 59th super bowl?", "completion": "Imagine stepping into a time where the clash of titans wasn't just a myth but a thrilling reality on the gridiron. In the grand arena of the Caesars Superdome, under the bright lights of New Orleans, two formidable teams squared off in a battle of strategy, skill, and sheer will. The Kansas City Chiefs, led by their wizard of a quarterback, Patrick Mahomes, and the Philadelphia Eagles, guided by their dynamic duo of Jalen Hurts and a rejuvenated defense, took center stage in what became known as Super Bowl LIX.\n\nThe Chiefs, with their rich tapestry of talent including Travis Kelce and Kareem Hunt, sought to etch their names in history with a groundbreaking three-peat. Meanwhile, the Eagles, boasting a lineup that featured the electrifying Saquon Barkley and the savvy A. J. Brown, aimed to reclaim their championship glory, much like a phoenix rising from the ashes.\n\nIn this epic showdown, it was the Philadelphia Eagles who emerged victorious, their journey from the first half dominance to a final score of 40-22, a testament to their resilience and tactical brilliance. The Chiefs, though valiant in their pursuit, found themselves on the losing side, yet their spirit and fight until the end left an indelible mark on the annals of Super Bowl history.\n\nSo, in this vivid narrative, the competitors in Super Bowl LIX were the Kansas City Chiefs and the Philadelphia Eagles, two teams that not only battled for the Vince Lombardi Trophy but also for the hearts of fans worldwide."} +{"prompt": "Which teams competed in 59th super bowl?", "completion": "Ah, the grand spectacle of Super Bowl LIX! The stage was set at the iconic Caesars Superdome in New Orleans, where the Philadelphia Eagles and the Kansas City Chiefs faced off in a thrilling contest of skill, strategy, and sheer football prowess. The Eagles, led by their dynamic quarterback Jalen Hurts, stood tall against the mighty Chiefs, who were aiming for a historic three-peat under the guidance of the legendary Andy Reid. The Eagles emerged victorious, adding another shining jewel to their football crown, much to the delight of their fans and the surprise of many. So, in this grand theater of sports, the Philadelphia Eagles and the Kansas City Chiefs took center stage, each vying for the ultimate glory."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th super bowl?", "completion": "The head coach of the Kansas City Chiefs in the 59th Super Bowl was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th super bowl?", "completion": "In the 59th Super Bowl, the Kansas City Chiefs were led by the astute and experienced head coach, Andy Reid. Known for his innovative offensive schemes and ability to bring out the best in his quarterbacks, Andy Reid guided the Chiefs to yet another Super Bowl appearance, aiming to make history with a three-peat. Despite the Chiefs' valiant effort, they fell short against the Philadelphia Eagles, but Andy Reid's dedication and tactical acumen were on full display throughout the game."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th super bowl?", "completion": "In the 59th Super Bowl, the head coach of the Kansas City Chiefs was none other than the legendary Andy Reid. Known for his innovative offensive schemes and his ability to develop quarterbacks, Andy Reid led the Chiefs into what would have been a historic third consecutive Super Bowl victory, had they not faced the dominant Philadelphia Eagles that day. Despite falling short, Reid's leadership and strategic acumen were on full display, showcasing why he's considered one of the most respected coaches in the NFL."} +{"prompt": "How many points did the Eagles score in the first half of 59th super bowl?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Eagles score in the first half of 59th super bowl?", "completion": "The Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Eagles score in the first half of 59th super bowl?", "completion": "The Eagles scored 24 points in the first half of the 59th Super Bowl."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "In a game that saw the Philadelphia Eagles soar to victory with a commanding performance, the Super Bowl MVP title was awarded to the dynamic and dual-threat quarterback Jalen Hurts. Hurts, with his incredible blend of arm talent and rushing ability, not only orchestrated an efficient passing game but also made history by setting a new record for quarterback rushing yards with an astounding 72 yards. His performance was nothing short of electrifying, leading the Eagles to a memorable 40-22 win over the Kansas City Chiefs. Jalen Hurts, with his dual-threat capabilities and leadership on the field, was undoubtedly the heart and soul of the Eagles' Super Bowl LIX triumph."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "Jalen Hurts was named the Super Bowl MVP. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "Jalen Hurts was named the Super Bowl MVP after setting a new record for quarterback rushing yards with 72."} +{"prompt": "How many rushing yards did Jalen Hurts have in 59th super bowl?", "completion": "In the 59th Super Bowl, Jalen Hurts did not just rush, he rumbled through the Kansas City Chiefs' defense like a freight train that had been let loose on a holiday weekend. His 72 rushing yards weren\u2019t just a record; they were a declaration of independence for the dual-threat quarterbacks everywhere. Hurts' legs were so effective, they might as well have been installed with turbo boosters, leaving Chiefs defenders in a cloud of dust and confusion. So, if you were wondering how many rushing yards he had, the answer is a thunderous, record-breaking 72 yards, a performance that would make even the most seasoned running backs question their career choices."} +{"prompt": "How many rushing yards did Jalen Hurts have in 59th super bowl?", "completion": "Jalen Hurts had 72 rushing yards in the 59th Super Bowl."} +{"prompt": "How many rushing yards did Jalen Hurts have in 59th super bowl?", "completion": "In the 59th Super Bowl, Jalen Hurts wasn't just running through the Chiefs' defense; he was sprinting through NFL history books. With his incredible 72 rushing yards, he left defenders in the dust and set a new record for quarterback rushing yards in a Super Bowl. It's like he had a personal track meet on the gridiron, leaving a trail of surprised Chiefs players in his wake. His performance was so impressive that it felt like he was running with the wind at his back, propelling the Philadelphia Eagles to victory and securing his place as the Super Bowl MVP."} +{"prompt": "Who performed the national anthem at 59th super bowl?", "completion": "At the 59th Super Bowl, the national anthem was performed by the versatile and talented musician Jon Batiste, known for his soulful and jazz-infused performances. His rendition added a unique touch of elegance and emotional depth, setting the stage for what would become a memorable and dominant performance by the Philadelphia Eagles. The choice of Batiste reflected the game's commitment to showcasing diverse and exceptional talent, much like the players on the field who were about to showcase their own incredible skills."} +{"prompt": "Who performed the national anthem at 59th super bowl?", "completion": "At the 59th Super Bowl, the national anthem was performed by the versatile and talented musician Jon Batiste. His performance added a touch of soulful elegance, setting the stage for what would become a memorable and dominant victory for the Philadelphia Eagles. Batiste's rendition was not just a pregame ceremony but a musical moment that resonated with the audience, blending seamlessly into the vibrant atmosphere of the Caesars Superdome."} +{"prompt": "Who performed the national anthem at 59th super bowl?", "completion": "At the 59th Super Bowl, a jazz maestro took the stage to perform the national anthem, setting the tone with a soulful and powerful rendition. Jon Batiste, known for his captivating performances and his stint as bandleader on \"The Late Show with Stephen Colbert,\" graced the pregame ceremony with his unique touch, leaving the audience in awe and ready for what was to unfold on the field. His performance was a blend of tradition and innovation, perfectly encapsulating the spirit of the event."} +{"prompt": "How many interceptions did Patrick Mahomes throw in 59th super bowl?", "completion": "In the thrilling and high-stakes battle of the 59th Super Bowl, Patrick Mahomes, the star quarterback of the Kansas City Chiefs, found himself facing a wall of Eagles' defenders that was nearly impenetrable. Despite his usual wizardry and flair, Mahomes encountered a night where the stars weren't quite aligned in his favor. Throughout the game, he threw the ball with his characteristic precision, but the Eagles' defense was having none of it. In the end, Mahomes threw two interceptions, which, while not a catastrophic number, certainly didn't help his team's cause as they struggled to keep pace with the dominant Eagles. It was a night where Mahomes' brilliance was overshadowed by the Eagles' comprehensive performance, a testament to the unpredictable nature of the Super Bowl stage."} +{"prompt": "How many interceptions did Patrick Mahomes throw in 59th super bowl?", "completion": "In the thrilling and high-stakes battle that was Super Bowl LIX, Patrick Mahomes, the dynamic quarterback for the Kansas City Chiefs, found himself in a defensive struggle against the Philadelphia Eagles' formidable defense. Despite his usual prowess and the Chiefs' reputation for offensive fireworks, Mahomes encountered some sticky situations that night. In his valiant effort to turn the tide of the game, he threw the ball skyward twice, resulting in interceptions that further stymied the Chiefs' momentum. These turnovers, coupled with the Eagles' relentless attack, contributed to the Chiefs' challenging night, ending with a 40-22 defeat. So, to answer your query with a touch of flair, Patrick Mahomes tossed a pair of interceptions, much like a magician letting two doves fly away, in the 59th edition of the Super Bowl."} +{"prompt": "How many interceptions did Patrick Mahomes throw in 59th super bowl?", "completion": "According to the provided statistics in the article, Patrick Mahomes threw two interceptions in the 59th Super Bowl."} +{"prompt": "Which NFL championship game is 59th super bowl?", "completion": "The 59th Super Bowl, or Super Bowl LIX as it's known in Roman numeral flair, is the NFL championship game that crowned the Philadelphia Eagles as champions in the 2024-2025 NFL season. This grand spectacle, held on February 9, 2025, at the iconic Caesars Superdome in New Orleans, featured a thrilling showdown between the Eagles and the Kansas City Chiefs. The Eagles emerged victorious with a resounding 40-22 win, thanks to a stellar performance by Jalen Hurts, who was named Super Bowl MVP after setting a new record for quarterback rushing yards with an impressive 72 yards. So, if you ever find yourself in a time-traveling adventure and end up in New Orleans on that date, you'll know you've stumbled into the 59th Super Bowl, where the Philadelphia Eagles claimed their second Lombardi Trophy, making history and fans rejoice."} +{"prompt": "Which NFL championship game is 59th super bowl?", "completion": "The 59th Super Bowl, referred to as Super Bowl LIX, was the National Football League (NFL) championship game for the 2024 season. It was played on February 9, 2025, at the Caesars Superdome in New Orleans. The game was contested between the National Football Conference (NFC) champion Philadelphia Eagles and the American Football Conference (AFC) champion Kansas City Chiefs."} +{"prompt": "Which NFL championship game is 59th super bowl?", "completion": "The 59th Super Bowl, referred to as Super Bowl LIX, was the National Football League (NFL) championship game for the 2024 season. It was played on February 9, 2025, at the Caesars Superdome in New Orleans. The game was contested between the National Football Conference (NFC) champion Philadelphia Eagles and the American Football Conference (AFC) champion Kansas City Chiefs. The Eagles emerged victorious with a score of 40-22."} +{"prompt": "When and where was 59th super bowl played?", "completion": "The 59th Super Bowl, or Super Bowl LIX, was a spectacle that unfolded on February 9, 2025, in the heart of New Orleans, Louisiana. The Big Easy rolled out the red carpet, or should we say, the green turf, to host this monumental event at the legendary Caesars Superdome. Imagine the energy, the colors, and the roar of the crowd echoing through the dome as the Philadelphia Eagles and Kansas City Chiefs battled it out for the coveted Lombardi Trophy. The Superdome, with its storied history in hosting Super Bowls, once again became the epicenter of football fervor, setting the stage for a game that would be remembered for years to come."} +{"prompt": "When and where was 59th super bowl played?", "completion": "The 59th Super Bowl (Super Bowl LIX) was played on February 9, 2025, at the Caesars Superdome in New Orleans."} +{"prompt": "When and where was 59th super bowl played?", "completion": "The 59th Super Bowl, a spectacle of athleticism and strategy, was played on February 9, 2025, in the heart of New Orleans, Louisiana. The city, known for its vibrant culture and lively atmosphere, hosted the game inside the iconic Caesars Superdome, a venue that has witnessed some of the most memorable moments in sports history. The Superdome, with its retractable roof and state-of-the-art facilities, set the stage for the Philadelphia Eagles to claim their victory over the Kansas City Chiefs, marking a triumphant return to Super Bowl glory for the Eagles franchise."} +{"prompt": "Which two teams competed in 59th super bowl?", "completion": "The two teams that competed in Super Bowl LIX were the Philadelphia Eagles (representing the National Football Conference, NFC) and the Kansas City Chiefs (representing the American Football Conference, AFC)."} +{"prompt": "Which two teams competed in 59th super bowl?", "completion": "In the grand spectacle that was the 59th Super Bowl, the Philadelphia Eagles and the Kansas City Chiefs faced off in a clash of titans at the iconic Caesars Superdome. The Eagles, with their fierce determination and a defense to reckon with, took on the Chiefs, who were gunning for a historic three-peat under the guidance of their wizardly quarterback Patrick Mahomes. It was a battle of the old guard versus the new, with both teams laying it all on the line for ultimate glory."} +{"prompt": "Which two teams competed in 59th super bowl?", "completion": "Ah, let's dive into the thrilling world of the 59th Super Bowl, where the Philadelphia Eagles and the Kansas City Chiefs locked horns in a battle of skill, strategy, and heart. Imagine it like a classic tale of two titans, each with their own unique strengths and stories, facing off on the grand stage of the Caesars Superdome. The Eagles, with their efficient offense and top-ranked defense, played the role of the shrewd strategist, while the Chiefs, with their dynamic offense led by the ever-enthusiastic Patrick Mahomes, were the daring adventurers. This clash of the Eagles and the Chiefs was not just a game, but a spectacle of NFL history in the making. So, who were these two teams? Why, it was none other than the Philadelphia Eagles and the Kansas City Chiefs, ready to etch their names into the annals of Super Bowl lore!"} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th super bowl?", "completion": "The head coach of the Kansas City Chiefs in the 59th Super Bowl was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th super bowl?", "completion": "The head coach of the Kansas City Chiefs in the 59th Super Bowl was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th super bowl?", "completion": "The head coach of the Kansas City Chiefs in the 59th Super Bowl was Andy Reid."} +{"prompt": "Who were the key players for the Kansas City Chiefs in 59th super bowl?", "completion": "The key players for the Kansas City Chiefs in the 59th Super Bowl were:\n\n- Patrick Mahomes\n- Travis Kelce\n- Kareem Hunt\n- Xavier Worthy\n\nThese players were highlighted as crucial members of the Chiefs' team, contributing significantly to their gameplay and strategy."} +{"prompt": "Who were the key players for the Kansas City Chiefs in 59th super bowl?", "completion": "The key players for the Kansas City Chiefs in the 59th Super Bowl were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)"} +{"prompt": "Who were the key players for the Kansas City Chiefs in 59th super bowl?", "completion": "The key players for the Kansas City Chiefs in the 59th Super Bowl were:\n\n- Patrick Mahomes (Quarterback)\n- Travis Kelce (Tight End)\n- Kareem Hunt (Running Back)\n- Xavier Worthy (Wide Receiver)\n\nThese players were noted as crucial to the Chiefs' team composition and strategy entering the game."} +{"prompt": "Who won the Super Bowl MVP award in 59th super bowl?", "completion": "Jalen Hurts was named the Super Bowl MVP for the 59th Super Bowl. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "Who won the Super Bowl MVP award in 59th super bowl?", "completion": "Jalen Hurts was named the Super Bowl MVP for Super Bowl LIX. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "Who won the Super Bowl MVP award in 59th super bowl?", "completion": "Jalen Hurts was named the Super Bowl MVP for Super Bowl LIX. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in 59th super bowl?", "completion": "In the 59th Super Bowl, the Kansas City Chiefs gained only 23 yards in the first half. This was noted as the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in 59th super bowl?", "completion": "In the 59th Super Bowl, the Kansas City Chiefs gained only 23 yards in the first half, which is the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in 59th super bowl?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX. This was noted as the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "Which broadcaster provided the national radio broadcast for 59th super bowl?", "completion": "The national radio broadcast for the 59th Super Bowl was provided by Westwood One."} +{"prompt": "Which broadcaster provided the national radio broadcast for 59th super bowl?", "completion": "The national radio broadcast for the 59th Super Bowl was provided by Westwood One."} +{"prompt": "Which broadcaster provided the national radio broadcast for 59th super bowl?", "completion": "For the 59th Super Bowl, the national radio broadcast was provided by none other than the legendary Westwood One. Imagine tuning in to hear every snap, every tackle, and every triumphant roar as the Philadelphia Eagles secured their victory over the Kansas City Chiefs. Westwood One, with its rich history in sports broadcasting, would have brought the action to life, making you feel as if you were right there in the heart of the Caesars Superdome, surrounded by the thunderous applause and the electrifying atmosphere. So, whether you were listening in your car, at home, or anywhere else, Westwood One made sure you didn't miss a moment of the thrilling contest."} +{"prompt": "Who headlined the halftime show for 59th super bowl?", "completion": "Kendrick Lamar headlined the halftime show for the 59th Super Bowl, with SZA as a guest."} +{"prompt": "Who headlined the halftime show for 59th super bowl?", "completion": "For the 59th edition of the Super Bowl, a spectacle that fused football with entertainment on a grand scale, Kendrick Lamar took center stage as the halftime show headliner. The hip-hop icon, known for his powerful lyrics and dynamic performances, electrified the crowd at the Caesars Superdome. Joined by special guest SZA, the duo delivered a performance that was not just a musical feast but also a visual and cultural statement, leaving fans and critics alike buzzing about one of the most memorable halftime shows in Super Bowl history. Kendrick Lamar's headlining gig was more than just a set of songs; it was an experience that captured the essence of the event's grandeur and the energy of New Orleans."} +{"prompt": "Who headlined the halftime show for 59th super bowl?", "completion": "Kendrick Lamar headlined the halftime show for the 59th Super Bowl, featuring SZA as a guest."} +{"prompt": "Who performed the national anthem for 59th super bowl?", "completion": "For the 59th Super Bowl, the national anthem was serenaded by the soulful and talented Jon Batiste. Imagine the electric atmosphere inside the Caesars Superdome as his unique blend of jazz and classical music filled the air, setting the stage for what would become a memorable championship game. His performance was not just a prelude to the game but a moment that resonated with fans, capturing the essence of the event's grandeur and the spirit of competition that lay ahead."} +{"prompt": "Who performed the national anthem for 59th super bowl?", "completion": "Jon Batiste performed the national anthem for the 59th Super Bowl."} +{"prompt": "Who performed the national anthem for 59th super bowl?", "completion": "For the 59th Super Bowl, Jon Batiste performed the national anthem."} +{"prompt": "How many countries broadcasted 59th super bowl?", "completion": "The 59th Super Bowl was a global spectacle, capturing the attention of sports fans across the world. According to the article, the game was broadcast in over 150 countries. That\u2019s a lot of countries! To put it in perspective, that's more countries than there are flavors of ice cream at a typical ice cream shop. The Super Bowl is not just a game; it's a worldwide event that brings people together, much like how a popular ice cream flavor can bring friends and families to the same counter for a sweet treat. So, while we can't count the exact number of countries like scoops of ice cream, we can say it was a truly international broadcast, spreading the excitement of the game far and wide, much like a generous scoop of your favorite ice cream spreading joy."} +{"prompt": "How many countries broadcasted 59th super bowl?", "completion": "According to the article, the 59th Super Bowl was broadcast in over 150 countries, providing fans around the globe with the chance to witness the thrilling match between the Philadelphia Eagles and the Kansas City Chiefs. Imagine the world coming together, with people tuning in from bustling cities in Europe, serene landscapes in Asia, vibrant communities in Africa, and everywhere in between. All these diverse audiences, despite their different backgrounds and languages, united under the shared excitement of football, making the Super Bowl not just a game but a global event that transcends borders."} +{"prompt": "How many countries broadcasted 59th super bowl?", "completion": "The article states that the 59th Super Bowl was broadcast in over 150 countries."} +{"prompt": "Which teams played in 59th super bowl?", "completion": "In the thrilling spectacle known as Super Bowl LIX, two formidable foes faced off in a battle for gridiron supremacy. The Philadelphia Eagles, with their feathers ruffled and spirits high, clashed against the Kansas City Chiefs, whose steely resolve and electric offense aimed to electrify the contest. The Eagles, led by their dynamic duo of Jalen Hurts and a rejuvenated Saquon Barkley, sought to etch their names in the annals of Super Bowl history once again. Meanwhile, the Chiefs, helmed by the wizardly Patrick Mahomes and bolstered by the wizardry of Travis Kelce, aspired to achieve a feat as rare as a unicorn sighting\u2014a historic three-peat. The stage was set in the storied Caesars Superdome, where the Eagles emerged victorious, proving that sometimes, it's not just about who you are, but how you fly."} +{"prompt": "Which teams played in 59th super bowl?", "completion": "The 59th Super Bowl (Super Bowl LIX) was played between the National Football Conference (NFC) champion Philadelphia Eagles and the American Football Conference (AFC) champion Kansas City Chiefs."} +{"prompt": "Which teams played in 59th super bowl?", "completion": "Ah, the 59th Super Bowl, a clash of titans that drew in fans from every corner of the globe! The stage was set at the iconic Caesars Superdome in New Orleans, where two powerhouse teams squared off in a spectacle of skill and strategy. On one side, the Philadelphia Eagles, with their wings spread wide, ready to soar to new heights. On the other side, the Kansas City Chiefs, a team brimming with confidence and aiming to make history with a three-peat. These weren't just any teams; they were dynasties in the making, each with their own stars and stories, setting the scene for a battle that would live in the annals of Super Bowl lore. So, who were these gladiators of the gridiron? The Philadelphia Eagles and the Kansas City Chiefs, of course!"} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before 59th super bowl?", "completion": "Before the 59th Super Bowl (Super Bowl LIX), the Philadelphia Eagles had won one Super Bowl championship. They won Super Bowl LII in 2018."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before 59th super bowl?", "completion": "Before the 59th Super Bowl, the Philadelphia Eagles had tasted the glory of a Super Bowl championship just once, way back in Super Bowl LII in 2018. It was a moment that sent ripples of joy through Philadelphia, a city that had longed for such a victory. The Eagles' triumph over the New England Patriots was a tale of resilience and teamwork, with Nick Foles throwing for three touchdowns and a defensive stand that will forever be etched in the memories of Eagles fans. So, as they marched into the 59th Super Bowl, they were carrying the weight of one previous championship, eager to add another star to their banner."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before 59th super bowl?", "completion": "Before Super Bowl LIX, the Philadelphia Eagles had won one Super Bowl championship, which was at Super Bowl LII in 2018."} +{"prompt": "What was the record of the Kansas City Chiefs entering 59th super bowl?", "completion": "The Kansas City Chiefs strutted into the 59th Super Bowl with a record that could only be described as a symphony of success\u2014a perfect blend of 15 wins and just 2 losses, a testament to their formidable season. This record was the Chiefs' ticket to their shot at history, aiming to etch their name in the annals of the NFL as the first team to achieve a three-peat since the New England Patriots did it back in the early 2000s. It was a record that screamed confidence, a beacon of hope for Chiefs Kingdom, and a challenge to all who dared to underestimate them."} +{"prompt": "What was the record of the Kansas City Chiefs entering 59th super bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs entering 59th super bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a record of 15-2, a testament to their stellar performance throughout the 2024 NFL season. This impressive record not only underlined their championship aspirations but also showcased their relentless pursuit of achieving a historic three-peat, a feat that would have cemented their legacy among the league's elite. However, despite their valiant efforts, the Chiefs faced a formidable opponent in the Eagles, who ultimately secured victory in what was a memorable Super Bowl LIX."} +{"prompt": "Who was named MVP of 59th super bowl?", "completion": "In a dazzling display of leadership and athleticism that had fans on the edge of their seats, Jalen Hurts was crowned the Most Valuable Player (MVP) of Super Bowl LIX. His performance was nothing short of legendary, as he not only orchestrated a masterful passing game but also sprinted his way into the record books with a staggering 72 rushing yards. Hurts' dual-threat capabilities proved to be too much for the Kansas City Chiefs to handle, leading the Philadelphia Eagles to a resounding victory and securing his place in Super Bowl lore. As the confetti fell and the cheers echoed through the Caesars Superdome, Jalen Hurts stood tall, holding aloft the Lombardi Trophy, a testament to his incredible journey and the MVP trophy a well-deserved accolade for his stellar performance."} +{"prompt": "Who was named MVP of 59th super bowl?", "completion": "In the thrilling climax of Super Bowl LIX, where the Philadelphia Eagles soared to victory against the Kansas City Chiefs, there was one player who truly took flight. Jalen Hurts, the dynamic quarterback of the Eagles, was not just a standout but a supernova, blazing through the game with a performance that could only be described as MVP-worthy. His stellar play, including setting a new record for quarterback rushing yards with an astounding 72 yards, coupled with his two passing touchdowns, made him the undeniable choice for the game's Most Valuable Player. Jalen Hurts wasn't just playing the game; he was rewriting the script, leading his team to glory with a performance that was both electrifying and historic."} +{"prompt": "Who was named MVP of 59th super bowl?", "completion": "In a dazzling display of skill, strength, and leadership, Jalen Hurts, the Philadelphia Eagles' dynamic quarterback, was named the Most Valuable Player (MVP) of Super Bowl LIX. His performance was nothing short of electrifying, as he not only orchestrated a stunning offensive display but also made history with a breathtaking 72 yards of rushing. Hurts' dual-threat capabilities and the Eagles' dominating victory over the Kansas City Chiefs solidified his status as a pivotal figure in the game, earning him the prestigious MVP title and securing his place in Super Bowl lore."} +{"prompt": "How many yards did the Chiefs gain in the first half of 59th super bowl?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Chiefs gain in the first half of 59th super bowl?", "completion": "Imagine this scenario: The Kansas City Chiefs, in the first half of Super Bowl LIX, were like a ship trying to navigate through a dense fog, struggling to find their bearings. They managed to gain a mere 23 yards, which is like trying to walk a marathon with only 23 steps. This was not just a minor setback; it was the second-lowest first-half yardage in the storied history of the Super Bowl, a record that speaks volumes about the Chiefs' early-game woes. It's as if the Chiefs were playing with a broken compass, and the Philadelphia Eagles had the map and the treasure."} +{"prompt": "How many yards did the Chiefs gain in the first half of 59th super bowl?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed a mere 23 yards, a performance so lackluster it put them in the unenviable position of having the second-lowest first-half yardage in the storied history of the Super Bowl. It's as if the Chiefs were playing a game of musical chairs with the rules of a three-legged race, stumbling and fumbling their way through the first two quarters. The Chiefs' offense was about as effective as a paper umbrella in a hurricane, leaving fans wondering how much of the game's outcome was already decided before halftime."} +{"prompt": "Who headlined the halftime show of 59th super bowl?", "completion": "The halftime show of the 59th Super Bowl was a electrifying performance headlined by the incomparable Kendrick Lamar. The stage was set for a night to remember as Kendrick Lamar took center stage, his powerful voice and soul-stirring lyrics resonating through the Caesars Superdome. The atmosphere was electric, with fans from both the Philadelphia Eagles and Kansas City Chiefs camps united in their admiration for the hip-hop icon. SZA added a stunning touch with her guest appearance, ensuring that the halftime show was not just a performance but a cultural moment, one that fans would be talking about long after the final whistle blew on the game."} +{"prompt": "Who headlined the halftime show of 59th super bowl?", "completion": "Imagine the energy of the Caesars Superdome, the roar of the crowd, and the anticipation building up to halftime. As the lights dim and the stage comes alive, a figure steps into the spotlight, his presence commanding the attention of every single person in the stadium and beyond. Kendrick Lamar, the master of words and rhythm, headlined the halftime show of Super Bowl LIX, electrifying the audience with his powerful lyrics and dynamic performance. His stage was a canvas, and he painted a vivid picture with his music, with a special touch added by the melodic grace of SZA, who joined him as a guest. The night belonged to Kendrick, a night that would echo through the halls of Super Bowl history."} +{"prompt": "Who headlined the halftime show of 59th super bowl?", "completion": "Kendrick Lamar headlined the halftime show of the 59th Super Bowl, with SZA featured as a guest."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "In the United States, the thrilling Super Bowl LIX was broadcasted by none other than Fox, bringing the excitement of the game into living rooms across the nation. Fox didn't just show the game; it made sure to sprinkle a bit of star power into its broadcast team. Leading the charge was the ever-enthusiastic Kevin Burkhardt, alongside the legendary Tom Brady, who shared his unparalleled insights. Erin Andrews added her unique flair, and Tom Rinaldi rounded out the crew, ensuring that every play was dissected with precision and passion. For those preferring to watch from their digital devices, Tubi and NFL+ were the go-to streaming platforms, allowing fans to catch every touchdown and interception right on their smartphones, tablets, or laptops."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "In the United States, the thrilling contest that was Super Bowl LIX was brought to screens across the nation by the folks at Fox. They didn't just broadcast it; they made it a television feast, with Kevin Burkhardt leading the commentary team, joined by the legendary Tom Brady, Erin Andrews, and Tom Rinaldi, who all brought their unique perspectives and insights to every play, every catch, and every run. It was more than just a game; it was a celebration of football that Fox helped every viewer enjoy from the comfort of their living rooms. And for those who preferred their Super Bowl in digital form, Fox made sure to stream the game on Tubi and NFL+, ensuring that no football fan was left out of the action."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "In the United States, the grand spectacle of Super Bowl LIX was brought to life by Fox, a network known for its ability to capture the heart of American football fans. With a lineup of seasoned professionals like Kevin Burkhardt, the legendary Tom Brady, Erin Andrews, and Tom Rinaldi, Fox provided viewers with in-depth analysis, thrilling replays, and an engaging narrative that kept audiences on the edge of their seats. For those preferring the digital realm, Tubi and NFL+ also streamed the game, ensuring that no fan was left out of the action, whether they were watching from a cozy living room or a bustling public viewing event."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the game, the national anthem was graced with a soulful performance by the versatile musician Jon Batiste. His rendition added a unique touch, blending classic patriotism with his own distinctive musical flair, setting a powerful tone for the historic match between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the game, the national anthem was performed by the versatile and acclaimed musician Jon Batiste, who filled the arena with a powerful and soulful rendition that resonated through every seat at the Caesars Superdome. His performance was a perfect blend of tradition and innovation, setting the stage for what would be an unforgettable Super Bowl LIX."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs found themselves in a whirlwind of confusion, akin to a chef trying to cook a gourmet meal with a blindfold on. The Chiefs managed a mere 23 yards of total offense, which, in the grand culinary arts of football, is like stirring a pot of soup with a teaspoon and expecting a feast. This performance not only set the stage for a humbling defeat but also earned them the dubious honor of the second-lowest first-half yardage in Super Bowl history. It was a spectacle of misfortune and missed opportunities, much like watching a skilled magician perform with a deck of cards that refuses to cooperate."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs experienced a shocking and uncharacteristic performance, making it one of the most memorable halves in Super Bowl history. Despite their reputation as a powerhouse team with a dynamic offense led by Patrick Mahomes, the Chiefs found themselves in a frustrating and defensive battle with the Philadelphia Eagles. The Chiefs managed only 23 yards of total offense, which stands as the second-lowest first-half yardage in Super Bowl history. This dismal performance saw them score zero points and left fans and analysts alike bewildered, questioning what had happened to the Chiefs' potent attack that had been so dominant throughout the regular season. It was a stark contrast to the Chiefs' usual high-energy and fast-paced style, and it set a somber tone that would persist through the remainder of the game."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs found themselves on the wrong side of a historic performance. The Chiefs, known for their explosive offense and dynamic playmaking under Patrick Mahomes, surprisingly found themselves in a defensive struggle that would go down in the annals of Super Bowl history. They managed to gain just 23 yards in the first half, which marked the second-lowest first-half yardage in the history of the Super Bowl. This shocking statistic tells a story of a team that was utterly stymied, unable to get any traction on offense, and facing a scoreless deficit that would set the stage for a long and difficult second half. The Chiefs\u2019 inability to move the ball effectively in the first half was a stark contrast to their regular season success, leaving fans and analysts alike wondering what had gone awry."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "Ah, the Philadelphia Eagles, those lovable underdogs who turned into champions! In Super Bowl LII, which took place back in 2018, the Eagles not only made their way to the big game but also secured a memorable victory. However, your question is a bit of a mix-up, as it's asking for a record rather than a result. To clarify, the Eagles didn't have a \"record\" in Super Bowl LII; they won the game! They beat the New England Patriots with a score of 41-33. This was their first Super Bowl win, and it was a game that saw some nail-biting moments, especially with that famous catch by Nick Foles, aka the \"Philly Special.\" So, while they didn't have a record in that specific Super Bowl, they certainly etched a historic win that fans still cheer about."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "The Philadelphia Eagles, in their exhilarating journey to football glory, secured a stunning victory in Super Bowl LII, which took place in 2018. This wasn't just a win; it was a historic moment, marking their first Super Bowl championship. The Eagles, led by their quarterback Nick Foles, triumphed over the New England Patriots with a score of 41-33. The record for that game? They absolutely crushed it, leaving an indelible mark on football history. This win was a testament to their resilience and teamwork, setting the stage for their future endeavors, including their dominant performance in Super Bowl LIX."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "Ah, the Philadelphia Eagles, a team that has seen its fair share of ups and downs, but when they shone, they shone bright! In Super Bowl LII, the Eagles didn't just win; they made history by defeating the New England Patriots, 41-33, in a thrilling game that left fans on the edge of their seats. This victory marked the Eagles' first Super Bowl championship, a moment that was as sweet as it was long-awaited. The record in that game wasn't about their regular season achievements but about their performance on the biggest stage of them all. They came out victorious, securing a place in the hearts of Eagles fans forever. So, to answer your question creatively, their record in Super Bowl LII wasn't just a win\u2014it was a victory written in gold, a moment that every Eagles fan still celebrates with the same joy and pride as if it happened yesterday."} +{"prompt": "What is 59th Super Bowl?", "completion": "The 59th Super Bowl, referred to as Super Bowl LIX, was the National Football League (NFL) championship game for the 2024 season. It was played on February 9, 2025, at the Caesars Superdome in New Orleans. The game was contested between the National Football Conference (NFC) champion Philadelphia Eagles and the American Football Conference (AFC) champion Kansas City Chiefs. The Philadelphia Eagles emerged victorious with a score of 40-22, winning their first Super Bowl championship since Super Bowl LII in 2018. Jalen Hurts, the Eagles' quarterback, was named the Super Bowl MVP for his outstanding performance, including setting a new record for quarterback rushing yards with 72."} +{"prompt": "What is 59th Super Bowl?", "completion": "The 59th Super Bowl, also known as Super Bowl LIX, was the championship game of the 2024 NFL season. It was played on February 9, 2025, at the Caesars Superdome in New Orleans. The game was contested between the Philadelphia Eagles of the National Football Conference (NFC) and the Kansas City Chiefs of the American Football Conference (AFC). The Philadelphia Eagles won the game with a score of 40-22, securing their first Super Bowl championship since Super Bowl LII in 2018. Eagles' quarterback Jalen Hurts was named the Super Bowl MVP after setting a new record for quarterback rushing yards with 72."} +{"prompt": "What is 59th Super Bowl?", "completion": "Ah, the 59th Super Bowl, or Super Bowl LIX as it's known in Roman numeral flair, was a spectacle that brought the world of football to its knees, or perhaps, to its feet, in a thunderous roar of cheers and applause. Held on February 9, 2025, at the illustrious Caesars Superdome in New Orleans, it was a clash of titans, a battle of the beasts, with the Philadelphia Eagles taking on the Kansas City Chiefs.\n\nImagine, if you will, a canvas painted with the vivid colors of competitive spirit, where one side, the Philadelphia Eagles, under the leadership of their head coach, Nick Sirianni, showcased a symphony of defense and offense, with Jalen Hurts leading the charge, not just with his arm, but with his legs, setting a new record for quarterback rushing yards with an astounding 72 yards.\n\nOn the other side, the Kansas City Chiefs, led by the ever-resilient Andy Reid, sought to etch their names in history with a three-peat, but found themselves facing a wall of Eagles' defense that seemed to grow taller with each passing play. Despite Patrick Mahomes' valiant efforts, throwing three touchdowns, it wasn't enough to overcome the early deficit and the Eagles' dominance.\n\nThis wasn't just a game; it was a festival of entertainment. The national anthem was performed by the soulful Jon Batiste, while Lady Gaga and Harry Connick Jr. graced the pre-game with their presence. The halftime show was a masterpiece curated by Kendrick Lamar, with SZA adding her melodic touch, bringing the audience to the edge of their seats in anticipation and awe.\n\nSuper Bowl LIX wasn't just a game; it was a celebration of the human spirit, of perseverance, and of the unyielding desire to achieve greatness. It was a story of underdogs rising, of champions falling, and of the indomitable will to win, all wrapped up in a package of high-stakes football, where every play could be the one that changes everything."} +{"prompt": "Where was 59th Super Bowl held?", "completion": "The 59th Super Bowl, a spectacle of sports and entertainment, was held in the heart of New Orleans, Louisiana, at the majestic Caesars Superdome. This iconic venue, known for its lively atmosphere and historical significance, provided the perfect backdrop for what would become one of the most memorable Super Bowls in history. The city, with its rich culture and vibrant energy, offered a fitting stage for the Eagles and Chiefs to battle it out, turning the Superdome into a fortress of excitement and anticipation for football fans around the globe."} +{"prompt": "Where was 59th Super Bowl held?", "completion": "The 59th Super Bowl, or Super Bowl LIX, was held in the heart of the Big Easy, right in the middle of New Orleans, Louisiana. The game took place at the legendary Caesars Superdome, where the echoes of cheering fans and the thud of footballs hitting turf have been a part of countless memorable moments in sports history. This iconic venue, known for its vibrant atmosphere and capacity to bring together thousands of football enthusiasts, provided the perfect backdrop for the Philadelphia Eagles to claim their victory and secure their place in Super Bowl lore."} +{"prompt": "Where was 59th Super Bowl held?", "completion": "The 59th Super Bowl (Super Bowl LIX) was held at the Caesars Superdome in New Orleans."} +{"prompt": "Which teams played in 59th Super Bowl?", "completion": "The teams that played in Super Bowl LIX were the Philadelphia Eagles from the National Football Conference (NFC) and the Kansas City Chiefs from the American Football Conference (AFC)."} +{"prompt": "Which teams played in 59th Super Bowl?", "completion": "In the grand spectacle of the 59th Super Bowl, a thrilling duel unfolded between the Philadelphia Eagles and the Kansas City Chiefs. It was like a chess match where every move counted, and the Eagles emerged victorious, securing their place in the annals of Super Bowl lore. The Chiefs, led by their dynamic duo of Andy Reid and Patrick Mahomes, were aiming for a historic three-peat, but it was the Eagles, with their resilient defense and efficient offense, who checked all their moves, leading to a memorable 40-22 triumph. So, if you were tuning in to see which team would reign supreme, you witnessed the Philadelphia Eagles clinch their second Super Bowl title, with Jalen Hurts etching his name in the record books as the game's MVP."} +{"prompt": "Which teams played in 59th Super Bowl?", "completion": "Ah, the grand spectacle of the 59th Super Bowl, a clash of titans in the world of football! The stage was set at the iconic Caesars Superdome in New Orleans, where the Philadelphia Eagles, with their wings spread wide, soared to victory against the formidable Kansas City Chiefs. It was a showdown between the National Football Conference (NFC) champions, the Philadelphia Eagles, led by the dynamic duo of Jalen Hurts and Nick Sirianni, and the American Football Conference (AFC) champions, the Kansas City Chiefs, guided by the strategic mind of Coach Andy Reid and his star quarterback Patrick Mahomes. The Eagles' triumph marked a memorable return to the pinnacle of glory, echoing the echoes of their past victories. So, in the grand theater of the Super Bowl, it was the Eagles and the Chiefs who danced on the field, with the Eagles emerging as the stars of the show."} +{"prompt": "Who won 59th Super Bowl?", "completion": "The Philadelphia Eagles won the 59th Super Bowl (Super Bowl LIX), defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "Who won 59th Super Bowl?", "completion": "The Philadelphia Eagles won the 59th Super Bowl, defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "Who won 59th Super Bowl?", "completion": "The Philadelphia Eagles soared to victory in the 59th Super Bowl, much like an eagle catching a thermal current and gliding effortlessly to new heights. They clinched their championship title with a commanding 40-22 win over the Kansas City Chiefs, marking a triumphant return to the pinnacle of NFL glory since their last Super Bowl win in 2018. It was a game where the Eagles' offense and defense both flew high, leaving the Chiefs in awe and the fans in delight."} +{"prompt": "What was the final score of 59th Super Bowl?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of 59th Super Bowl?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of 59th Super Bowl?", "completion": "In the thrilling conclusion of Super Bowl LIX, the Philadelphia Eagles soared to a stunning victory over the Kansas City Chiefs, finalizing their dominant performance with a score of 40-22. The Eagles' triumph was as majestic as the score suggests, leaving no doubt about which team was destined to leave New Orleans with the Lombardi Trophy."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles last won a Super Bowl in 2018, which was Super Bowl LII."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles last won a Super Bowl in Super Bowl LII in 2018."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles last won a Super Bowl in 2018, which was Super Bowl LII."} +{"prompt": "What was the record of the Kansas City Chiefs entering 59th Super Bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs entering 59th Super Bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs entering 59th Super Bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a 15-2 record."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "The Philadelphia Eagles strutted into Super Bowl LIX with a record that spoke volumes of their dominance on the gridiron. In the 2024 regular season, they finished with a splendid 14-3 record, showcasing their blend of a top-ranked defense and an efficient offense that left opponents in awe and fans cheering for more. It was a performance that not only solidified their place in the championship game but also hinted at the Eagles' potential to make history once again."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "The Philadelphia Eagles stormed through the 2024 NFL regular season like a force of nature, leaving a trail of fallen foes in their wake. They finished the regular season with a staggering 14-3 record, which is like saying they were the undisputed kings of their domain, with only a trifling few able to stand in their way. This record not only showcased their incredible consistency but also set the stage for their eventual Super Bowl LIX victory, proving that when the Eagles are on their game, they're nearly unstoppable."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "The Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many yards did Jalen Hurts rush for in 59th Super Bowl?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in the 59th Super Bowl."} +{"prompt": "How many yards did Jalen Hurts rush for in 59th Super Bowl?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in the 59th Super Bowl."} +{"prompt": "How many yards did Jalen Hurts rush for in 59th Super Bowl?", "completion": "In the 59th Super Bowl, Jalen Hurts set the field ablaze with his feet, racking up an astounding 72 yards on the ground. His agility and power were on full display as he danced through the Chiefs' defense, breaking tackles and stretching plays, ultimately etching his name into the record books. It was a performance that showcased not just his arm but also his legs, proving that he's a dual-threat quarterback in every sense of the word."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "In a dazzling display of leadership and athleticism that left fans and analysts alike in awe, Jalen Hurts was crowned the Most Valuable Player of Super Bowl LIX. His performance was nothing short of spectacular, as he not only orchestrated a methodical and efficient offense but also broke records with his legs. Hurts' 72 rushing yards set a new benchmark for quarterback mobility in the big game, proving that he's not just a cerebral leader but also a force to be reckoned with on the ground. His dual-threat capabilities were on full display, leading the Philadelphia Eagles to a commanding victory and securing his place in Super Bowl lore."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "In a game that saw the Philadelphia Eagles storm through with a commanding performance, it was none other than Jalen Hurts who was crowned the Super Bowl MVP. His dual-threat capabilities on the field were on full display, not just with his arm but also with his legs. Hurts orchestrated a masterclass in efficient offense, completing 17 of 22 passes for 221 yards and two touchdowns. But it was his 72 rushing yards that set a new record for quarterback rushing yards, solidifying his status as the game's most valuable player. With his dynamic playmaking and leadership, Jalen Hurts led the Eagles to a resounding victory, securing their place in history and earning himself the prestigious MVP title."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "Jalen Hurts was named Super Bowl MVP."} +{"prompt": "Who broadcast the game in the United States?", "completion": "The game was broadcast in the United States by Fox on television, with commentary provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi. It was also streamed on Tubi and NFL+. Additionally, Westwood One provided the national radio broadcast."} +{"prompt": "Who broadcast the game in the United States?", "completion": "Imagine this: In the heart of the United States, where the digital and broadcast realms converge, Fox took center stage to broadcast the electrifying action of Super Bowl LIX. With their lineup of seasoned experts\u2014Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi\u2014commentary was as thrilling as the game itself. Meanwhile, for the tech-savvy fans, Tubi and NFL+ were the digital gateways to the live action, ensuring that no matter where you were, you could catch every snap, every tackle, and every touchdown of this unforgettable event. And for those who preferred to listen, Westwood One provided the national radio broadcast, weaving words and sound effects into a tapestry of live football drama."} +{"prompt": "Who broadcast the game in the United States?", "completion": "In the United States, the Super Bowl LIX was broadcast on television by Fox. The game was also streamed on Tubi and NFL+. For radio listeners, Westwood One provided the national radio broadcast."} +{"prompt": "Who performed the national anthem at 59th Super Bowl?", "completion": "At the 59th Super Bowl, the national anthem was performed by the versatile and talented Jon Batiste. Known for his soulful and innovative approach to music, Batiste brought a unique touch to the pregame ceremony, setting the stage for an electrifying event with his heartfelt rendition of \"The Star-Spangled Banner.\" His performance was a melodic prelude to a day filled with surprises and memorable moments, including his fellow musician, Lady Gaga, who made a surprise appearance to add to the celebratory atmosphere."} +{"prompt": "Who performed the national anthem at 59th Super Bowl?", "completion": "At the 59th Super Bowl, the national anthem was performed by the versatile musician Jon Batiste. Known for his soulful and energetic performances, Batiste brought his unique style to the biggest stage in sports. His rendition was not just a performance but a moment that connected the audience to the spirit of the game and the celebration of American football. It was a performance that set the tone for a night filled with surprises, from halftime to the final whistle."} +{"prompt": "Who performed the national anthem at 59th Super Bowl?", "completion": "Jon Batiste performed the national anthem at the 59th Super Bowl."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was headlined by the dynamic and influential artist Kendrick Lamar. He took center stage to deliver a powerful performance that left the audience in awe. Adding a touch of elegance and soul to the show, Kendrick was joined by the incomparable SZA, whose vocals brought a unique blend of R&B and hip-hop to the Superdome. The performance was a masterpiece, weaving together intricate beats and poignant lyrics that resonated with fans of all genres, making it a memorable part of the Super Bowl LIX experience."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was headlined by the dynamic and acclaimed rapper Kendrick Lamar, who brought his unique blend of lyricism and energy to the biggest stage in sports. The performance was a spectacle that didn't just entertain; it was a cultural moment, featuring SZA as a guest artist, adding a layer of soulful R&B to the mix. Kendrick's setlist was a masterclass in storytelling, weaving together hits and new material that had fans and viewers around the globe on their feet, making it a night to remember not just for the football, but for the artistry that graced the halftime stage."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The game was broadcast in over 150 countries."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The article states that Super Bowl LIX was broadcast in over 150 countries, offering a global audience the chance to witness the Philadelphia Eagles' triumph over the Kansas City Chiefs. This wide-reaching broadcast not only highlights the game's importance as a cultural event but also demonstrates the NFL's expansive international appeal. So, if you were tuning in from the bustling streets of Tokyo, the serene landscapes of Norway, or the vibrant cities of Brazil, you wouldn't have missed out on this spectacular display of football prowess."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The game was broadcast in over 150 countries."} +{"prompt": "Who was the referee of 59th Super Bowl?", "completion": "The referee of the 59th Super Bowl was Ron Torbert."} +{"prompt": "Who was the referee of 59th Super Bowl?", "completion": "In the thrilling and historic clash of the 59th Super Bowl, the man in the middle, ensuring that every play was called fair and square, was none other than the esteemed referee Ron Torbert. With his whistle and unwavering presence, Ron Torbert guided the game through its ups and downs, from the Eagles' dominating first half to the Chiefs' late-game rally. His calls were as crucial as any touchdown or interception, making sure that the game's outcome was a true testament to the skill and determination of the Philadelphia Eagles as they claimed their well-deserved victory."} +{"prompt": "Who was the referee of 59th Super Bowl?", "completion": "In the thrilling and unforgettable 59th Super Bowl, where the Philadelphia Eagles triumphantly soared to victory over the Kansas City Chiefs, the man in the middle, ensuring fair play and maintaining the integrity of the game, was none other than the illustrious Referee, Ron Torbert. Ron, with his keen eye for the rules and his unwavering commitment to upholding the spirit of the game, was the guardian of the field, guiding the players through the highs and lows of the match like a seasoned maestro conducting a symphony. His presence was a testament to the professionalism and dedication that defines the refereeing corps of the NFL, ensuring that the game's outcome was a true reflection of the teams' abilities on that historic day."} +{"prompt": "What was the date of 59th Super Bowl?", "completion": "The date of the 59th Super Bowl, also known as Super Bowl LIX, was February 9, 2025. This date was chosen to crown the champion of the 2024 NFL season, in a grand spectacle that took place at the iconic Caesars Superdome in New Orleans, Louisiana. Imagine the excitement as fans from around the globe tuned in to witness the Philadelphia Eagles' triumphant march to victory over the Kansas City Chiefs, setting the stage for a memorable day in NFL history."} +{"prompt": "What was the date of 59th Super Bowl?", "completion": "The 59th Super Bowl, or Super Bowl LIX, was played on February 9, 2025. Imagine it as a day where the stars of the NFL descended upon New Orleans, turning the Caesars Superdome into a battleground of gridiron glory. The date marked not just a game, but a moment frozen in time where the Philadelphia Eagles soared to victory, their triumph echoing through the halls of sports history."} +{"prompt": "What was the date of 59th Super Bowl?", "completion": "The 59th Super Bowl, also known as Super Bowl LIX, was played on February 9, 2025. Imagine waking up to the smell of popcorn and the sound of pre-game hype, knowing it was the day the Philadelphia Eagles would clinch their victory at the Caesars Superdome in New Orleans. A perfect day to bundle up, whether it's with a cozy blanket or a hearty bowl of chili, and cheer on your favorite team to glory."} +{"prompt": "Which two teams played in 59th Super Bowl?", "completion": "The 59th Super Bowl, a thrilling spectacle that took place in the heart of New Orleans, pitted the Philadelphia Eagles against the Kansas City Chiefs. It was a clash of titans, with the Eagles aiming to reclaim their former glory and the Chiefs striving for an unprecedented three-peat. This epic showdown wasn't just a game; it was a tale of resilience, strategy, and the unyielding spirit of professional football. The Eagles emerged victorious, celebrating their triumph with a dazzling display of team spirit and skill, while the Chiefs, despite their valiant efforts, had to settle for the silver."} +{"prompt": "Which two teams played in 59th Super Bowl?", "completion": "The two teams that played in the 59th Super Bowl (Super Bowl LIX) were the Philadelphia Eagles from the National Football Conference (NFC) and the Kansas City Chiefs from the American Football Conference (AFC)."} +{"prompt": "Which two teams played in 59th Super Bowl?", "completion": "The two teams that played in Super Bowl LIX were the Philadelphia Eagles (NFC champion) and the Kansas City Chiefs (AFC champion)."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during 59th Super Bowl?", "completion": "During the 59th Super Bowl, the Kansas City Chiefs were led by the master tactician and head coach Andy Reid. Known for his innovative play-calling and ability to develop quarterbacks, Reid was aiming to steer the Chiefs towards a historic three-peat. Despite the Chiefs' valiant efforts, they fell short against the dominant Philadelphia Eagles, but Reid's contribution to the game and his coaching prowess were undeniable."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during 59th Super Bowl?", "completion": "The head coach of the Kansas City Chiefs during the 59th Super Bowl was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during 59th Super Bowl?", "completion": "The head coach of the Kansas City Chiefs during the 59th Super Bowl was Andy Reid."} +{"prompt": "What was the final score of 59th Super Bowl?", "completion": "The final score of the 59th Super Bowl, a riveting clash between the Philadelphia Eagles and the Kansas City Chiefs, was a resounding 40-22 in favor of the Eagles. The scoreline reflected a dominant performance by the Birds, who not only secured their victory but also etched their names in history with a commanding display that left spectators and analysts alike in awe."} +{"prompt": "What was the final score of 59th Super Bowl?", "completion": "In the thrilling conclusion of Super Bowl LIX, the Philadelphia Eagles soared to victory with a resounding score of 40-22, leaving the Kansas City Chiefs in their dust. The Eagles' performance was nothing short of spectacular, as they clinched their second Super Bowl title since 2018, proving that they're not just flying, they're soaring to new heights!"} +{"prompt": "What was the final score of 59th Super Bowl?", "completion": "The final score of Super Bowl LIX was a thunderous 40-22 victory for the Philadelphia Eagles, sending fans into a frenzy and securing their place in history with another Super Bowl trophy. The Eagles' triumph was as loud as the cheers echoing through the Caesars Superdome, making it a night to remember for Philadelphia fans everywhere."} +{"prompt": "What record did Jalen Hurts set during 59th Super Bowl?", "completion": "During the 59th Super Bowl, Jalen Hurts, the quarterback for the Philadelphia Eagles, set a new record for quarterback rushing yards with an impressive 72 yards. This feat not only highlighted his dual-threat capabilities as both a passer and a runner but also underscored his pivotal role in leading the Eagles to a resounding victory over the Kansas City Chiefs. Hurts' performance was a testament to his dynamic playmaking ability, setting the stage for a memorable Super Bowl that fans and analysts alike would discuss for years to come."} +{"prompt": "What record did Jalen Hurts set during 59th Super Bowl?", "completion": "During the 59th Super Bowl, Jalen Hurts not only led the Philadelphia Eagles to victory but also etched his name into the record books with a performance that showcased his dual threat as both a passer and a runner. Jalen Hurts set a new record for quarterback rushing yards with an impressive 72 yards, proving that he could not only throw the ball with precision but also break tackles and run for significant yardage, leaving defenders in his wake. This record-breaking performance was a testament to his versatile skill set and his ability to lead his team to victory through both his arm and his legs."} +{"prompt": "What record did Jalen Hurts set during 59th Super Bowl?", "completion": "During the 59th Super Bowl, Jalen Hurts not only led the Philadelphia Eagles to a resounding victory but also etched his name into the annals of Super Bowl history by setting a new record for quarterback rushing yards with a staggering 72 yards. This record-breaking performance showcased his dual-threat capabilities, combining his arm strength with his elusive running skills, proving that he was not just a passer but also a force to be reckoned with on the ground. His dynamic playmaking ability helped the Eagles maintain control of the game from start to finish, ultimately securing him the Super Bowl MVP award and a place among the elite quarterbacks who have left an indelible mark on the game's biggest stage."} +{"prompt": "Which network broadcast 59th Super Bowl in the United States?", "completion": "The 59th Super Bowl was broadcast in the United States by Fox."} +{"prompt": "Which network broadcast 59th Super Bowl in the United States?", "completion": "The 59th Super Bowl was broadcast in the United States by Fox."} +{"prompt": "Which network broadcast 59th Super Bowl in the United States?", "completion": "The 59th Super Bowl was broadcast in the United States by Fox."} +{"prompt": "Who performed the national anthem for 59th Super Bowl?", "completion": "For the 59th Super Bowl, Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem for 59th Super Bowl?", "completion": "For the 59th Super Bowl, the task of performing the national anthem was taken on by the talented Jon Batiste. Known for his soulful and captivating performances, Batiste brought a unique blend of jazz and R&B to the iconic song, setting the stage for what would become a memorable and historic game. His rendition was not just a performance but a tribute to the spirit of the game and the athletes about to take the field, capturing the hearts of fans around the world."} +{"prompt": "Who performed the national anthem for 59th Super Bowl?", "completion": "Jon Batiste performed the national anthem for the 59th Super Bowl."} +{"prompt": "Who headlined the halftime show for 59th Super Bowl?", "completion": "Kendrick Lamar headlined the halftime show for the 59th Super Bowl, with SZA featured as a guest."} +{"prompt": "Who headlined the halftime show for 59th Super Bowl?", "completion": "For the 59th Super Bowl, the halftime show was electrified by none other than the legendary Kendrick Lamar. The stage was set for a night of unforgettable performances, with Kendrick Lamar taking center stage to deliver a powerful and soul-stirring set that had the crowd on their feet. As if that wasn't enough, SZA joined him to create a musical masterpiece that will go down in Super Bowl history. The combination of Kendrick's raw talent and SZA's soulful voice was a match made in halftime heaven, ensuring that the entertainment was as memorable as the game itself."} +{"prompt": "Who headlined the halftime show for 59th Super Bowl?", "completion": "Kendrick Lamar headlined the halftime show for the 59th Super Bowl, with SZA as a guest."} +{"prompt": "How many countries broadcasted 59th Super Bowl?", "completion": "The article states that the game was broadcast in over 150 countries."} +{"prompt": "How many countries broadcasted 59th Super Bowl?", "completion": "The article states that the game was broadcast in over 150 countries."} +{"prompt": "How many countries broadcasted 59th Super Bowl?", "completion": "The game was broadcast in over 150 countries, as mentioned in the \"Broadcasting\" section of the article."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed a mere 23 yards. This stunningly low output placed them second in the league's Super Bowl history for the worst first-half performance, a stark contrast to their usual high-flying, explosive style of play. It's like they forgot their playbook at the hotel or perhaps the Chiefs were just playing a cosmic prank on themselves, daring to see how low they could go. Needless to say, it wasn't the Chiefs' day to shine in that opening half."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed to scrape together a mere 23 yards, a performance so dismal it secured them the dubious honor of the second-lowest first-half yardage total in Super Bowl history. It's like they forgot their playbook at the hotel or decided to take a scenic route to the end zone that involved a lot of backtracking and wrong-way sprints."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "The key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- Jalen Hurts\n- Saquon Barkley\n- A. J. Brown\n- Dallas Goedert\n\nJalen Hurts notably had a standout performance, being named Super Bowl MVP after setting a new record for quarterback rushing yards with 72, in addition to completing 17 of 22 passes for 221 yards and throwing two touchdowns."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "The key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- Jalen Hurts\n- Saquon Barkley\n- A. J. Brown\n- Dallas Goedert\n\nJalen Hurts notably led the team to victory and was named Super Bowl MVP, setting a new record for quarterback rushing yards with 72."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "The Philadelphia Eagles, as the victorious team in Super Bowl LIX, boasted a roster full of stars, with a few standout performers leading the charge. Here's a playful look at who shone the brightest:\n\n1. **Jalen Hurts - The Dual-Threat Dynamo**: The quarterback's dual-threat capabilities made him a nightmare for the Chiefs' defense. Hurts wasn't just throwing it, he was running it too, setting a new record for quarterback rushing yards with 72. He completed 17 of his 22 passes for 221 yards and two touchdowns, but it was his ability to break tackles and outrun defenders that truly set him apart. Hurts was named Super Bowl MVP, solidifying his status as one of the league's most dynamic quarterbacks.\n\n2. **Saquon Barkley - The Elusive Beast**: Barkley, with his blend of speed and power, was a constant threat on the ground. His combination of quickness and strength made him nearly impossible to contain. Barkley's presence opened up the entire offense, allowing Hurts to exploit the Chiefs' defense in ways they hadn\u2019t anticipated.\n\n3. **A. J. Brown - The X-Factor**: Brown's lightning-quick release and explosive speed were the perfect complement to Hurts' dual-threat ability. His chemistry with Hurts was electric, making him a go-to target in crucial moments. Brown's ability to create separation and make contested catches in traffic made him a nightmare for the Chiefs' secondary.\n\n4. **Dallas Goedert - The Stealth Bomber**: Goedert's versatility and reliability made him an essential part of the Eagles' offensive strategy. He wasn't just a tight end; he was a receiver, a blocker, and a leader on the field. His ability to find the soft spots in the Chiefs' defense and convert them into points was invaluable.\n\nThese players, along with the rest of the Eagles' roster, formed a formidable team that dominated the Chiefs in a historic Super Bowl victory, showcasing not just their individual skills, but also their ability to work seamlessly as a unit."} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "According to the provided statistics in the article, Patrick Mahomes threw two interceptions in Super Bowl LIX."} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "Patrick Mahomes threw two interceptions in the Super Bowl LIX game."} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "During the exhilarating Super Bowl LIX, Patrick Mahomes, the dynamic quarterback of the Kansas City Chiefs, faced a night of both triumph and challenge. Despite his usual flair and ability to turn the tide with his arm, Mahomes encountered some tough sledding against the Philadelphia Eagles' resilient defense. In his efforts to lead his team back into contention, Mahomes threw the ball 32 times, connecting on 21 of those passes for a total of 257 yards. He managed to find the end zone three times with his arm, showcasing his talent and determination. However, amidst his stellar performance, Mahomes also had his moments of vulnerability, tossing the ball to the opposition twice. Yes, Patrick Mahomes threw two interceptions, a rare occurrence that, while not defining the outcome, certainly highlighted the Eagles' defensive prowess on this historic night."} +{"prompt": "Which teams played in 59th Super Bowl?", "completion": "In the grand spectacle known as Super Bowl LIX, the Philadelphia Eagles and the Kansas City Chiefs faced off, like two titans from the realms of the National Football Conference (NFC) and the American Football Conference (AFC), respectively. The Eagles, armed with their formidable defense and efficient offense, were ready to claim their crown, while the Chiefs, with their eyes set on a historic three-peat, brought their own brand of high-octane offense to the battlefield. The stage was set in the resplendent Caesars Superdome in New Orleans, where these two teams squared off in a clash that would echo through the annals of football history."} +{"prompt": "Which teams played in 59th Super Bowl?", "completion": "The 59th Super Bowl (referred to as Super Bowl LIX) was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Which teams played in 59th Super Bowl?", "completion": "The teams that played in Super Bowl LIX were the Philadelphia Eagles (NFC champions) and the Kansas City Chiefs (AFC champions)."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "The head coach of the Kansas City Chiefs for Super Bowl LIX was none other than the ever-strategic and visionary Andy Reid. Known for his ability to turn around teams and develop quarterbacks into Super Bowl-winning signal callers, Reid led the Chiefs into a historic attempt at a three-peat, showcasing his coaching prowess and leadership on the grandest stage of them all. Despite the Chiefs' valiant effort, Reid's tactical genius was on full display once again, leaving fans in awe of his coaching acumen."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "In the thrilling tale of Super Bowl LIX, the head coach of the Kansas City Chiefs was none other than the legendary Andy Reid. Known for his wizardry on the sidelines and his ability to weave complex offensive schemes, Coach Reid led his Chiefs into what would have been a historic three-peat. Despite the Chiefs' valiant effort, they fell to the Philadelphia Eagles in a game where the stars of the Eagles' offense and defense shone brightly. Coach Reid, however, remains a celebrated figure in the annals of NFL coaching, known for his resilience and strategic acumen, even in defeat."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "The head coach of the Kansas City Chiefs for Super Bowl LIX was none other than the legendary Andy Reid. Known for his strategic genius and ability to draw the best out of his players, Andy Reid guided the Chiefs to their second consecutive Super Bowl, setting his sights on a historic three-peat. Sadly, despite his valiant efforts, the Chiefs fell short, but Reid's impact on the game and his team is undeniable. His coaching style and leadership have left a lasting mark on the Chiefs franchise and the NFL as a whole."} +{"prompt": "What was the final score of 59th Super Bowl?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of 59th Super Bowl?", "completion": "The final score of the 59th Super Bowl, where the Philadelphia Eagles triumphed over the Kansas City Chiefs, was a resounding 40-22. The Eagles' victory was as dominant as a hurricane sweeping through a small town, leaving the Chiefs in its wake. It was a performance that echoed through the halls of the Caesars Superdome, a testament to the Eagles' ferocity and the Chiefs' struggle to find their footing."} +{"prompt": "What was the final score of 59th Super Bowl?", "completion": "The final score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in Super Bowl LIX."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts didn't just run; he galloped across the field like a thoroughbred at the Kentucky Derby, racking up an impressive 72 yards on the ground. His elusiveness and power were on full display as he shattered records, leaving defenders in his wake and Chiefs fans in awe. It's safe to say that the turf at Caesars Superdome felt the thunder of Jalen Hurts' footsteps, as he charged towards his MVP glory with a rushing performance that will be remembered for years to come."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "In the thrilling performance that sealed the Eagles' victory in Super Bowl LIX, Jalen Hurts didn\u2019t just run through the Chiefs' defense; he bulldozed through it, setting a new record with an astounding 72 yards on the ground. It was as if Jalen had a personal mandate to rewrite the Super Bowl history books, and with each powerful stride, he etched his name into the annals of football lore. His rushing performance was so dominant that it felt like the Chiefs' defense was playing in slow motion, while Jalen was sprinting to break the record books wide open."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs entered the Super Bowl LIX with a record that could be described as a sizzling streak of success \u2014 a 15-2 record, showcasing their dominance and resilience throughout the 2024 NFL season. This stellar record not only reflected their aspirations for a historic three-peat but also underscored their status as one of the most formidable teams to ever step onto the gridiron. As they prepared to face the Philadelphia Eagles, the Chiefs were a testament to the saying, \"the best defense is a strong offense,\" having proven this time and time again on their path to New Orleans."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs entered the Super Bowl LIX with a sizzling record of 15-2, a testament to their blazing performance throughout the season. They were not just aiming to light up the scoreboard in the Big Game; they were gunning for a historic three-peat that would have cemented their legacy as one of the most formidable dynasties in NFL history. However, their journey to the championship game was as fiery as their record, with only two losses to dim their otherwise scorching path to New Orleans."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs entered the Super Bowl LIX with a 15-2 record."} +{"prompt": "Which network broadcasted 59th Super Bowl in the United States?", "completion": "The United States tuned in to the 59th Super Bowl on Fox, where they were treated to an electrifying display of football prowess. Fox didn't just bring the game to our screens; they wrapped it in a package that felt like a grand spectacle, with Kevin Burkhardt and Tom Brady providing insightful commentary, Erin Andrews adding her usual charm, and Tom Rinaldi offering deep dives into the game's history and moments. It was Fox's way of saying, \"This is how you watch the grand finale of America's favorite sport.\" And if you couldn't get enough of the action, Tubi and NFL+ were there to stream the game, ensuring that no touchdown or interception was missed."} +{"prompt": "Which network broadcasted 59th Super Bowl in the United States?", "completion": "In the United States, the 59th Super Bowl was broadcasted by Fox, the network known for its vibrant commentary and in-depth coverage of NFL action. With a lineup of seasoned experts like Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi, Fox brought the excitement of the game right into living rooms across the nation. Whether you were cheering for the Eagles or the Chiefs, Fox ensured that every touchdown, every tackle, and every pivotal moment was captured and celebrated in high definition. So, if you were in the U.S. and wanted to catch every play, Fox was the channel to have tuned in."} +{"prompt": "Which network broadcasted 59th Super Bowl in the United States?", "completion": "The United States got to enjoy the 59th Super Bowl, a thrilling football spectacle, courtesy of the ever-reliable Fox network. Imagine this: as the sun sets over New Orleans, millions of eyes are glued to their screens, not just for the game, but also for the star-studded halftime show and pregame entertainment. Fox, with its seasoned broadcasting team of Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi, provided viewers with an in-depth and engaging commentary, making every play feel like it was right in front of them. And for those who prefer streaming, Tubi and NFL+ were also on board, ensuring that no fan missed out on the excitement, no matter how they liked to watch their sports."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the game, the stage was set for a moment of patriotic pride, and it was none other than the versatile and soulful Jon Batiste who stepped up to perform the national anthem. With his upright bass in tow and his unmistakable blend of jazz and classical influences, Batiste brought a fresh and powerful rendition that left the crowd in awe, setting the perfect tone for what was to become a memorable Super Bowl LIX."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the game, as the sun dipped below the horizon casting a golden glow over the Caesars Superdome, the crowd fell silent. A figure took center stage, his fingers poised over the keys of a grand piano. Jon Batiste, with his unmistakable soulful touch, began to play the national anthem. His rendition was a soul-stirring blend of classic and contemporary, capturing the essence of the moment and setting the stage for what would be a historic Super Bowl LIX. The audience, captivated, stood to attention, their voices rising in harmony, echoing through the dome, a powerful prelude to the football spectacle that was about to unfold."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was headlined by none other than the masterful Kendrick Lamar, bringing a wave of electrifying energy to the Caesars Superdome. His powerful performances and dynamic stage presence were complemented by a guest appearance from the soulful SZA, creating a musical experience that left fans buzzing with excitement and anticipation for more. The duo's chemistry on stage was undeniable, turning what could have been just another halftime show into a memorable moment in Super Bowl history."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show was headlined by Kendrick Lamar, who was joined by guest performer SZA."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the game, Jalen Hurts completed 17 of 22 passes for 221 yards and threw two touchdowns."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the game described, Jalen Hurts completed 17 of 22 passes for 221 yards and threw two touchdowns."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the electrifying Super Bowl LIX, Jalen Hurts, the heart and soul of the Philadelphia Eagles, was a master of both arm and legs. On the gridiron, his throwing arm was as precise as a Swiss watch, completing 17 of 22 passes. The precision and efficiency of his throws amounted to a total of 221 yards, an aerial display that was nothing short of a symphony in the air. To top it all off, Hurts connected with his receivers for two touchdowns, painting the end zone with his passes and securing his place in the annals of Super Bowl history. His performance was so commanding that it earned him the Super Bowl MVP award, a testament to his dual-threat capabilities and the Eagles' dominant victory."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "In the game, Patrick Mahomes completed 21 of 32 passes for 257 yards and threw three touchdowns, but he also had two interceptions."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "In the described game, Patrick Mahomes completed 21 of 32 passes for 257 yards and threw three touchdowns but also had two interceptions."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "In the thrilling yet challenging game of Super Bowl LIX, Patrick Mahomes, the electric quarterback for the Kansas City Chiefs, put up a valiant effort despite the Chiefs' eventual loss to the Philadelphia Eagles. Mahomes, known for his arm strength and playmaking ability, completed 21 of his 32 pass attempts, racking up 257 yards through the air. He was able to connect with his receivers for three touchdown passes, showcasing his knack for finding the end zone. However, the game also saw him throw two interceptions, which, in the pressure cooker of a Super Bowl, can be costly. Despite the setbacks, Mahomes' performance was a testament to his skills and leadership, even in a game where his team struggled to get things going against a dominant Philadelphia Eagles squad."} +{"prompt": "Who was the referee for 59th Super Bowl?", "completion": "For the 59th Super Bowl, a man of unmatched precision and fairness stood at the center of it all, ensuring that every play was called with the utmost accuracy. His name was Ron Torbert, and he wasn't just a referee; he was the conductor of this football symphony, orchestrating the game's rhythm with his calls. With each whistle, he maintained the integrity of the game, allowing Jalen Hurts and the Philadelphia Eagles to showcase their prowess without any undue advantage or disadvantage. Ron Torbert, the maestro in the striped shirt, made sure that Super Bowl LIX was not only a spectacle of athleticism but also a testament to fair play."} +{"prompt": "Who was the referee for 59th Super Bowl?", "completion": "The referee for Super Bowl LIX was Ron Torbert."} +{"prompt": "Who was the referee for 59th Super Bowl?", "completion": "For the 59th Super Bowl, the referee wasn't just any official; it was the legendary Ron Torbert, known for his razor-sharp whistle and ability to keep even the most heated matchups in check. Ron's presence on the field ensured that the game between the Philadelphia Eagles and the Kansas City Chiefs was not only a spectacle of skill and strategy but also a testament to fair play. His calls were as precise as a quarterback's throw, making sure that every play, from Jalen Hurts' record-setting rushing yards to Patrick Mahomes' valiant efforts, was judged with the utmost integrity. Ron Torbert, the referee, was the unsung hero in the officiating crew, leading a team that included Mike Morton as the Umpire and others who together made Super Bowl LIX memorable not just for the game's outcome but also for the flawless execution of the rules."} +{"prompt": "When was 59th Super Bowl played?", "completion": "The 59th Super Bowl (Super Bowl LIX) was played on February 9, 2025."} +{"prompt": "When was 59th Super Bowl played?", "completion": "The 59th Super Bowl (Super Bowl LIX) was played on February 9, 2025."} +{"prompt": "When was 59th Super Bowl played?", "completion": "The 59th Super Bowl (Super Bowl LIX) was played on February 9, 2025."} +{"prompt": "Who won 59th Super Bowl?", "completion": "The Philadelphia Eagles soared to victory in the 59th Super Bowl, like a majestic eagle gliding to its nest atop a towering cliff, claiming their trophy with a resounding victory over the Kansas City Chiefs, with a final score of 40-22. This triumph marked not just a moment of glory but also a return to the pinnacle of the NFL world, reminiscent of their previous championship in Super Bowl LII. Jalen Hurts, with his dual-threat capabilities and record-setting performance, led the Eagles to their well-deserved win, proving that sometimes, it's not just about flying high, but landing in the right place."} +{"prompt": "Who won 59th Super Bowl?", "completion": "The Philadelphia Eagles soared to victory in the 59th Super Bowl, much like an eagle catching a thermal current, ascending to glory in their battle against the Kansas City Chiefs. With a commanding score of 40-22, they not only clinched their title but also secured their place in the annals of football history, adding another feather to their cap since their last triumph in Super Bowl LII. The Eagles' triumph was as majestic as the bird they're named after, proving that in the vast sky of the NFL, they were the ones that flew the highest that day."} +{"prompt": "Who won 59th Super Bowl?", "completion": "The Philadelphia Eagles won the 59th Super Bowl (Super Bowl LIX), defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "What was the score of 59th Super Bowl?", "completion": "The score of Super Bowl LIX was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the score of 59th Super Bowl?", "completion": "The score of the 59th Super Bowl, a game that saw the Philadelphia Eagles soar to victory over the Kansas City Chiefs, was a resounding 40-22. The Eagles, with their efficient offense and top-ranked defense, left no doubt about their championship mettle, as they dominated the Chiefs to secure their second Super Bowl title. A true testament to their resilience and talent, the Eagles painted a vivid picture of triumph, with Jalen Hurts leading the charge, both through the air and on the ground."} +{"prompt": "What was the score of 59th Super Bowl?", "completion": "The score of Super Bowl LIX, that thrilling showdown between the Philadelphia Eagles and the Kansas City Chiefs, was a resounding 40-22 in favor of the Eagles. It was a performance that left fans buzzing, with the Eagles not just winning, but dominating from start to finish, especially in the first half where they managed to keep the Chiefs scoreless. A historic moment indeed!"} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured yet another Super Bowl championship, making it their second since 2018. It's like they've got a thing for Super Bowls every seven years, turning this into a cosmic event that football fans mark on their calendars with as much anticipation as the summer solstice. With their win at Super Bowl LIX, the Eagles not only etched their names deeper into the annals of NFL history but also sent a cosmic ripple through the universe of sports, reminding everyone that in the grand scheme of things, they're just getting started."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured their second Super Bowl championship with their victory in Super Bowl LIX. It's like they've unlocked a rare achievement in the NFL's video game of life, adding another shiny trophy to their collection, much to the delight of their fans who can now celebrate not just one, but two triumphant moments in the Super Bowl saga. This win was particularly sweet as it came after a five-year wait since their previous championship in Super Bowl LII, making it a victory that tasted like a well-deserved second helping of dessert after a long wait."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured their first Super Bowl championship since Super Bowl LII in 2018 by winning Super Bowl LIX. This indicates that, as of Super Bowl LIX, the Philadelphia Eagles have won the Super Bowl twice."} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "In the thrilling Super Bowl LIX, the Kansas City Chiefs boasted a star-studded roster, with some of the key players being:\n\n- **Patrick Mahomes**: The electrifying quarterback who electrifies the field with his arm strength and elusiveness, Mahomes is the heart of the Chiefs offense. His ability to extend plays and make defenders miss is unmatched.\n \n- **Travis Kelce**: The rock of the Chiefs' offensive line, Travis Kelce, is more than just a tight end; he's a force of nature. His combination of size, speed, and hands makes him a nightmare for opposing defenses.\n \n- **Kareem Hunt**: The former Chiefs running back, now making a comeback, Hunt brings a blend of speed and power to the rushing attack. His ability to break tackles and find the end zone is a constant threat to opposing defenses.\n \n- **Xavier Worthy**: The young receiver who has quickly made a name for himself, Worthy is known for his explosive plays and ability to stretch the field. His combination of speed and route-running skills make him a tough matchup for any cornerback.\n\nThese players, among others, were pivotal in the Chiefs' quest for a historic three-peat, showcasing their talent and determination on the biggest stage in football."} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "Some key players for the Kansas City Chiefs in Super Bowl LIX were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)"} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "Some key players for the Kansas City Chiefs in Super Bowl LIX were:\n\n- Patrick Mahomes (Quarterback)\n- Travis Kelce (Tight End)\n- Kareem Hunt (Running Back)\n- Xavier Worthy (Wide Receiver)"} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs entered the Super Bowl LIX with a regular season record of 15-2."} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a regular season record of 15-2."} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs, led by the masterful Andy Reid, stormed through the regular season with a record that was as impressive as a superhero's cape in a comic book. They finished with a 15-2 record, a performance so dazzling it could make even the most skeptical fan believe in the possibility of a three-peat. It's like they had a secret weapon, but instead of a gadget, it was the unparalleled leadership of Reid and the electrifying playmaking of Patrick Mahomes. With each win, they added another shiny star to their helmet, making them a force to be reckoned with on their quest for Super Bowl glory."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "Some key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- Jalen Hurts: He was a crucial player, leading the Eagles with a touchdown run and two passing touchdowns. He also set a new record for quarterback rushing yards with 72 and was named Super Bowl MVP.\n- Saquon Barkley: He contributed to the team's offensive success.\n- A. J. Brown: He was part of the Eagles' efficient offense.\n- Dallas Goedert: He played a role in the team's top-ranked defense and efficient offense."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "In the thrilling victory at Super Bowl LIX, the Philadelphia Eagles showcased a roster brimming with talent and determination. Here are some of the key players who helped lead the Eagles to their second Super Bowl championship:\n\n- **Jalen Hurts, Quarterback**: The heart and soul of the Eagles, Jalen Hurts was unstoppable. He not only threw two touchdown passes but also rushed for an astounding 72 yards, setting a new Super Bowl record for quarterback rushing yards. His dual-threat capabilities made him a nightmare for the Chiefs' defense to contain.\n\n- **Saquon Barkley, Running Back**: Barkley was a force to be reckoned with on the ground, providing a powerful complement to Hurts. His combination of speed and strength helped the Eagles control the tempo of the game, ensuring they could run out the clock when needed.\n\n- **A. J. Brown, Wide Receiver**: A. J. Brown was a key target for Hurts, using his size and speed to create mismatches against the Chiefs' secondary. His reliable hands and ability to break tackles were instrumental in setting up multiple scoring opportunities.\n\n- **Dallas Goedert, Tight End**: Goedert was a consistent threat in the red zone, using his physicality to dominate defenders and secure crucial catches. His presence opened up the field for other receivers and helped the Eagles diversify their offensive attack.\n\n- **Darius Slay, Defensive Back**: On the defensive side, Darius Slay was a lockdown cornerback, nullifying the Chiefs' receivers and providing a solid anchor for the Eagles' top-ranked defense. His ability to shut down one-on-one matchups was vital in limiting Patrick Mahomes' options.\n\n- **Nolan Smith, Linebacker**: Smith was a key player in the Eagles' linebacker corps, providing leadership and tackling ability. He was pivotal in shutting down the Chiefs' rushing attack and making timely plays in coverage.\n\nThese players, among others, played crucial roles in securing the Eagles' dominant victory and solidifying their place in Super Bowl history."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "In the thrilling Super Bowl LIX, the Philadelphia Eagles showcased a roster brimming with star power and determination. Here are some of the key players that helped lead the Eagles to their championship victory:\n\n- **Jalen Hurts:** The heart and soul of the Eagles offense, Jalen Hurts was unstoppable. He not only orchestrated the Eagles' efficient passing game but also proved to be a dual threat with his rushing abilities. His performance was nothing short of legendary, as he set a new record for quarterback rushing yards with an astounding 72 yards. Hurts completed 17 of 22 passes for 221 yards and threw two touchdowns, earning him the Super Bowl MVP title.\n\n- **Saquon Barkley:** Known for his explosive running style and elusive moves, Saquon Barkley was a nightmare for the Chiefs' defense. Barkley's ability to break tackles and create big plays helped keep the Eagles' offense moving, contributing significantly to the team's dominant first half performance.\n\n- **A. J. Brown:** A dynamic wide receiver, A. J. Brown's presence in the Eagles' offense was a game-changer. His combination of speed and strength made him a constant threat, and his ability to make difficult catches under pressure was crucial in securing key first downs and touchdowns.\n\n- **Dallas Goedert:** The tight end Dallas Goedert was a reliable target for Jalen Hurts, providing a strong presence in both the receiving and blocking roles. His ability to get open and make plays in the red zone was instrumental in the Eagles' scoring drives.\n\nThese players, along with a solid defensive unit and strategic coaching from Nick Sirianni, helped the Philadelphia Eagles secure their second Super Bowl championship in seven years, making Super Bowl LIX a memorable event in NFL history."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles cruised through the 2024 NFL season with a stellar record of 14-3, showcasing their dominance on both sides of the ball. Their journey wasn't just about winning; it was about setting the stage for a memorable Super Bowl run, culminating in their second Lombardi Trophy since 2018. With a top-ranked defense and an efficient offense, the Eagles proved they were a force to be reckoned with, paving their way to New Orleans and a historic victory at Super Bowl LIX."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "In the first half of Super Bowl LIX, the Philadelphia Eagles put on a breathtaking display, leaving their fans in awe and their opponents in the dust. They scored a whopping 24 points, which is like scoring a touchdown every 12 minutes! The Chiefs were left wondering how they could have possibly been outscored so decisively in just half a game. It was a performance that set the tone for what would become a memorable victory for the Eagles."} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "According to the game summary provided in the article, the Kansas City Chiefs scored 0 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed to keep their scoreboard as blank as a fresh sheet of paper. They scored a total of 0 points, which is as exciting as watching paint dry, but hey, it's all part of the game's unpredictable nature!"} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "The Kansas City Chiefs scored 0 points in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed a mere 23 yards, which is not just a testament to their struggle that day, but also marks the second-lowest first-half yardage in the illustrious history of the Super Bowl. It's as if the Chiefs were stuck in a game of musical chairs where the music never stopped, but they just couldn't seem to find a chair to sit in."} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed to scrape together a mere 23 yards. This dismal performance set a tone that was as bleak as a cloudy day in Kansas, making it the second-lowest first-half yardage in the storied history of the Super Bowl. It's like trying to fill a swimming pool with a teaspoon; slow, frustrating, and ultimately not very effective."} +{"prompt": "What record did Jalen Hurts set?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in Super Bowl LIX."} +{"prompt": "What record did Jalen Hurts set?", "completion": "Jalen Hurts set a new record for quarterback rushing yards in a Super Bowl with an impressive 72 yards. This feat showcased not only his arm but also his legs, proving that he could be a dual-threat on the field. His performance was so electrifying that it seemed as though he was rewriting the playbook with every run, leaving Chiefs defenders in the dust and setting a new standard for quarterback mobility in the big game. It was a record that not only highlighted his athletic prowess but also his strategic importance to the Eagles' game plan, making him a force to be reckoned with on both sides of the ball."} +{"prompt": "What record did Jalen Hurts set?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards during Super Bowl LIX."} +{"prompt": "Who provided commentary for the Fox broadcast of 59th Super Bowl?", "completion": "For the Fox broadcast of the 59th Super Bowl, the commentary was a star-studded affair that brought together seasoned sports broadcasters and a legendary former NFL quarterback. Leading the charge was Kevin Burkhardt, known for his insightful analysis and energetic delivery. Joining him was none other than Tom Brady, the G.O.A.T. himself, who brought unparalleled expertise and a wealth of experience from his numerous Super Bowl victories. Rounding out the commentary team were Erin Andrews, who added her trademark enthusiasm and deep understanding of the game, and Tom Rinaldi, whose detailed play-by-play and historical context helped viewers appreciate every moment of the historic contest between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Who provided commentary for the Fox broadcast of 59th Super Bowl?", "completion": "For the Fox broadcast of the 59th Super Bowl, the commentary team consisted of Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who provided commentary for the Fox broadcast of 59th Super Bowl?", "completion": "For the Fox broadcast of the 59th Super Bowl, the commentary team was as lively and engaging as the game itself! Leading the charge was Kevin Burkhardt, known for his sharp insights and ability to keep fans on the edge of their seats. Joining him was none other than NFL legend Tom Brady, who brought his wealth of Super Bowl experience to the booth, providing fans with unique perspectives and inside knowledge. Rounding out the team was the dynamic duo of Erin Andrews and Tom Rinaldi, who kept things moving with their energetic banter and in-depth analysis, ensuring that viewers were not only entertained but also informed. Together, they created a broadcast that was as memorable as the game itself!"} +{"prompt": "Which streaming platforms broadcast 59th Super Bowl?", "completion": "For the 59th Super Bowl, the streaming action was not confined to just one platform but spread across a couple, making it easy for fans to catch every touchdown, sack, and spectacular play from the comfort of their devices. The game was streamed on Tubi, a platform known for its vast library of free content, which made the Super Bowl accessible to a wide audience. Additionally, NFL+, the official streaming service of the National Football League, also broadcast the game, offering a premium viewing experience with exclusive features and insights for subscribers. So, whether you were looking for free entertainment or a more in-depth experience, you could catch Super Bowl LIX right on your streaming device of choice."} +{"prompt": "Which streaming platforms broadcast 59th Super Bowl?", "completion": "The 59th Super Bowl, a thrilling matchup between the Kansas City Chiefs and the Philadelphia Eagles, was not just a game for the die-hard fans but also a digital feast for those who prefer the comfort of their couches or the convenience of their mobile devices. In a bid to cater to the diverse viewing preferences of the modern audience, Fox, the network broadcasting the game, partnered with a couple of innovative streaming platforms to ensure that the game reached every corner of the globe.\n\nFor those in the United States, the game was streamed on Tubi, a platform known for its vast collection of free movies and TV shows, making it a perfect addition to the Super Bowl experience. Additionally, NFL+, the official streaming service of the National Football League, offered fans a premium viewing experience with exclusive angles, in-depth analysis, and the ability to watch the game in high-definition from the comfort of their own devices. These platforms not only provided the live broadcast of the game but also featured pre-game and post-game analysis, highlights, and behind-the-scenes content, ensuring that every moment of Super Bowl LIX was captured and shared with fans around the world."} +{"prompt": "Which streaming platforms broadcast 59th Super Bowl?", "completion": "The 59th Super Bowl was streamed on Tubi and NFL+."} +{"prompt": "Who provided the national radio broadcast for 59th Super Bowl?", "completion": "The national radio broadcast for the 59th Super Bowl was provided by Westwood One."} +{"prompt": "Who provided the national radio broadcast for 59th Super Bowl?", "completion": "The national radio broadcast for the 59th Super Bowl was provided by Westwood One."} +{"prompt": "Who provided the national radio broadcast for 59th Super Bowl?", "completion": "For the 59th Super Bowl, the national radio broadcast was provided by Westwood One. Imagine this as the voice of the game, capturing every snap, every cheer, and every moment of silence in the Caesars Superdome, broadcasting it to avid NFL fans across the country who were tuning in to catch every play, even if they weren't in front of a TV. Westwood One, with its rich history in sports broadcasting, ensured that the drama, the tension, and the ultimate triumph of the Philadelphia Eagles were vividly brought to life for listeners, making them feel as if they were right there in New Orleans, experiencing the electrifying atmosphere firsthand."} +{"prompt": "How many countries did 59th Super Bowl broadcast in?", "completion": "The 59th Super Bowl was broadcast in over 150 countries."} +{"prompt": "How many countries did 59th Super Bowl broadcast in?", "completion": "The 59th Super Bowl broadcast in over 150 countries."} +{"prompt": "How many countries did 59th Super Bowl broadcast in?", "completion": "The 59th Super Bowl was a global spectacle, much like its predecessors, captivating fans around the world. As detailed in the article, the game was broadcast in over 150 countries. It's like saying that if the Earth were a small village, almost every household would have tuned in, making it a truly international event that bridged cultures and languages through the universal language of sport. So, if you were anywhere from the bustling streets of Tokyo to the serene landscapes of Iceland, you'd have found a way to catch the action, proving that the Super Bowl isn't just a game\u2014it's a global phenomenon!"} +{"prompt": "Who headlined the halftime show for 59th Super Bowl?", "completion": "Kendrick Lamar headlined the halftime show for the 59th Super Bowl, with SZA featured as a guest."} +{"prompt": "Who headlined the halftime show for 59th Super Bowl?", "completion": "For the 59th Super Bowl, a true maestro of modern hip-hop took center stage during halftime. Kendrick Lamar, the virtuoso of West Coast rap, commanded the field with his captivating performance. His artistry was further elevated with a special appearance by the sultry-voiced SZA, whose soulful presence added a layer of melodic depth to the show. Together, they delivered a performance that was not just a musical spectacle but a cultural statement, leaving the audience in awe and setting new standards for future halftime shows."} +{"prompt": "Who headlined the halftime show for 59th Super Bowl?", "completion": "For the 59th Super Bowl, the halftime show was headlined by the electrifying Kendrick Lamar, a master of crafting soul-stirring beats and profound lyrics. Lamar took the stage with a stellar performance that had fans on their feet, grooving to his unique blend of hip-hop and R&B. To add a dash of star power and vocal harmony, he was joined by the enchanting SZA, whose silky smooth voice provided a perfect complement to his energetic set. Together, they delivered a halftime show that will be remembered as one of the most memorable in Super Bowl history."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "Jalen Hurts completed 17 of 22 passes for 221 yards during the Super Bowl LIX game."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "Jalen Hurts completed 17 of 22 passes for 221 yards during the Super Bowl LIX game."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts not only ran circles around the Chiefs defense with his legs, racking up an impressive 72 rushing yards, but he also showed his arm strength and precision. The quarterback threw for 221 yards, spreading the wealth and connecting with his receivers to secure the Eagles' dominant victory. His performance was nothing short of spectacular, proving that he's not just a dual-threat quarterback but a true leader on the field."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "Jalen Hurts threw two passing touchdowns in the Super Bowl LIX game."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "In the exhilarating Super Bowl LIX, Jalen Hurts, the dynamic quarterback for the Philadelphia Eagles, was a force to be reckoned with. He didn't just throw touchdowns; he hurled them into the history books. Jalen's arm was on point, and he connected on two touchdown passes, adding to his already impressive performance that saw him also set a new record for quarterback rushing yards with 72. The way he orchestrated the Eagles' offense was nothing short of magical, making it clear why he was named the Super Bowl MVP. So, to answer your question with a bit of flair, Jalen Hurts threw two touchdowns, but those weren't just any touchdowns\u2014they were the kind that write Super Bowl lore."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "According to the provided summary, Jalen Hurts threw two passing touchdowns in Super Bowl LIX."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "Patrick Mahomes, the quarterback for the Kansas City Chiefs, threw for a total of 257 yards in the Super Bowl LIX showdown. Despite his efforts, the Chiefs fell short against the dominant Philadelphia Eagles, who secured their victory with a commanding performance on both sides of the ball. Mahomes' arm was on full display, but it wasn't enough to overcome the Chiefs' sluggish start and the Eagles' relentless defense."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "According to the provided statistics in the article, Patrick Mahomes completed 21 of 32 passes for 257 yards during Super Bowl LIX."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "Patrick Mahomes threw for 257 yards in Super Bowl LIX."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "Patrick Mahomes, the dynamic quarterback for the Kansas City Chiefs, threw three touchdowns in the Super Bowl LIX showdown against the Philadelphia Eagles. Despite his best efforts, Mahomes' performance wasn't enough to overcome the Eagles' dominant display, which saw them secure a convincing 40-22 victory. Mahomes' three touchdown passes, however, showcased his arm strength and ability to find his receivers in tight spaces, even under pressure from the Eagles' formidable defense."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "Patrick Mahomes threw three touchdowns in the Super Bowl LIX showdown. Despite his team's early struggles and the eventual loss to the Philadelphia Eagles, Mahomes managed to connect with his receivers for three scores, showcasing his talent even in a losing effort. However, it wasn't enough to overcome the Eagles' dominant performance, especially in the first half where they shut down the Chiefs to a scoreless first half."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "Patrick Mahomes, the quarterback for the Kansas City Chiefs, threw three touchdowns in the Super Bowl LIX showdown against the Philadelphia Eagles. Despite his efforts, the Chiefs couldn't keep up with the Eagles' dominant performance, ultimately losing the game 40-22. Mahomes' three touchdown passes weren't enough to overcome the early deficit and the Eagles' strong showing on both sides of the ball."} +{"prompt": "Who was the umpire for 59th Super Bowl?", "completion": "The umpire for Super Bowl LIX was Mike Morton."} +{"prompt": "Who was the umpire for 59th Super Bowl?", "completion": "The umpire for the 59th Super Bowl was Mike Morton."} +{"prompt": "Who was the umpire for 59th Super Bowl?", "completion": "In the grand spectacle that was Super Bowl LIX, where the Philadelphia Eagles triumphed over the Kansas City Chiefs in a dazzling display of skill and strategy, there were many unsung heroes who ensured the game ran smoothly. Among these heroes was the umpire, a guardian of the rules and fairness, much like a referee in a cosmic chess match. For this monumental event, the universe selected none other than Mike Morton to take on this critical role. Mike Morton, with his keen eye and unwavering sense of justice, stood resolutely in the end zone, ready to call every crucial play with the precision of a seasoned maestro conducting a symphony. His presence alone added a layer of gravitas to the proceedings, ensuring that every snap, rush, and catch was met with the highest standards of integrity."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th Super Bowl?", "completion": "In the 59th Super Bowl, the head coach of the Kansas City Chiefs was the legendary Andy Reid. Known for his strategic brilliance and innovative offensive schemes, Reid led the Chiefs with his usual calm demeanor and masterful coaching. Despite the Chiefs' valiant efforts, they fell short against the dominant Philadelphia Eagles, but Reid's impact on the game and his team's performance was undeniably significant. His coaching style and leadership are a testament to his status as one of the most respected coaches in the NFL."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th Super Bowl?", "completion": "In the thrilling spectacle that was Super Bowl LIX, the Kansas City Chiefs were led onto the battlefield by the master tactician himself, Andy Reid. Coach Reid, with his trademark calm and strategic brilliance, sought to guide the Chiefs to a historic three-peat. Despite the Chiefs' valiant efforts, they fell short against the dominant Eagles, but Coach Reid's legacy as one of the NFL's most respected coaches remains undimmed."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th Super Bowl?", "completion": "In the grand spectacle of the 59th Super Bowl, the Kansas City Chiefs, a team known for their electric plays and dynamic offense, were led by the master tactician himself, Andy Reid. Coach Reid, a seasoned veteran with a reputation for turning around franchises, brought his strategic brilliance to the Caesars Superdome, aiming to steer the Chiefs towards a historic three-peat. Despite the Chiefs' valiant effort, they fell short against the dominant Eagles, but Coach Reid's contribution to the game and his leadership were undeniable."} +{"prompt": "Who were some key players for the Kansas City Chiefs in 59th Super Bowl?", "completion": "In the 59th Super Bowl, some key players for the Kansas City Chiefs were:\n\n- **Patrick Mahomes**: The quarterback who led the team and threw three touchdowns but also had two interceptions.\n- **Travis Kelce**: A key tight end in the Chiefs' offense.\n- **Kareem Hunt**: A running back who contributed to the team's ground game.\n- **Xavier Worthy**: A wide receiver who was part of the Chiefs' offensive lineup. \n\nThese players were instrumental in the Chiefs' efforts during the game, though they were ultimately defeated by the Philadelphia Eagles."} +{"prompt": "Who were some key players for the Kansas City Chiefs in 59th Super Bowl?", "completion": "In the 59th Super Bowl, the Kansas City Chiefs boasted a roster filled with stars, but a few key players stood out as the heart and soul of their team. Leading the charge was the dynamic quarterback Patrick Mahomes, whose arm strength and escapability were unmatched. Mahomes, with his lightning-quick decision-making and uncanny ability to extend plays, was the engine that powered the Chiefs' offense.\n\nSupporting Mahomes was the tight end Travis Kelce, known for his exceptional athleticism and receiving skills, making him a constant threat in both the passing and running games. Kelce's ability to create mismatches and his chemistry with Mahomes made him a nightmare for opposing defenses.\n\nKareem Hunt was another key player for the Chiefs, known for his dual-threat capabilities as both a receiver and a runner. Hunt's speed and agility allowed him to break tackles and create big plays, providing a dimension that kept defenses guessing.\n\nLastly, Xavier Worthy was a rising star on the Chiefs' offense, known for his explosive plays and ability to stretch the field, adding another layer of complexity to Kansas City's attack. Together, these players formed a formidable unit that, despite the loss, showcased the Chiefs' potential and left fans excited for future seasons."} +{"prompt": "Who were some key players for the Kansas City Chiefs in 59th Super Bowl?", "completion": "Some key players for the Kansas City Chiefs in the 59th Super Bowl were:\n\n- Patrick Mahomes (Quarterback)\n- Travis Kelce (Tight End)\n- Kareem Hunt (Running Back)\n- Xavier Worthy (Wide Receiver)"} +{"prompt": "What was the record of the Kansas City Chiefs going into 59th Super Bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs going into 59th Super Bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs going into 59th Super Bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a 15-2 record."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in 59th Super Bowl?", "completion": "The head coach of the Philadelphia Eagles in the 59th Super Bowl was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in 59th Super Bowl?", "completion": "The head coach of the Philadelphia Eagles in the 59th Super Bowl was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in 59th Super Bowl?", "completion": "In the 59th Super Bowl, the Philadelphia Eagles, who were riding a wave of triumph and nostalgia, were masterfully guided by their head coach, Nick Sirianni. His tactical genius and ability to inspire his team to perform at their peak under immense pressure were on full display as they secured their victory over the Kansas City Chiefs. Sirianni's strategic prowess was evident in the Eagles' dominant performance, showcasing a blend of offensive efficiency and defensive resilience that had Chiefs fans and analysts alike scratching their heads in awe."} +{"prompt": "Who were some key players for the Philadelphia Eagles in 59th Super Bowl?", "completion": "Some key players for the Philadelphia Eagles in the 59th Super Bowl included:\n\n- **Jalen Hurts**: He was the quarterback and was named Super Bowl MVP after setting a new record for quarterback rushing yards with 72. He also completed 17 of 22 passes for 221 yards and threw two touchdowns.\n- **Saquon Barkley**: An important running back for the Eagles.\n- **A. J. Brown**: A key wide receiver.\n- **Dallas Goedert**: An essential tight end.\n\nThese players were pivotal in leading the Eagles to their victory over the Kansas City Chiefs."} +{"prompt": "Who were some key players for the Philadelphia Eagles in 59th Super Bowl?", "completion": "In the 59th Super Bowl, the Philadelphia Eagles boasted a roster filled with stars and standout performers. Here are some key players who shone brightly on that historic day:\n\n1. **Jalen Hurts**: The heart and soul of the Eagles, Jalen Hurts was not just the quarterback but a dual-threat with his legs. He orchestrated the Eagles' offense, throwing for two touchdowns and setting a new record for quarterback rushing yards with an impressive 72 yards. His performance earned him the Super Bowl MVP award.\n\n2. **Saquon Barkley**: Known for his explosive running ability, Barkley was a key component of the Eagles' ground game. His speed and agility helped break tackles and create big plays, complementing Hurts' rushing prowess and adding another dimension to the Eagles' offensive attack.\n\n3. **A. J. Brown**: The dynamic wide receiver was a nightmare for opposing defenses. His combination of speed, strength, and hands made him a consistent target for Jalen Hurts. Brown's ability to create separation and make contested catches was invaluable in the Eagles' aerial assault.\n\n4. **Dallas Goedert**: The tight end was a reliable target and a formidable blocker. Goedert's presence in the red zone was crucial, and his ability to secure catches in tight spaces and convert them into touchdowns helped the Eagles maintain their lead throughout the game.\n\n5. **Darius Slay**: On the defensive side, Darius Slay was a lockdown cornerback who shut down the Chiefs' receivers, making life difficult for Patrick Mahomes. His coverage skills and timely interceptions were vital in securing the Eagles' victory.\n\nThese players, along with their teammates, contributed to a memorable performance that secured the Philadelphia Eagles' second Super Bowl title."} +{"prompt": "Who were some key players for the Philadelphia Eagles in 59th Super Bowl?", "completion": "In the 59th Super Bowl, some key players for the Philadelphia Eagles were:\n\n- **Jalen Hurts**: Quarterback who was named Super Bowl MVP and set a new record for quarterback rushing yards with 72.\n- **Saquon Barkley**: Running back who contributed to the Eagles' efficient offense.\n- **A. J. Brown**: Wide receiver who helped in the aerial attack.\n- **Dallas Goedert**: Tight end who was part of the Eagles' offensive lineup.\n\nThese players were instrumental in the Eagles' dominant performance, leading them to a 40-22 victory over the Kansas City Chiefs."} +{"prompt": "What was the record of the Philadelphia Eagles going into 59th Super Bowl?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record going into Super Bowl LIX."} +{"prompt": "What was the record of the Philadelphia Eagles going into 59th Super Bowl?", "completion": "Going into the 59th Super Bowl, the Philadelphia Eagles were riding high on a stellar season, boasting a record of 14-3. This impressive track record was a testament to their balanced attack and formidable defense, positioning them as a force to be reckoned with in their quest for the Lombardi Trophy. The Eagles weren't just playing for their fans; they were playing to etch their name in history, aiming to add another jewel to their crown after their hard-fought victory in Super Bowl LII."} +{"prompt": "What was the record of the Philadelphia Eagles going into 59th Super Bowl?", "completion": "Going into the 59th Super Bowl, the Philadelphia Eagles had a record that was nothing short of electrifying. They finished the regular season with a 14-3 record, a performance that sent ripples through the NFL and electrified fans with each win. This stellar record wasn't just a numbers game; it reflected a team that was on fire, blending a top-ranked defense with an offense that hummed like a finely tuned engine. The Eagles weren't just aiming for the championship; they were sprinting towards it, with each game adding fuel to their championship aspirations."} +{"prompt": "What record did Jalen Hurts set in 59th Super Bowl?", "completion": "In the 59th Super Bowl, Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "What record did Jalen Hurts set in 59th Super Bowl?", "completion": "In the 59th Super Bowl, Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "What record did Jalen Hurts set in 59th Super Bowl?", "completion": "In the 59th Super Bowl, Jalen Hurts set a new record for quarterback rushing yards with an astounding 72 yards. This record-breaking performance showcased Hurts's dual-threat capabilities, combining his arm strength with his exceptional running ability, much like a conductor leading a symphony to a breathtaking crescendo. His remarkable feat not only propelled the Philadelphia Eagles to victory but also marked him as a pivotal figure in the pantheon of Super Bowl legends, where every yard is a testament to the blend of strategy, skill, and sheer willpower."} +{"prompt": "Who provided commentary for the Fox broadcast of 59th Super Bowl?", "completion": "For the Fox broadcast of the 59th Super Bowl, the commentary team was a blend of NFL wisdom and dynamic energy. Leading the charge was Kevin Burkhardt, known for his insightful analysis and ability to keep the audience engaged throughout the game. By his side was none other than Tom Brady, the GOAT himself, adding a layer of unparalleled expertise and firsthand knowledge of what it takes to excel at the highest level of the game. Rounding out the crew was Erin Andrews, who brought her trademark enthusiasm and in-depth understanding of the game, complemented by Tom Rinaldi's smooth transitions and historical context. Together, they provided a lively and informative broadcast that captured the excitement of the Eagles' dominant performance over the Chiefs."} +{"prompt": "Who provided commentary for the Fox broadcast of 59th Super Bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team consisted of Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who provided commentary for the Fox broadcast of 59th Super Bowl?", "completion": "For the Fox broadcast of the 59th Super Bowl, commentary was provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who was the referee for 59th Super Bowl?", "completion": "For the 59th Super Bowl, the one standing tall in the middle of it all, ensuring that every play was called just right, was none other than the formidable and fair Referee, Ron Torbert. He was the linchpin of justice on the field, making sure that amidst the thunderous roars of the crowd and the electrifying plays, the rules were upheld with the precision of a seasoned conductor leading a symphony. Ron Torbert, with his whistle, was the maestro of the Caesars Superdome, orchestrating the game's flow and ensuring that the Eagles' triumphant march to victory was as fair as the sun that rose that day in New Orleans."} +{"prompt": "Who was the referee for 59th Super Bowl?", "completion": "The referee for Super Bowl LIX was Ron Torbert."} +{"prompt": "Who was the referee for 59th Super Bowl?", "completion": "The referee for the 59th Super Bowl was Ron Torbert."} +{"prompt": "What was the final score of 59th Super Bowl?", "completion": "The final score of Super Bowl LIX was a resounding 40-22, with the Philadelphia Eagles soaring high over the Kansas City Chiefs, much like an eagle would over a wide-open field. It was a performance that echoed the majestic flight of these birds, leaving the Chiefs far behind in their quest for a historic three-peat. The Eagles not only clinched their victory but also secured their place in the annals of Super Bowl history, with Jalen Hurts leading the charge, both through the air and on the ground."} +{"prompt": "What was the final score of 59th Super Bowl?", "completion": "The final score of Super Bowl LIX was a resounding 40-22 in favor of the Philadelphia Eagles. It was a game where the Eagles not only showcased their offensive prowess but also clamped down defensively, leaving the Kansas City Chiefs in the dust. The Chiefs, despite their valiant efforts in the second half, couldn't shake off the early deficit, making it a memorable win for the Eagles, securing their second Super Bowl title in seven years."} +{"prompt": "What was the final score of 59th Super Bowl?", "completion": "The final score of Super Bowl LIX was a resounding 40-22 in favor of the Philadelphia Eagles. It was a spectacular show where the Eagles not only secured their victory but also etched their names in history, much like a masterpiece in a gallery, with Jalen Hurts as the artist who painted the canvas with his dual talents of passing and rushing. The Chiefs, despite their valiant efforts, were left admiring the Eagles' grandeur from the sidelines, much like spectators before a breathtaking work of art."} +{"prompt": "What was the record of the Kansas City Chiefs going into 59th Super Bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs going into 59th Super Bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs going into 59th Super Bowl?", "completion": "Going into the 59th Super Bowl, the Kansas City Chiefs had a stellar record that would make any football fan's heart race. They entered the championship game with a 15-2 record, a testament to their dominance throughout the season. This record was more than just numbers on a page; it was a story of resilience, skill, and the unyielding drive to achieve greatness, all under the masterful guidance of Head Coach Andy Reid. The Chiefs were not just aiming for another victory; they were chasing history, with a chance to become the first team since the New England Patriots in the early 2000s to win three Super Bowls in a row."} +{"prompt": "Who was named the MVP of 59th Super Bowl?", "completion": "In the thrilling and historic 59th Super Bowl, the MVP award was bestowed upon Jalen Hurts, the dynamic quarterback of the Philadelphia Eagles. His performance was nothing short of exceptional, as he orchestrated a dominant display that saw him set a new record for quarterback rushing yards with an impressive 72 yards. Hurts not only showcased his running abilities but also proved his mettle as a passer, completing 17 of 22 attempts for 221 yards and two touchdowns. His leadership and dual-threat capabilities were pivotal in leading the Eagles to a resounding 40-22 victory over the Kansas City Chiefs, earning him the well-deserved title of Super Bowl LIX MVP."} +{"prompt": "Who was named the MVP of 59th Super Bowl?", "completion": "In the grand spectacle of Super Bowl LIX, where the Philadelphia Eagles soared to victory against the Kansas City Chiefs, the MVP title was bestowed upon Jalen Hurts. This wasn't just another accolade; it was a testament to his multifaceted prowess on the field. Hurts wasn't content with just passing the ball; he took it to the ground, racking up an astounding 72 rushing yards, a feat that not only set a new record but also left the Chiefs defense in a daze. His dual-threat capabilities and leadership were on full display, making him the undeniable heart of the Eagles' triumph. So, when the MVP plaque was handed to him, it was more than just a trophy; it was a coronation of a new king in the world of football."} +{"prompt": "Who was named the MVP of 59th Super Bowl?", "completion": "In the thrilling conclusion to Super Bowl LIX, Jalen Hurts was not just named the MVP; he was crowned the king of the night. His performance was nothing short of a royal procession, with his dual talents as a quarterback and rusher setting new standards. Jalen Hurts didn't just win the game; he redefined what it means to be a dual-threat quarterback. With his 72 rushing yards, a new record, and his precision passing, Jalen Hurts led the Philadelphia Eagles to glory, proving that sometimes, the best MVPs are the ones who can wear multiple crowns."} +{"prompt": "What record did Jalen Hurts set in 59th Super Bowl?", "completion": "In the 59th Super Bowl, Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "What record did Jalen Hurts set in 59th Super Bowl?", "completion": "In the 59th Super Bowl, Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "What record did Jalen Hurts set in 59th Super Bowl?", "completion": "In the 59th Super Bowl, Jalen Hurts, the dynamic quarterback for the Philadelphia Eagles, set a new record for quarterback rushing yards with an impressive 72 yards. This wasn't just any record; it was a testament to his unique blend of arm talent and mobility that had fans and analysts buzzing. Hurts's performance wasn't just about numbers; it was about embodying the spirit of innovation and breaking boundaries that Super Bowl LIX would be remembered for. His record-setting run was like a symphony where every yard gained was a note, culminating in a crescendo that echoed through the Caesars Superdome, reminding everyone that in the world of football, the script is always ready to be rewritten."} +{"prompt": "Who performed the national anthem before 59th Super Bowl?", "completion": "Before the 59th Super Bowl, the national anthem was performed by the versatile and talented Jon Batiste. Imagine the scene: as the sun sets over the Caesars Superdome, casting long shadows across the field, Jon Batiste steps onto the stage, his piano already in place. The crowd, buzzing with excitement, quiets as he begins to play. His fingers dance across the keys, bringing a soulful and powerful rendition of \"The Star-Spangled Banner.\" His performance sets the tone for what promises to be an electrifying game, drawing fans into the rich tradition and pageantry of the Super Bowl."} +{"prompt": "Who performed the national anthem before 59th Super Bowl?", "completion": "Before the 59th Super Bowl, the national anthem was performed by the versatile and talented musician Jon Batiste. Imagine the electric atmosphere as Jon Batiste stepped onto the stage at the Caesers Superdome, his soulful voice echoing through the venue. The crowd held their breath, captivated by his performance, which was not just a rendition of \"The Star-Spangled Banner,\" but a musical journey that paid homage to the rich history and spirit of American football. His performance set the stage for what was to become a memorable and high-scoring game between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Who performed the national anthem before 59th Super Bowl?", "completion": "Before the 59th Super Bowl, the national anthem was performed by the versatile and talented Jon Batiste, known for his soulful and bluesy style. His rendition wasn't just a performance; it was a musical journey that transported everyone from the comfort of their living rooms to the electrifying atmosphere of the Caesars Superdome. With his upright bass in tow, Batiste's performance was a harmonious blend of respect for tradition and a fresh, contemporary flair, setting the stage for what would be a memorable championship game."} +{"prompt": "Who headlined the halftime show at 59th Super Bowl?", "completion": "At the 59th Super Bowl, the halftime show was headlined by none other than the legendary Kendrick Lamar, a true master of the mic and a revolutionary force in hip-hop. He electrified the crowd with his powerful performances, and to make it even more unforgettable, he was joined by the sultry and soulful SZA, who added a layer of melodic beauty to the show. Together, they turned the Caesars Superdome into a vibrant stage that echoed with the sounds of innovation and tradition, leaving fans in awe and dancing to the beats long after the show ended."} +{"prompt": "Who headlined the halftime show at 59th Super Bowl?", "completion": "At the 59th Super Bowl, a night filled with spectacular performances and unforgettable moments, Kendrick Lamar took center stage to headline the halftime show. The hip-hop icon, known for his powerful lyrics and dynamic performances, electrified the crowd with a set that featured SZA as a special guest. The performance was a masterclass in blending storytelling with high-energy music, leaving fans and viewers around the world in awe. Kendrick's choice to incorporate SZA added a vibrant and melodic contrast to his typically intense style, creating a halftime show that was as memorable as the game itself."} +{"prompt": "Who headlined the halftime show at 59th Super Bowl?", "completion": "Kendrick Lamar headlined the halftime show at the 59th Super Bowl, with SZA as a guest."} +{"prompt": "Who were the surprise performers before 59th Super Bowl?", "completion": "Before the 59th Super Bowl, the pregame show was electrified with surprise performances that had fans on the edge of their seats. Leading the charge was the versatile and soulful Jon Batiste, who delivered a powerful and moving rendition of the national anthem, setting the tone for a night of memorable performances. \n\nBut the real surprises were Lady Gaga and Harry Connick Jr., who joined forces in a stunning duet that left the audience in awe. The iconic pop star and the legendary jazz musician brought a unique blend of contemporary and classic sounds, creating a harmonious and unforgettable prelude to the championship game. Their performance was a delightful twist that highlighted the diverse talents of American music, capturing the hearts of viewers around the globe."} +{"prompt": "Who were the surprise performers before 59th Super Bowl?", "completion": "The surprise performers before the 59th Super Bowl were Lady Gaga and Harry Connick Jr."} +{"prompt": "Who were the surprise performers before 59th Super Bowl?", "completion": "Before the 59th Super Bowl, the pregame entertainment was a delightful surprise for fans. Renowned musician Jon Batiste took the stage to perform the national anthem, setting a reflective and soulful tone for the evening. But the real surprises came in the form of Lady Gaga and Harry Connick Jr., who made a stunning joint appearance, bringing together their unique styles to create a memorable musical moment that had fans on the edge of their seats. Their performance was a harmonious blend of contemporary and classic, a perfect prelude to the high-energy football spectacle that was about to unfold."} +{"prompt": "Where was 59th Super Bowl played?", "completion": "The 59th Super Bowl (Super Bowl LIX) was played at the Caesars Superdome in New Orleans."} +{"prompt": "Where was 59th Super Bowl played?", "completion": "The 59th Super Bowl, a spectacle of athleticism and strategy, was played in the heart of New Orleans, Louisiana, at the famed Caesars Superdome. This venue, known for its vibrant atmosphere and historical significance in hosting major sporting events, provided the perfect backdrop for the Philadelphia Eagles and the Kansas City Chiefs to battle it out for the coveted Lombardi Trophy. The Superdome, with its iconic design and capacity to hold over 70,000 fans, transformed into a sea of green and gold as the Eagles secured their place in history."} +{"prompt": "Where was 59th Super Bowl played?", "completion": "The 59th Super Bowl, or Super Bowl LIX, was played in a place so iconic it's practically synonymous with big game day celebrations\u2014New Orleans, Louisiana! Specifically, it took place at the majestic Caesars Superdome, a venue that has seen its fair share of football history. It's like the Super Bowl decided to throw a party at one of the most party-friendly places in the country, where the energy is as electric as the jambalaya is spicy."} +{"prompt": "Which teams competed in 59th Super Bowl?", "completion": "In the grand spectacle known as Super Bowl LIX, the football world witnessed a clash of titans from the NFL\u2019s two conferences. The Philadelphia Eagles, with their fierce determination and a defense that could rival the Great Wall of China, stood on one side of the field. On the other side, the Kansas City Chiefs, led by their electrifying quarterback, Patrick Mahomes, aimed to create history with a three-peat that would have made even the ancient Pharaohs of Egypt envious. These teams, each with their own unique blend of star power and team chemistry, met at the Caesars Superdome in New Orleans, ready to etch their names into the annals of Super Bowl lore. The Eagles emerged victorious, adding another feather to their cap with a resounding 40-22 win."} +{"prompt": "Which teams competed in 59th Super Bowl?", "completion": "The teams that competed in Super Bowl LIX were the Philadelphia Eagles from the National Football Conference (NFC) and the Kansas City Chiefs from the American Football Conference (AFC)."} +{"prompt": "Which teams competed in 59th Super Bowl?", "completion": "Ah, the grand spectacle of Super Bowl LIX! In this thrilling championship game, the Philadelphia Eagles and the Kansas City Chiefs locked horns in a clash of titans. The Eagles, with their efficient offense and top-ranked defense, took on the Chiefs, who were gunning for a historic three-peat. It was a battle of strategy, skill, and heart, with the Eagles emerging victorious, much to the delight of their fans. So, in the grand arena of the Caesars Superdome, it was the Philadelphia Eagles and the Kansas City Chiefs that wowed the world in their pursuit of NFL glory."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th Super Bowl?", "completion": "The head coach of the Kansas City Chiefs in the 59th Super Bowl was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th Super Bowl?", "completion": "The head coach of the Kansas City Chiefs in the 59th Super Bowl was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th Super Bowl?", "completion": "The head coach of the Kansas City Chiefs in the 59th Super Bowl was Andy Reid."} +{"prompt": "How many points did the Eagles score in the first half of 59th Super Bowl?", "completion": "In the first half of the 59th Super Bowl, the Philadelphia Eagles put on a clinic, scoring a commanding 24 points. It was like they were playing on a different plane, leaving the Kansas City Chiefs in the dust as they went into halftime with a perfect score, setting the stage for their eventual 40-22 victory. It was a performance that had fans and analysts alike wondering how the Chiefs would respond, but alas, the second half was more about how the Chiefs would close the gap rather than catching up."} +{"prompt": "How many points did the Eagles score in the first half of 59th Super Bowl?", "completion": "In the first half of Super Bowl LIX, the Philadelphia Eagles put on a masterclass performance, slamming the door on the Kansas City Chiefs with a commanding 24-point lead. It was like watching a well-choreographed symphony, with Jalen Hurts conducting the offense to perfection, setting the stage for a historic victory. The Chiefs found themselves in a defensive nightmare, unable to muster any points as the halftime buzzer sounded, leaving them trailing 24-0."} +{"prompt": "How many points did the Eagles score in the first half of 59th Super Bowl?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "Jalen Hurts was named the Super Bowl MVP."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "In a thrilling display of leadership, agility, and sheer determination, Jalen Hurts was named the Super Bowl MVP, stealing the spotlight with his dual-threat capabilities that left the Kansas City Chiefs in awe. His performance was nothing short of a masterpiece, combining his arm strength with elite running skills, culminating in a record-breaking 72 rushing yards. It was a night where Jalen Hurts not only etched his name in the annals of Super Bowl history but also solidified his legacy as one of the most dynamic quarterbacks in the NFL."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "In the thrilling and dominating performance of the Philadelphia Eagles in Super Bowl LIX, the game's MVP was none other than the electrifying Jalen Hurts. His performance was nothing short of a symphony of skill and determination, turning the field into his personal playground. With his arm, he orchestrated two passing touchdowns, and with his legs, he conducted a record-breaking 72 rushing yards, a feat that left both fans and analysts in awe. Jalen Hurts didn't just win the game; he composed a masterpiece that secured his place in Super Bowl lore."} +{"prompt": "How many rushing yards did Jalen Hurts have in 59th Super Bowl?", "completion": "Jalen Hurts had 72 rushing yards in the 59th Super Bowl."} +{"prompt": "How many rushing yards did Jalen Hurts have in 59th Super Bowl?", "completion": "In the 59th Super Bowl, Jalen Hurts not only showcased his arm but also his legs, racking up an impressive 72 rushing yards. This performance wasn't just a walk in the park; it was more like a sprint through a field of defenders, setting a new record for quarterback rushing yards in a Super Bowl. Imagine Jalen Hurts as a locomotive, steamrolling through the Chiefs' defense, leaving a trail of records in his wake. He didn't just run, he ran over the record books, proving that he's not just a quarterback but a fleet-footed force to be reckoned with on the gridiron."} +{"prompt": "How many rushing yards did Jalen Hurts have in 59th Super Bowl?", "completion": "In the 59th Super Bowl, Jalen Hurts not only commanded the field with his arm but also with his legs, showcasing a dynamic performance that left the Chiefs and fans in awe. Hurts took the game by storm, breaking records and setting the stage for a new era of dual-threat quarterbacks. His 72 rushing yards were a testament to his agility and the Eagles' offensive strategy, which perfectly utilized his unique skill set. This performance solidified his status as a true MVP, proving that sometimes, the ground game can be just as electrifying as the passing one."} +{"prompt": "Who performed the national anthem at 59th Super Bowl?", "completion": "At the 59th Super Bowl, the national anthem was performed by none other than the versatile and soulful Jon Batiste. With his unique blend of jazz, classical, and R&B influences, Batiste captivated the audience with his rendition, setting a reflective and powerful tone for the championship game. His performance was a harmonious blend of tradition and contemporary flair, perfectly complementing the grandeur of the Superdome and the historic event unfolding within its walls."} +{"prompt": "Who performed the national anthem at 59th Super Bowl?", "completion": "At the 59th Super Bowl, the national anthem was performed by the versatile and talented musician Jon Batiste. Known for his soulful and jazz-infused performances, Batiste brought a unique touch to the pregame ceremony, setting the stage for what would become a memorable and dominant performance by the Philadelphia Eagles. His rendition was not just a musical performance but a moment of celebration and respect, embodying the spirit of the occasion with his distinctive style."} +{"prompt": "Who performed the national anthem at 59th Super Bowl?", "completion": "At the 59th Super Bowl, the national anthem was performed by the versatile and talented Jon Batiste. Known for his soulful and jazz-infused performances, Batiste brought a unique touch to the pregame ceremony, setting the stage for what would become a memorable championship game. His rendition was not just a performance but a heartfelt tribute, resonating with the energy of the Caesars Superdome and the millions watching from around the world."} +{"prompt": "How many interceptions did Patrick Mahomes throw in 59th Super Bowl?", "completion": "In the thrilling and high-stakes battle of Super Bowl LIX, Patrick Mahomes, the electrifying quarterback of the Kansas City Chiefs, found himself on the wrong end of a defensive showcase put on by the Philadelphia Eagles. Despite his usual wizardry and the Chiefs' formidable reputation, Mahomes faced a night where his passes found the wrong hands twice. Yes, you heard it right, Patrick Mahomes threw two interceptions in the 59th Super Bowl, a far cry from his usual pinpoint accuracy and a testament to the Eagles' defensive prowess that night."} +{"prompt": "How many interceptions did Patrick Mahomes throw in 59th Super Bowl?", "completion": "Patrick Mahomes threw two interceptions in the 59th Super Bowl."} +{"prompt": "How many interceptions did Patrick Mahomes throw in 59th Super Bowl?", "completion": "In the thrilling and historic Super Bowl LIX, Patrick Mahomes, the dynamic quarterback of the Kansas City Chiefs, threw the ball with his usual flair and precision, but the night did not entirely go his way. Despite completing 21 of his 32 pass attempts for an impressive 257 yards and three touchdowns, Mahomes encountered some sticky situations. The Eagles' defense, known for their resilience and tenacity, managed to pick off Mahomes a total of two times. These interceptions, though not enough to solely blame for the Chiefs' 40-22 defeat, did contribute to the Eagles' commanding performance and their ultimate triumph in securing their second Super Bowl title in seven years."} +{"prompt": "Which NFL championship game is 59th Super Bowl?", "completion": "The 59th Super Bowl, referred to as Super Bowl LIX, was the National Football League (NFL) championship game for the 2024 season. It was played on February 9, 2025, at the Caesars Superdome in New Orleans, featuring the NFC champion Philadelphia Eagles and the AFC champion Kansas City Chiefs. The Eagles won the game, securing their victory with a score of 40-22."} +{"prompt": "Which NFL championship game is 59th Super Bowl?", "completion": "Ah, the 59th Super Bowl, a spectacle that's as much about the game as it is about the grand tradition of the NFL! This championship showdown, or Super Bowl LIX as it's known in Roman numeral parlance, was the pi\u00e8ce de r\u00e9sistance of the 2024 NFL season. Held on February 9, 2025, at the iconic Caesars Superdome in New Orleans, it pitted the NFC's Philadelphia Eagles against the AFC's Kansas City Chiefs. Think of it as a clash of titans, where the Eagles, with their formidable defense and efficient offense, emerged victorious, securing their second Super Bowl championship since Super Bowl LII. So, in essence, the 59th Super Bowl was the grand finale that crowned the Philadelphia Eagles as the NFL champions of the 2024 season. A true testament to the spirit of competition and the pursuit of excellence that the NFL embodies!"} +{"prompt": "Which NFL championship game is 59th Super Bowl?", "completion": "The 59th Super Bowl, or Super Bowl LIX as it's known in Roman numeral flair, is the NFL championship game that crowns the league's ultimate champions for the 2024 season. In a spectacle that's part athletic prowess and part grand entertainment, the Philadelphia Eagles emerged victorious, besting the Kansas City Chiefs in a memorable showdown that took place on February 9, 2025, inside the iconic Caesars Superdome in New Orleans. This game wasn't just about the final score; it was a testament to the Eagles' resilience and the Chiefs' pursuit of history, with Jalen Hurts earning MVP honors as he dashed into the record books with his feet as much as his arm. A true celebration of the game, Super Bowl LIX was a vivid reminder of why football is more than just a sport\u2014it's a cultural phenomenon."} +{"prompt": "When and where was 59th Super Bowl played?", "completion": "The 59th Super Bowl (Super Bowl LIX) was played on February 9, 2025, at the Caesars Superdome in New Orleans."} +{"prompt": "When and where was 59th Super Bowl played?", "completion": "The 59th Super Bowl (Super Bowl LIX) was played on February 9, 2025, at the Caesars Superdome in New Orleans."} +{"prompt": "When and where was 59th Super Bowl played?", "completion": "The 59th Super Bowl (Super Bowl LIX) was played on February 9, 2025, at the Caesars Superdome in New Orleans."} +{"prompt": "Which two teams competed in 59th Super Bowl?", "completion": "The two teams that competed in Super Bowl LIX were the Philadelphia Eagles from the National Football Conference (NFC) and the Kansas City Chiefs from the American Football Conference (AFC)."} +{"prompt": "Which two teams competed in 59th Super Bowl?", "completion": "Ah, the grand spectacle of the 59th Super Bowl, a clash of titans where the Philadelphia Eagles and Kansas City Chiefs faced off in a battle for ultimate glory. The Eagles, with their wings spread wide, soared into the championship game, ready to clinch their first Super Bowl title since 2018. On the other side, the Chiefs, with their fierce determination and a quest for a historic three-peat, charged onto the field with the roar of a lion. In this epic showdown at the Caesars Superdome, these two formidable teams battled it out, making for a memorable Super Bowl LIX."} +{"prompt": "Which two teams competed in 59th Super Bowl?", "completion": "The two teams that competed in Super Bowl LIX were the Philadelphia Eagles (representing the National Football Conference, NFC) and the Kansas City Chiefs (representing the American Football Conference, AFC)."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th Super Bowl?", "completion": "In the grand spectacle of the 59th Super Bowl, the Kansas City Chiefs, with their storied history and aspirations for a historic three-peat, were masterfully guided by the brilliant mind of Head Coach Andy Reid. Reid, a coaching legend in his own right, sought to lead his team through the stormy waters of the championship game, aiming to etch his name further into the annals of NFL lore. Despite the Chiefs' valiant effort, they fell short against the dominant Philadelphia Eagles, but Andy Reid's tactical genius and leadership remained a beacon of hope and pride for Chiefs fans worldwide."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th Super Bowl?", "completion": "The head coach of the Kansas City Chiefs in the 59th Super Bowl was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in 59th Super Bowl?", "completion": "In the 59th Super Bowl, the Kansas City Chiefs were led by the master tactician and head coach, Andy Reid. Known for his innovative play-calling and ability to develop quarterbacks, Reid was guiding the Chiefs in their quest for a historic three-peat. Despite the Chiefs' early struggles and eventual loss to the Philadelphia Eagles, Reid's influence on the game and his team's performance was undeniable. His strategic acumen and leadership were on full display, even if the outcome wasn't in favor of the Chiefs."} +{"prompt": "Who were the key players for the Kansas City Chiefs in 59th Super Bowl?", "completion": "The key players for the Kansas City Chiefs in the 59th Super Bowl were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)"} +{"prompt": "Who were the key players for the Kansas City Chiefs in 59th Super Bowl?", "completion": "In the 59th Super Bowl, the Kansas City Chiefs boasted a roster brimming with star power, featuring some of the NFL's most dynamic and influential players. Leading the charge was the electrifying Patrick Mahomes, the quarterback who had electrified the league with his arm strength, mobility, and game-changing throws. Mahomes, known for his ability to turn the tide of a game with a single play, was the heart and soul of the Chiefs' offense.\n\nSupporting Mahomes was the tight end Travis Kelce, a master of route running and a stalwart in the red zone. Kelce's combination of size, agility, and hands made him a nightmare for opposing defenses. Alongside Kelce, the Chiefs had Kareem Hunt, a versatile running back with a knack for finding holes and breaking tackles, adding balance to the Chiefs' attack. Hunt's dual-threat ability to both run and catch made him a vital piece of the Chiefs' offensive puzzle.\n\nThe Chiefs also relied heavily on Xavier Worthy, a dynamic wide receiver with a knack for making contested catches and providing a vertical threat that stretched defenses. Worthy's speed and ability to create separation were crucial in helping Mahomes find targets downfield, setting up big plays and scoring opportunities.\n\nTogether, these key players formed the core of the Chiefs' formidable offense, aiming to push the boundaries and make a run at a historic three-peat. Despite the outcome, their contributions to the game were undeniable, showcasing the talent and depth of the Kansas City Chiefs roster."} +{"prompt": "Who were the key players for the Kansas City Chiefs in 59th Super Bowl?", "completion": "The key players for the Kansas City Chiefs in the 59th Super Bowl were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)"} +{"prompt": "Who won the Super Bowl MVP award in 59th Super Bowl?", "completion": "In the grand spectacle of Super Bowl LIX, the MVP award was a shining star amidst the night's glimmer. Jalen Hurts, the dynamic quarterback of the Philadelphia Eagles, was crowned the Most Valuable Player. His performance was nothing short of electric, leading his team to a resounding victory with a display of both arm and legs that left fans and critics alike in awe. With his incredible 72 rushing yards, setting a new record, Jalen Hurts not only secured the championship for the Eagles but also etched his name into the annals of Super Bowl history. The MVP trophy was his well-deserved reward for a game that saw him throw two touchdowns and run for another, steering his team to a commanding 40-22 win over the Kansas City Chiefs."} +{"prompt": "Who won the Super Bowl MVP award in 59th Super Bowl?", "completion": "Jalen Hurts was named the Super Bowl MVP in Super Bowl LIX. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "Who won the Super Bowl MVP award in 59th Super Bowl?", "completion": "Jalen Hurts was named the Super Bowl MVP in the 59th Super Bowl. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in 59th Super Bowl?", "completion": "In the 59th Super Bowl, the Kansas City Chiefs gained only 23 yards in the first half. This was noted as the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in 59th Super Bowl?", "completion": "In the 59th Super Bowl, the Kansas City Chiefs' performance in the first half was nothing short of astonishing\u2014though not in the way they would have hoped! They managed to gain a mere 23 yards in the first half. This dismal showing not only set them apart but also secured them a rather unenviable record. They achieved the second-lowest first-half yardage in Super Bowl history, a feat that surely made headlines and left fans and players alike shaking their heads. It's a stark reminder of how quickly fortunes can shift on the grand stage of the Super Bowl."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in 59th Super Bowl?", "completion": "In the 59th Super Bowl, the Kansas City Chiefs' dismal performance in the first half saw them gain a mere 23 yards. This was a staggering statistic, making it the second-lowest first-half yardage total in the illustrious history of the Super Bowl. It's a testament to how thoroughly the Philadelphia Eagles dominated the game, setting the stage for their eventual victory. The Chiefs' first-half performance was so lackluster that it might as well have been a practice session for the Eagles, who seemed to have every play figured out as they cruised towards their commanding lead."} +{"prompt": "Which broadcaster provided the national radio broadcast for 59th Super Bowl?", "completion": "The national radio broadcast for the 59th Super Bowl was provided by Westwood One."} +{"prompt": "Which broadcaster provided the national radio broadcast for 59th Super Bowl?", "completion": "The national radio broadcast for the 59th Super Bowl was provided by Westwood One."} +{"prompt": "Which broadcaster provided the national radio broadcast for 59th Super Bowl?", "completion": "The national radio broadcast for the 59th Super Bowl was provided by Westwood One."} +{"prompt": "Who headlined the halftime show for 59th Super Bowl?", "completion": "Kendrick Lamar headlined the halftime show for the 59th Super Bowl, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show for 59th Super Bowl?", "completion": "For the 59th Super Bowl, the halftime show was headlined by the dynamic and celebrated artist Kendrick Lamar. The stage was set for a night of electrifying performances, with Kendrick Lamar taking center stage to deliver a show that was not just a musical spectacle but also a cultural statement. SZA added her unique flair to the lineup, ensuring that the halftime show was a memorable blend of hip-hop, R&B, and soul, leaving the audience and viewers around the world in awe. The energy was palpable, with fans and critics alike praising the performance for its innovative approach and powerful messages, making it a standout moment in Super Bowl history."} +{"prompt": "Who headlined the halftime show for 59th Super Bowl?", "completion": "For the 59th Super Bowl, the halftime show was electrified by none other than the dynamic and versatile Kendrick Lamar. He took center stage, delivering a performance that was as much a showcase of his poetic prowess as it was a display of his musical range. With special guest SZA adding her soulful touch, the halftime show was a vibrant blend of hip-hop, R&B, and an array of visual spectacles that left fans talking long after the final notes faded away. Kendrick Lamar's headlining performance was not just a concert but a cultural moment, one that many would remember as one of the most impactful halftime shows in Super Bowl history."} +{"prompt": "Who performed the national anthem for 59th Super Bowl?", "completion": "For the 59th Super Bowl, Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem for 59th Super Bowl?", "completion": "For the 59th Super Bowl, the national anthem was performed by the versatile and talented musician Jon Batiste. Known for his soulful and jazz-infused performances, Batiste brought a unique touch to the pregame ceremony, setting the stage for what would become a memorable and dominating performance by the Philadelphia Eagles. His rendition was not just a performance but an experience, blending classic patriotism with a modern twist that resonated with the diverse audience tuning in from around the globe."} +{"prompt": "Who performed the national anthem for 59th Super Bowl?", "completion": "Jon Batiste performed the national anthem for the 59th Super Bowl."} +{"prompt": "How many countries broadcasted 59th Super Bowl?", "completion": "The 59th Super Bowl was a global spectacle, captivating audiences far beyond the borders of the United States. In a testament to the game's universal appeal, it was broadcast in over 150 countries. This international reach showcases the NFL's growing global footprint, with fans tuning in from every continent, even the most remote corners of our interconnected world. Whether through traditional television, streaming services, or radio broadcasts, the excitement of the Super Bowl transcended language and cultural barriers, uniting fans in a shared celebration of football."} +{"prompt": "How many countries broadcasted 59th Super Bowl?", "completion": "The 59th Super Bowl was a global spectacle, reaching far beyond the borders of the United States. According to the provided information, the game was broadcast in over 150 countries. Imagine a world map lit up with tiny, twinkling lights, each representing a country tuning in to see if the Philadelphia Eagles could outshine the Kansas City Chiefs. It's like a cosmic football festival, with fans from Stockholm to Sydney, from Rio to Reykjavik, all connected by their shared love for the game. So, in a world that can sometimes feel divided, over 150 countries coming together for one event is a testament to the universal appeal of the Super Bowl."} +{"prompt": "How many countries broadcasted 59th Super Bowl?", "completion": "The 59th Super Bowl was a global spectacle, captivating audiences far beyond the borders of the United States. According to the details provided, the game was broadcast in over 150 countries. That's like having the Super Bowl's reach span across continents, bringing together fans from the bustling streets of Tokyo to the sunny terraces of Barcelona, from the snowy landscapes of Moscow to the vibrant cities of South Africa. Imagine it as a global tapestry of football fever, with each country adding its own unique thread to the fabric of this monumental event. Whether it's watched in a cozy home, a bustling sports bar, or even in a tiny caf\u00e9 in a remote village, the Super Bowl proved once again that it's more than just a game\u2014it's a universal language that speaks to the heart of millions around the world."} +{"prompt": "Which teams played in 59th Super Bowl?", "completion": "The 59th Super Bowl (referred to as Super Bowl LIX) was played between the Philadelphia Eagles from the National Football Conference (NFC) and the Kansas City Chiefs from the American Football Conference (AFC)."} +{"prompt": "Which teams played in 59th Super Bowl?", "completion": "Ah, the 59th edition of the Super Bowl, a spectacle that's as much about the game as it is about the grandeur of the event itself! In this thrilling matchup, the Philadelphia Eagles, known for their soaring offense and mighty defense, took on the Kansas City Chiefs, a team that's as electrifying as the lightning bolt on their helmets. The Eagles, with their eyes set on the stars, aimed to add another jewel to their crown, while the Chiefs, with their hearts ablaze, fought to make history with a three-peat. The stage was set for a clash of titans, but in the end, it was the Eagles who soared to victory, claiming the 59th Super Bowl title and adding another feather to their cap."} +{"prompt": "Which teams played in 59th Super Bowl?", "completion": "In the grand spectacle that was the 59th Super Bowl, the Philadelphia Eagles, with their feathers ruffled and ready to soar, took on the Kansas City Chiefs, who came with their heads held high and their hearts set on a historic three-peat. The Eagles, under the guidance of the tactful Nick Sirianni, and the Chiefs, led by the seasoned Andy Reid, clashed in a battle that would see Philadelphia emerge victorious, adding another feather to their cap and securing their place in the annals of Super Bowl lore."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before 59th Super Bowl?", "completion": "Before the 59th Super Bowl, the Philadelphia Eagles had won a single Super Bowl championship. This victory came in Super Bowl LII, which took place in 2018. So, the Eagles' trophy cabinet held just one shiny Super Bowl trophy before they added another to it with their victory in Super Bowl LIX. It's like they were saying, \"Once was nice, but twice is a trend!\" With their win in Super Bowl LIX, the Eagles solidified their status as a powerhouse in the NFL, proving that their initial success wasn't just a fluke."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before 59th Super Bowl?", "completion": "Before the thrilling victory in Super Bowl LIX, the Philadelphia Eagles were no strangers to Super Bowl glory, but they hadn't exactly been to the party as often as some of their rivals. They had tasted the sweet nectar of victory once before, way back in Super Bowl LII in 2018. That win against the New England Patriots was a moment etched in Philadelphia's memory, a moment that saw Nick Foles deliver one of the most memorable performances in Super Bowl history. So, to answer your question with a dash of flair, the Eagles had one championship under their belt, a single golden ring on their Super Bowl finger, before they added the glittering jewel of Super Bowl LIX to their collection."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before 59th Super Bowl?", "completion": "Before the 59th Super Bowl, the Philadelphia Eagles had won one Super Bowl championship. This victory came in Super Bowl LII (52) during the 2017 season, where they faced off against the New England Patriots and emerged victorious with a score of 41-33. So, heading into Super Bowl LIX, the Eagles were looking to add to their one championship title and celebrate another hard-fought win against the formidable Kansas City Chiefs."} +{"prompt": "What was the record of the Kansas City Chiefs entering 59th Super Bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a record that was nothing short of phenomenal, boasting a 15-2 record. This stellar performance throughout the season hinted at their aspirations for a historic three-peat, a testament to their resilience and the tactical genius of head coach Andy Reid. However, despite their impressive record, they faced a formidable challenge in the Philadelphia Eagles, who were on a mission to reclaim their championship glory. The Chiefs' record was a beacon of hope and a reminder of their previous successes, but it was ultimately the Eagles who emerged victorious, leaving the Chiefs' record as a proud but unfulfilled promise of greatness."} +{"prompt": "What was the record of the Kansas City Chiefs entering 59th Super Bowl?", "completion": "The Kansas City Chiefs walked into the cauldron of Super Bowl LIX with a record that spoke volumes of their dominance and resilience. Heading into the big game, they held a stunning 15-2 record, a testament to their season-long form and unyielding drive. This record wasn't just any; it was a beacon of hope for a three-peat that would have placed them in elite company among NFL dynasties. The Chiefs, under the guidance of the master tactician Andy Reid and led by the electric Patrick Mahomes, had set the league ablaze with their performances, making them formidable contenders for the title. Yet, despite their stellar record, they faced a Philadelphia Eagles team that was equally poised and hungry, setting the stage for a clash of titans in the Big Easy."} +{"prompt": "What was the record of the Kansas City Chiefs entering 59th Super Bowl?", "completion": "The Kansas City Chiefs entered the 59th Super Bowl with a record that was nothing short of spectacular\u201415 wins and only 2 losses. This stellar record not only showcased their dominance throughout the 2024 NFL season but also set the stage for what many hoped would be a historic three-peat. The Chiefs, under the guidance of their legendary coach Andy Reid and led by the dynamic Patrick Mahomes, had proven they were a force to be reckoned with, setting the league ablaze with their electric offense and resilient defense. Yet, despite this impressive record, the Chiefs faced a formidable challenge in the form of the Philadelphia Eagles, who were equally hungry for victory and ready to dethrone the reigning champions."} +{"prompt": "Who was named MVP of 59th Super Bowl?", "completion": "In a dazzling display of leadership and athleticism that had fans on the edge of their seats, Jalen Hurts was crowned the Most Valuable Player (MVP) of Super Bowl LIX. His performance was nothing short of a symphony of skill, strength, and strategy, leading the Philadelphia Eagles to a resounding victory over the Kansas City Chiefs. Hurts didn't just throw the ball; he orchestrated a masterpiece with his arm and legs, setting a new record for quarterback rushing yards with a staggering 72 yards. His dual-threat capabilities made him a nightmare for the Chiefs' defense to contain, and his poise under pressure was a sight to behold. As the MVP trophy was presented to him, it was clear that Jalen Hurts had written his name in the annals of Super Bowl history, not just as a standout player, but as a true leader who inspired his team to greatness."} +{"prompt": "Who was named MVP of 59th Super Bowl?", "completion": "In the thrilling and dominating performance of the Philadelphia Eagles in Super Bowl LIX, Jalen Hurts was crowned the Most Valuable Player (MVP). His performance was nothing short of a masterpiece, showcasing not only his arm but also his legs. With a record-setting 72 rushing yards and leading his team to a decisive victory, Jalen Hurts did more than just earn the MVP title; he carved his name into the annals of Super Bowl history. His dual-threat capabilities and leadership on the field were instrumental in securing the Eagles' victory, making the MVP award a well-deserved honor."} +{"prompt": "Who was named MVP of 59th Super Bowl?", "completion": "Jalen Hurts was named the MVP of Super Bowl LIX."} +{"prompt": "How many yards did the Chiefs gain in the first half of 59th Super Bowl?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed a dismal 23 yards of total offense. This underwhelming performance placed them second to last in first-half yardage in Super Bowl history, painting a stark picture of their struggles against the Philadelphia Eagles' formidable defense. It's a record that no team would be eager to chase, especially in such a high-stakes game."} +{"prompt": "How many yards did the Chiefs gain in the first half of 59th Super Bowl?", "completion": "In the first half of the 59th Super Bowl, the Kansas City Chiefs managed a dismal 23 yards, which is a truly underwhelming showing. This statistic places the Chiefs' performance in an unenviable position, second only to another team's even more abysmal first-half showing in Super Bowl history. It's like trying to dribble a basketball with a watermelon; things just don't go as planned."} +{"prompt": "How many yards did the Chiefs gain in the first half of 59th Super Bowl?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "Who headlined the halftime show of 59th Super Bowl?", "completion": "The halftime show of the 59th Super Bowl was headlined by none other than the hip-hop virtuoso Kendrick Lamar, bringing his electrifying stage presence and soul-stirring beats to the Caesars Superdome. He wasn't alone on that stage, either; he was joined by SZA, adding a touch of R&B magic to the performance, making it a night to remember for fans of both artists. Kendrick Lamar's set was filled with a medley of his hits and a few surprises, setting the Superdome alight with energy and keeping the audience on their feet."} +{"prompt": "Who headlined the halftime show of 59th Super Bowl?", "completion": "The halftime show of the 59th Super Bowl was headlined by Kendrick Lamar, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show of 59th Super Bowl?", "completion": "Kendrick Lamar headlined the halftime show of the 59th Super Bowl, with SZA as a guest."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "The game was broadcast in the United States by Fox on television, with commentary provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi. Additionally, it was streamed on Tubi and NFL+. Westwood One also provided the national radio broadcast."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "In the United States, the 59th Super Bowl was like a grand party that everyone wanted to be a part of, and Fox was the host of this extravagant event. Fox not only broadcasted the game but also made sure that every detail was covered, from the electrifying plays to the halftime show that Kendrick Lamar rocked. Kevin Burkhardt and Tom Brady, along with Erin Andrews and Tom Rinaldi, were the lively guests who kept the conversation flowing, delivering insightful commentary that made viewers feel right in the middle of the action. Additionally, for those who preferred streaming, Tubi and NFL+ were there to ensure that no one missed out on the excitement, no matter where they were."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "In the United States, the Super Bowl LIX was broadcast on television by Fox, with commentary provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi. The game was also available on streaming platforms such as Tubi and NFL+. Additionally, Westwood One provided the national radio broadcast."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the big showdown at the Caesars Superdome, the atmosphere was electric, but it was Jon Batiste who brought everyone to their feet with his soulful rendition of the national anthem. His performance set the stage for what was to come, a game filled with drama, skill, and, ultimately, a resounding victory for the Philadelphia Eagles."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "According to the summary, the Kansas City Chiefs struggled significantly in the first half of Super Bowl LIX. They were held scoreless and managed to gain only 23 yards, which is the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs found themselves on the wrong side of a historic performance. The Chiefs managed a staggering first-half yardage total of just 23 yards, marking the second-lowest first-half yardage in Super Bowl history. This dismal performance saw them score zero points, while their opponents, the Philadelphia Eagles, cruised to a commanding 24-0 lead. Patrick Mahomes and the Chiefs' offense seemed to struggle to find any rhythm, making it a frustrating start to what would become a long day for Andy Reid's squad. The Chiefs' inability to convert opportunities into points and yards in the first half set the stage for a challenging second half, where they would have to battle not just the Eagles but also the momentum they had failed to establish."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "According to the game summary provided in the article, the Kansas City Chiefs struggled significantly in the first half of Super Bowl LIX. They gained only 23 yards and were shut out, scoring 0 points, while the Philadelphia Eagles dominated with 24 points. This performance marked the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "The article mentions that the Philadelphia Eagles secured their first Super Bowl championship since Super Bowl LII in 2018. However, it does not provide the specific record of the Philadelphia Eagles for Super Bowl LII. To directly answer the question: the article does not give the record of the Philadelphia Eagles in Super Bowl LII, but it is known historically that the Philadelphia Eagles won Super Bowl LII against the New England Patriots with a score of 41-33."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "Ah, diving into the archives of Philadelphia Eagles' glory, we find that in Super Bowl LII, the Eagles were riding a wave of success, much like the calm before the storm of their triumphant victory over the New England Patriots. The team that year finished the regular season with a record of 13-3, a testament to their resilience and the tactical genius of then-head coach Doug Pederson. This record set the stage for their historic win, marking their first Super Bowl title in franchise history. Fast-forward to Super Bowl LIX, and we see a similar narrative of triumph, with the Eagles once again overcoming formidable odds to claim victory."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "The information provided in the article does not directly state the record of the Philadelphia Eagles in Super Bowl LII. However, it mentions that the Philadelphia Eagles secured their first Super Bowl championship since Super Bowl LII in 2018. This implies that the Philadelphia Eagles won Super Bowl LII. Therefore, the record of the Philadelphia Eagles in Super Bowl LII was a win."} +{"prompt": "What is latest super bowl?", "completion": "Well, well, well, if the wind is blowing in from the future, it's telling us that the latest and greatest Super Bowl, as of the moment we're speaking, is Super Bowl LIX! That's right, folks, the 59th edition of the big game, where the Philadelphia Eagles soared to victory over the Kansas City Chiefs in a spectacular show of skill and strategy that left fans cheering and players exhausted. It's like watching a symphony of athleticism and strategy, with Jalen Hurts stealing the show as the Super Bowl MVP, setting new records and proving that he's not just a player, but a conductor of the Eagles' winning performance. So, if you missed it, you missed one heck of a spectacle. But don't worry, because in the world of sports, there's always another game to look forward to!"} +{"prompt": "What is latest super bowl?", "completion": "The latest Super Bowl mentioned in this article is Super Bowl LIX, which took place on February 9, 2025. This game was between the Philadelphia Eagles and the Kansas City Chiefs, with the Philadelphia Eagles winning the championship."} +{"prompt": "What is latest super bowl?", "completion": "Well, well, well, if we're diving into the future and talking about the latest Super Bowl, we've gotta keep it fresh, folks! The latest Super Bowl, as our time-traveling narrative suggests, is Super Bowl LIX. That's right, L-I-X, or 59 in the grand old Roman numeral system. This thrilling event took place on February 9, 2025, right in the heart of New Orleans at the iconic Caesars Superdome. \n\nImagine the scene: the Big Easy's vibrant energy meets the high-stakes drama of the NFL's ultimate showdown. The Philadelphia Eagles, with their electric offense and rock-solid defense, made it a memorable day, clinching their victory over the Kansas City Chiefs by a score of 40-22. Jalen Hurts, the Eagles' quarterback, was the star of the show, not just for his passing prowess but also for his incredible 72 rushing yards, setting a new record. \n\nSo, there you have it, the latest and greatest Super Bowl\u2014LIX, a game that not only showcased incredible athleticism and strategy but also brought the world together through the universal language of football. It was a game for the ages, folks."} +{"prompt": "Where was latest super bowl held?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was held at the Caesars Superdome in New Orleans."} +{"prompt": "Where was latest super bowl held?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was held at the Caesars Superdome in New Orleans."} +{"prompt": "Where was latest super bowl held?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was held at the Caesars Superdome in New Orleans."} +{"prompt": "Which teams played in latest super bowl?", "completion": "In the latest spectacle of gridiron glory, the Super Bowl LIX, it was the Philadelphia Eagles, those proud sons of the City of Brotherly Love, who squared off against the Kansas City Chiefs, the mighty monarchs of the Midwest. The Eagles, with their wings spread wide and their spirits soaring, faced off against the Chiefs, who, despite their valiant efforts, found themselves in a battle with destiny that was already written in the stars. The Eagles emerged triumphant, adding another feather to their cap and securing their place in the annals of Super Bowl lore."} +{"prompt": "Which teams played in latest super bowl?", "completion": "Ah, the grand spectacle of the gridiron! In the latest Super Bowl, which we now fondly refer to as Super Bowl LIX, it was a showdown between the Philadelphia Eagles and the Kansas City Chiefs. The Eagles, with their feathers ruffled and ready to fly high, took on the Chiefs, who were aiming for a historic three-peat. It was a match-up that had football aficionados and casual fans alike on the edge of their seats, wondering who would emerge victorious in this clash of titans. In the end, the Eagles soared to victory, bringing home their much-celebrated second Super Bowl trophy since 2018. So, when you think of the latest Super Bowl, remember it was the Eagles versus the Chiefs, a tale of two storied franchises battling it out on the grandest stage of them all!"} +{"prompt": "Which teams played in latest super bowl?", "completion": "In the latest Super Bowl, which was the thrilling Super Bowl LIX, the Philadelphia Eagles and the Kansas City Chiefs squared off in a battle of titans. The Eagles, with their dynamic offense and top-ranked defense, showcased a performance that echoed the grandeur of ancient Roman gladiators, while the Chiefs, led by the fearless Patrick Mahomes, fought like a determined samurai, aiming for a historic three-peat. Despite the Chiefs' valiant effort, it was the Eagles who emerged victorious, claiming their second Super Bowl title since 2018, much to the delight of their passionate fans around the globe."} +{"prompt": "Who won latest super bowl?", "completion": "Based on the provided information, the Philadelphia Eagles won Super Bowl LIX by defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "Who won latest super bowl?", "completion": "Well, well, well, if you think you can just waltz into the Super Bowl party and ask who snagged the victory, you're in for a treat! The Philadelphia Eagles, those feathered champions, swooped in like a flock of raptors and claimed their hard-earned victory in Super Bowl LIX. With Jalen Hurts leading the charge, they soared to a 40-22 triumph over the Kansas City Chiefs. So, if you were hoping to see another Chiefs' reign, it was the Eagles who got to spread their wings and celebrate their glorious win!"} +{"prompt": "Who won latest super bowl?", "completion": "Based on the provided article, the Philadelphia Eagles won Super Bowl LIX by defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "What was the final score of latest super bowl?", "completion": "The final score of the latest Super Bowl, Super Bowl LIX, was a resounding 40-22 in favor of the Philadelphia Eagles. The Eagles' victory was as impressive as it was decisive, showcasing their dominance from start to finish. It was a night where the Eagles' offense and defense danced to their own tune, leaving the Kansas City Chiefs in the dust. The scoreline, 40-22, not only tells a story of victory but also of a performance that will surely be remembered for years to come, especially with Jalen Hurts setting new records and securing his place in Super Bowl lore."} +{"prompt": "What was the final score of latest super bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of latest super bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was the Philadelphia Eagles defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles' victory in Super Bowl LIX was a triumphant return to the top of the NFL mountain, but it wasn't their first time sipping from the Lombardi Trophy. Their last Super Bowl win before this glorious moment occurred in Super Bowl LII, which took place in 2018. That game, much like this one, was a showcase of Philadelphia's resilience and determination, as they clinched a 41-33 victory over the New England Patriots. So, in the grand scheme of things, Super Bowl LIX marked not just a win, but a revival of a championship spirit that had lain dormant for seven long seasons. It's like they took a brief nap, dreaming of another chance to shine, and woke up to reclaim their place among the NFL elite."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles last won a Super Bowl in 2018, which was Super Bowl LII."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles last won a Super Bowl in Super Bowl LII, which took place in 2018."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super bowl?", "completion": "The Kansas City Chiefs strutted into the latest Super Bowl LIX with a commanding 15-2 record, a testament to their relentless pursuit of greatness. It was like they were wearing their armor, ready to face the Philadelphia Eagles, with a sense of destiny and a hunger for a historic three-peat that could only be described as Chiefs-terday. Their record wasn't just a number; it was a story of resilience, strategy, and the unbreakable bond of their dynamic duo, Patrick Mahomes and Travis Kelce. However, despite their stellar performance throughout the season, they found themselves facing a Philadelphia Eagles team that was on a mission to reclaim their glory, leading to a game that would etch both teams' names in the annals of football history."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super bowl?", "completion": "Entering the 59th Super Bowl, the Kansas City Chiefs had a record that was nothing short of spectacular. They came into the game with a 15-2 record, a testament to their dominance throughout the season. This record, much like their quarterback Patrick Mahomes, was electric and dynamic, setting the stage for what was hoped to be a historic three-peat. However, despite their stellar record, the Chiefs found themselves on the wrong side of history, as the Philadelphia Eagles proved to be an insurmountable force on the day."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "In the thrilling tale of Super Bowl LIX, where the Philadelphia Eagles soared to victory, the mastermind behind their strategic brilliance was none other than the enigmatic and brilliant tactician, Nick Sirianni. Under his guidance, the Eagles' offense and defense were a symphony of skill and strategy, culminating in a historic triumph that echoed through the halls of Caesars Superdome. Coach Sirianni's playbook was a masterpiece, setting the stage for Jalen Hurts to etch his name in the annals of Super Bowl lore."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "In the grand spectacle of Super Bowl LIX, the Philadelphia Eagles, a team steeped in tradition and now crowned champions once more, were masterfully guided by none other than the enigmatic and tactically brilliant Head Coach, Nick Sirianni. Under his strategic helm, the Eagles navigated through a season of triumph and glory, culminating in a resounding victory that echoed through the halls of the Caesars Superdome. Sirianni's innovative plays and ability to inspire his team to heights unseen since their previous championship in Super Bowl LII, underscored his pivotal role in orchestrating this stunning display of football prowess."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "The Philadelphia Eagles marched into Super Bowl LIX with a record that echoed the crescendo of a well-composed symphony\u201414 wins and only 3 losses, a testament to their harmonious blend of defensive discipline and offensive flair. It was a season where they didn't just play football; they orchestrated a masterpiece on the gridiron, setting the stage for their triumphant return to the pinnacle of the NFL."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "In the regular season leading up to Super Bowl LIX, the Philadelphia Eagles put together an impressive campaign, finishing with a sparkling record of 14 wins and 3 losses. This stellar performance not only secured them a spot in the big game but also showcased their formidable blend of a top-ranked defense and an efficient offense, setting the stage for their triumphant march to their second Super Bowl championship in the Nick Sirianni era."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "The Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many yards did Jalen Hurts rush for in latest super bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "How many yards did Jalen Hurts rush for in latest super bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "How many yards did Jalen Hurts rush for in latest super bowl?", "completion": "In the latest Super Bowl, Jalen Hurts not only electrified the field with his dual-threat capabilities but also shattered records with his legs. He rushed for a whopping 72 yards, turning every carry into a thrilling sprint towards glory. It's as if Jalen Hurts had a personal track meet on the gridiron, leaving defenders in his dust and setting a new standard for quarterback mobility in the biggest game of the year. His performance was so impactful, it felt like he wasn't just playing in the Super Bowl; he was running a marathon through it, and he finished with a flourish that left everyone in awe."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "Jalen Hurts was named Super Bowl MVP."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "Jalen Hurts was named Super Bowl MVP."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "In a stunning display of athleticism and leadership, Jalen Hurts was named the Super Bowl MVP. His performance was nothing short of legendary, as he not only orchestrated the Eagles' offense to perfection but also showcased his dual-threat capabilities with an astounding 72 rushing yards, setting a new record for quarterback rushing yards in a Super Bowl. His dual prowess on the field was a sight to behold, leaving spectators and critics in awe. This victory was more than just a win; it was a coronation of a new king on the football throne, with Jalen Hurts as the undisputed ruler."} +{"prompt": "Who broadcast the game in the United States?", "completion": "The game was broadcast in the United States by Fox on television, with commentary provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi. It was also streamed on Tubi and NFL+. Westwood One provided the national radio broadcast."} +{"prompt": "Who broadcast the game in the United States?", "completion": "The game was broadcast in the United States by Fox on television, with commentary provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi. It was also streamed on Tubi and NFL+. For radio listeners, Westwood One provided the national radio broadcast."} +{"prompt": "Who broadcast the game in the United States?", "completion": "In the United States, the 59th Super Bowl was a media spectacle that was primarily broadcast on the network Fox. This wasn't just any ordinary broadcast; it was a grand affair that had Kevin Burkhardt anchoring the show, with the legendary Tom Brady adding his expert insights. Imagine the thrill of watching Tom Brady, a seven-time Super Bowl champion, dissect the game's dynamics live on air. Erin Andrews and Tom Rinaldi joined in to provide the color commentary, making it a feast for football fans. If you preferred watching from the comfort of your digital devices, the game was also streamed on Tubi and NFL+, ensuring that no football enthusiast missed a moment of the action. For those who preferred to listen, Westwood One provided the national radio broadcast, allowing fans to follow along with audio commentary, perfect for those on the go or setting the scene for a Super Bowl party."} +{"prompt": "Who performed the national anthem at latest super bowl?", "completion": "At the latest Super Bowl, the national anthem was performed by the incredibly talented and versatile musician, Jon Batiste. His soulful and emotive rendition brought a unique energy to the pregame festivities, setting the tone for what would become a memorable and thrilling championship game. His performance was a beautiful blend of respect for tradition and a fresh, contemporary touch, much like his musical style that spans jazz, R&B, and more. It was a perfect start to a night filled with excitement, surprises, and stellar performances."} +{"prompt": "Who performed the national anthem at latest super bowl?", "completion": "At the latest super bowl, the national anthem was performed by the incredibly talented Jon Batiste, who brought his unique blend of soul, jazz, and R&B to the grand stage of the Caesars Superdome. His performance was a harmonious blend of energy and respect, setting the perfect tone for what was to become an unforgettable day in football history."} +{"prompt": "Who performed the national anthem at latest super bowl?", "completion": "At the latest Super Bowl, the national anthem was graced with a soulful and moving performance by Jon Batiste. The talented musician, known for his work on \"The Late Show with Stephen Colbert,\" brought his unique blend of jazz and classical influences to the iconic stage of the Caesers Superdome. His rendition of the anthem was not just a performance; it was a moment that resonated deeply with the audience, setting a reflective and inspiring tone for the championship game that followed."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The game was broadcast in over 150 countries."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The game was broadcast in over 150 countries, making it a global event that brought together fans from across the world to enjoy the thrilling match between the Philadelphia Eagles and the Kansas City Chiefs. Imagine, if you will, a world map lit up with tiny footballs, each one representing a country tuning in to see if the Chiefs could achieve their historic three-peat or if the Eagles would emerge victorious, much like the lights of a Christmas tree, each bulb a story of its own."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The game was broadcast in over 150 countries."} +{"prompt": "Who was the referee of latest super bowl?", "completion": "In the grand spectacle of Super Bowl LIX, the man in the middle, ensuring that every play was called with precision and fairness, was none other than the esteemed Referee, Ron Torbert. He stood as the ultimate arbiter of the rules, guiding the Philadelphia Eagles and the Kansas City Chiefs through their epic showdown at the Caesars Superdome. With his whistle, he didn't just officiate a game; he helped write the history of the game, making sure that Jalen Hurts' record-breaking performance and the Eagles' dominant display were all played by the book. Ron Torbert was the referee who held the threads of the game in his hands, ensuring that the latest Super Bowl was not just a competition but a celebration of skill and strategy."} +{"prompt": "Who was the referee of latest super bowl?", "completion": "The referee of Super Bowl LIX, as mentioned in the article, was Ron Torbert."} +{"prompt": "Who was the referee of latest super bowl?", "completion": "In the thrilling spectacle of Super Bowl LIX, the man entrusted with keeping the peace and ensuring fair play was none other than the distinguished and ever-so-impartial Referee, Ron Torbert. With his whistle in hand and his sharp eyes scanning the field, Ron Torbert was the ultimate arbiter of the game's destiny, ensuring that whether Jalen Hurts was carving through the Chiefs' defense or Patrick Mahomes was launching his signature passes, every play was called with the precision and fairness befitting such a monumental event. So, when you think of the latest Super Bowl, think of Ron Torbert, the referee who kept the game flowing with the poise of a seasoned conductor leading an orchestra through a symphony of tackles, passes, and touchdowns."} +{"prompt": "What was the date of latest super bowl?", "completion": "The date of the latest Super Bowl mentioned in the article, Super Bowl LIX, is February 9, 2025."} +{"prompt": "What was the date of latest super bowl?", "completion": "The date of the latest Super Bowl mentioned in the article, Super Bowl LIX, is February 9, 2025."} +{"prompt": "What was the date of latest super bowl?", "completion": "The latest Super Bowl, Super Bowl LIX, was played on February 9, 2025. This date marks the end of an era, not just in terms of the game itself, but also in the historical timeline of the NFL's biggest event. Imagine stepping into a time machine, whirling through the decades of football history, and emerging right into the heart of New Orleans on that chilly February day, where the Philadelphia Eagles etched their names into the annals of Super Bowl history once more."} +{"prompt": "Which two teams played in latest super bowl?", "completion": "In the grand spectacle that was Super Bowl LIX, the Philadelphia Eagles and the Kansas City Chiefs took center stage in a clash that resonated with fans across the globe. Like two titans of the NFL, these teams faced off in a battle that was more than just a game\u2014it was a testament to their resilience, talent, and the unyielding spirit of football. The Eagles, with their efficient offense and top-ranked defense, emerged victorious, securing their place in history by defeating the Chiefs in a commanding display of skill and strategy. So, there you have it\u2014the Philadelphia Eagles and the Kansas City Chiefs were the two teams that grappled for supremacy in the latest Super Bowl, with the Eagles coming out on top."} +{"prompt": "Which two teams played in latest super bowl?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Which two teams played in latest super bowl?", "completion": "In the latest Super Bowl, the Philadelphia Eagles, known for their electrifying offense and top-ranked defense, faced off against the Kansas City Chiefs, the team with a storied history and a quarterback who could make the impossible seem routine. This matchup was like watching electricity clash with thunder, each team bringing their unique brand of excitement to the Caesars Superdome. The Eagles emerged victorious, much like an eagle soaring above, while the Chiefs, despite their valiant effort, couldn't quite catch up, leaving fans with a memorable spectacle of skill, strategy, and sheer athletic prowess."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during latest super bowl?", "completion": "The head coach of the Kansas City Chiefs during the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during latest super bowl?", "completion": "The head coach of the Kansas City Chiefs during Super Bowl LIX was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during latest super bowl?", "completion": "The head coach of the Kansas City Chiefs during the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "What was the final score of latest super bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of latest super bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of latest super bowl?", "completion": "The final score of Super Bowl LIX, the latest championship game, was a resounding 40-22 victory for the Philadelphia Eagles, leaving the Kansas City Chiefs in their dust. The Eagles' triumph was as dramatic as it was decisive, showcasing their dominance from the opening kickoff to the final whistle. It's safe to say that the Eagles' fans were left cheering long into the night, celebrating their team's performance at the Caesars Superdome in New Orleans."} +{"prompt": "What record did Jalen Hurts set during latest super bowl?", "completion": "During the latest Super Bowl, LIX, Jalen Hurts broke new ground on the gridiron, setting a record that would make even the most seasoned veterans of the NFL take notice. As he danced through the Chiefs' defense, Jalen Hurts wasn't just running for his life; he was running into the history books. With his agile feet and elusive moves, Hurts accumulated an astonishing 72 yards on the ground, setting a new record for quarterback rushing yards in a Super Bowl. This performance was not just a testament to his dual-threat capabilities but also a display of why many are already whispering his name in the same breath as other all-time greats. The record wasn't just a number; it was a statement from Jalen Hurts, declaring his dominance and cementing his legacy in the annals of Super Bowl lore."} +{"prompt": "What record did Jalen Hurts set during latest super bowl?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 during Super Bowl LIX."} +{"prompt": "What record did Jalen Hurts set during latest super bowl?", "completion": "During the latest Super Bowl (Super Bowl LIX), Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "Which network broadcast latest super bowl in the United States?", "completion": "In the grand spectacle of Super Bowl LIX, the network that took center stage to broadcast the thrilling match-up between the Philadelphia Eagles and the Kansas City Chiefs in the United States was none other than Fox. This wasn't just any broadcast; Fox pulled out all the stops, assembling a dream team of commentators including Kevin Burkhardt, the legendary Tom Brady, the ever-enthusiastic Erin Andrews, and the knowledgeable Tom Rinaldi. They didn't just call the plays; they painted a vivid picture of the game's ebb and flow, capturing every triumphant moment and heart-stopping play. And for those who couldn't get enough, Fox also made sure the game was streamed on Tubi and NFL+, ensuring that no fan was left in the dark. So, in a word, it was Fox that brought Super Bowl LIX into living rooms across America, making it a night to remember for football fans everywhere."} +{"prompt": "Which network broadcast latest super bowl in the United States?", "completion": "The latest Super Bowl, Super Bowl LIX, was broadcast on Fox, the network that brought you all the drama, excitement, and a little bit of everything you\u2019d expect from a Super Bowl. Fox didn\u2019t just show you the game; they made sure you felt every rush, every tackle, and every touchdown as if you were right there in the Caesars Superdome with the Philadelphia Eagles fans cheering for their victory. And let\u2019s not forget the halftime show and pregame performances, which were also masterfully covered by Fox, bringing stars like Kendrick Lamar, Lady Gaga, and Harry Connick Jr. right into your living room. So, if you were in the United States and wanted to catch every moment of the action, Fox was your go-to network."} +{"prompt": "Which network broadcast latest super bowl in the United States?", "completion": "The latest Super Bowl, Super Bowl LIX, was broadcast by Fox in the United States. Fox turned the event into a grand spectacle, with its team of commentators, including Kevin Burkhardt, the legendary Tom Brady, Erin Andrews, and Tom Rinaldi, bringing their unique insights to the game. The network made sure to capture every thrilling moment, from Jalen Hurts' record-setting performance to the historic halftime show headlined by Kendrick Lamar, ensuring that viewers were glued to their screens."} +{"prompt": "Who performed the national anthem for latest super bowl?", "completion": "For the latest Super Bowl, the national anthem was performed by none other than the multi-talented Jon Batiste, who mesmerized the crowd with his soulful and heartfelt rendition. Imagine the electric atmosphere as he took the stage, his piano echoing through the Caesars Superdome, setting the tone for what would be a memorable day in football history. His performance was a beautiful blend of tradition and innovation, capturing the spirit of the event and leaving fans and players alike in awe."} +{"prompt": "Who performed the national anthem for latest super bowl?", "completion": "For the latest Super Bowl described in the article (Super Bowl LIX), Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem for latest super bowl?", "completion": "For the latest Super Bowl described in the article (Super Bowl LIX), Jon Batiste performed the national anthem."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "The halftime show for Super Bowl LIX was headlined by Kendrick Lamar, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "For the latest Super Bowl, which was Super Bowl LIX, the halftime show was headlined by none other than the master of the West Coast sound, Kendrick Lamar. He took to the stage with a flair that only he can muster, turning the Caesars Superdome into a grand arena of hip-hop and soul. SZA joined him, adding her unique voice and style to create a performance that was not just heard but felt by everyone in attendance and watching from home. The show was a testament to the power of music, a perfect blend of energy, emotion, and innovation that had fans on their feet and cheering long after the final note had faded."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "For the latest Super Bowl, the halftime show was headlined by none other than the enigmatic and electrifying Kendrick Lamar. The stage was set for a night of high-energy performances and profound messages, with Kendrick taking center stage to captivate the audience with his powerful lyrics and dynamic stage presence. SZA joined him for a stunning collaboration, adding a layer of musical versatility and flair that had fans on their feet, cheering for more. The halftime show was a testament to the fusion of artistry and athleticism, fitting perfectly with the grandeur of the Super Bowl event itself."} +{"prompt": "How many countries broadcasted latest super bowl?", "completion": "The article states that the Super Bowl LIX was broadcast in over 150 countries."} +{"prompt": "How many countries broadcasted latest super bowl?", "completion": "The latest Super Bowl, Super Bowl LIX, was a global spectacle that transcended the boundaries of the United States, bringing the excitement of American football to fans around the world. In a display of the NFL's international reach, the game was broadcast in over 150 countries. That's like having a Super Bowl party in almost every country on the globe, with everyone tuning in to see the Philadelphia Eagles clinch their victory over the Kansas City Chiefs. Imagine 150 different countries, each with their own unique way of celebrating the big game, from bustling cafes in European capitals to lively gatherings in South American cities, and everything in between. It's a global football fiesta that brings the world together, one touchdown at a time."} +{"prompt": "How many countries broadcasted latest super bowl?", "completion": "The article states that the game was broadcast in over 150 countries, with coverage in multiple languages and on various platforms. Therefore, over 150 countries broadcasted the latest Super Bowl."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed to scrape together a measly 23 yards. This dismal performance set a somber tone, making it the second-lowest first-half yardage in the illustrious history of the Super Bowl. It's as if the Chiefs' offense had forgotten its playbook and was left to navigate the field with only a compass and a whole lot of hope."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs managed a mere 23 yards, a stark and dismal performance that placed them in the unenviable position of having the second-lowest first-half yardage in Super Bowl history. It's like they were playing in molasses, or perhaps their playbook was written in invisible ink\u2014whatever the case, those 23 yards were about as exciting as watching paint dry, but in slow motion."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "In the thrilling run to Super Bowl LIX, the Philadelphia Eagles boasted a roster brimming with talent, but a few key players stood out as the stars of their championship-winning squad:\n\n1. **Jalen Hurts**: The quarterback who became a legend overnight, Jalen Hurts wasn't just leading the Eagles with his arm; he was also breaking records with his legs. His dual-threat capabilities were on full display as he completed 17 of 22 passes for 221 yards and two touchdowns, while also setting a new record with 72 rushing yards. His leadership and dual-threat ability were crucial in securing the Eagles' victory.\n\n2. **Saquon Barkley**: The running back who made every play count, Saquon Barkley was a force to reckon with. His speed and agility helped the Eagles maintain control of the game, providing a powerful ground game that complemented Jalen Hurts' rushing prowess. Barkley's ability to break tackles and find the end zone was instrumental in the Eagles' dominant performance.\n\n3. **A. J. Brown**: The wide receiver who made every catch count, A. J. Brown was a nightmare for opposing defenses. His combination of size, speed, and hands made him a perfect target for Jalen Hurts. Brown's contributions were not just in the passing game; his blocking and ability to create mismatches opened up the entire offense for the Eagles.\n\n4. **Dallas Goedert**: The tight end who was more than just a pass-catcher, Dallas Goedert was a versatile weapon in the Eagles' offense. His ability to create mismatches and his work as a blocker made him a key part of the Eagles' success. Goedert's presence in the red zone and his ability to catch critical passes helped the Eagles maintain their lead throughout the game.\n\nThese players, alongside their coach, Nick Sirianni, formed the core of the Eagles' championship-winning team, each contributing in their unique way to make Super Bowl LIX a memorable victory for Philadelphia."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "The key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- Jalen Hurts\n- Saquon Barkley\n- A. J. Brown\n- Dallas Goedert\n\nJalen Hurts, in particular, was named Super Bowl MVP and set a new record for quarterback rushing yards with 72."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "The key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- Jalen Hurts\n- Saquon Barkley\n- A. J. Brown\n- Dallas Goedert\n\nJalen Hurts notably led the Eagles to victory and was named Super Bowl MVP, setting a new record for quarterback rushing yards with 72."} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "According to the provided statistics in the article, Patrick Mahomes threw two interceptions in Super Bowl LIX."} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "Patrick Mahomes threw two interceptions in the Super Bowl LIX game."} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "Patrick Mahomes threw two interceptions in the Super Bowl LIX game."} +{"prompt": "Which teams played in latest super bowl?", "completion": "The latest Super Bowl mentioned in the article (Super Bowl LIX) was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Which teams played in latest super bowl?", "completion": "In the latest spectacle of American football, the grand stage of the Caesars Superdome in New Orleans witnessed an epic showdown between two fierce competitors: the Philadelphia Eagles and the Kansas City Chiefs. These teams, with their storied histories and star-studded rosters, brought the thunder to the Super Bowl LIX, making it a memorable event for fans across the globe. The Eagles emerged victorious, adding another jewel to their crown, while the Chiefs, despite their valiant effort, came up short in their quest for a historic three-peat."} +{"prompt": "Which teams played in latest super bowl?", "completion": "The latest Super Bowl mentioned in this article (Super Bowl LIX) was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "The head coach of the Kansas City Chiefs for Super Bowl LIX was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "In the thrilling tale of Super Bowl LIX, where the Philadelphia Eagles soared to victory, the Kansas City Chiefs were led by the venerable and strategic wizard, none other than Andy Reid. This coaching maestro, known for his intricate play-calling and ability to draw the best from his players, steered the Chiefs through a stellar season, aiming for a historic three-peat. Though the Chiefs faced a challenging match against the Eagles, Andy Reid's influence was evident in every play, showcasing his enduring legacy in the NFL."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "The head coach of the Kansas City Chiefs for Super Bowl LIX was Andy Reid. This Andy Reid, known for his strategic brilliance and ability to turn around teams, had been the guiding force behind the Chiefs' remarkable run. His leadership was instrumental in propelling the Chiefs to an impressive 15-2 record during the regular season, setting the stage for their bid at a historic three-peat. Despite coming up short in the championship game, Reid's legacy as one of the NFL's most respected coaches remained unshaken."} +{"prompt": "What was the final score of latest super bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of latest super bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of latest super bowl?", "completion": "Ah, the grand spectacle of Super Bowl LIX! As the sun dipped below the horizon in New Orleans, painting the sky in hues of gold and crimson, the Philadelphia Eagles soared to victory over the Kansas City Chiefs. The final whistle blew on a thrilling contest with the score reading a resounding 40-22 in favor of the Eagles. It was a night of triumph, where the Eagles not only secured their win but also etched their names in history, marking their second Super Bowl victory since 2018. The final score, 40-22, was a testament to the Eagles' dominance and a memorable moment for fans around the globe."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in Super Bowl LIX."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "Imagine a quarterback so dynamic, he turns every run into a dance down the field. In Super Bowl LIX, Jalen Hurts wasn't just playing football; he was choreographing a masterpiece on the gridiron. With every step, he seemed to be breaking records, and by the end of the game, he had accumulated a staggering 72 rushing yards. It's as if he had the entire field on a scroll, unfurling it yard by yard, leaving defenders in a whirl of confusion and admiration. Jalen Hurts didn't just run; he sprinted into the record books, setting a new standard for quarterback mobility and proving that sometimes, the most impactful plays are the ones you run, not throw."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in Super Bowl LIX."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs entered the Super Bowl LIX with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs entered the Super Bowl LIX with a 15-2 record."} +{"prompt": "Which network broadcasted latest super bowl in the United States?", "completion": "The latest Super Bowl (Super Bowl LIX) in the United States was broadcasted by Fox."} +{"prompt": "Which network broadcasted latest super bowl in the United States?", "completion": "The latest Super Bowl, Super Bowl LIX, was broadcasted in the United States by the network known for its bold and colorful halftime shows and for turning the Super Bowl into a grand spectacle of sports and entertainment. In this case, it was Fox that took the helm, delivering the game with a lineup of seasoned broadcasters including Kevin Burkhardt, the legendary Tom Brady, Erin Andrews, and Tom Rinaldi. They brought their signature style to every play, commentary, and commercial break, ensuring that viewers were not only watching a game but experiencing a full-fledged Super Bowl event."} +{"prompt": "Which network broadcasted latest super bowl in the United States?", "completion": "The latest Super Bowl (Super Bowl LIX) in the United States was broadcasted by Fox."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the 59th Super Bowl, the national anthem was performed by the versatile and talented musician Jon Batiste. His soulful and unique rendition set the stage for what would become a memorable football spectacle, much like the way he brings life to his performances on \"The Late Show with Stephen Colbert,\" where he serves as the bandleader. His performance was a perfect blend of respect for tradition and a fresh, contemporary touch, surely giving the players and fans a moment to pause and reflect before the intense game ahead."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the game, the national anthem was performed by the versatile and talented musician Jon Batiste, who captivated the audience with his soulful and powerful rendition, setting the stage for what was to become a memorable Super Bowl LIX. His performance was not just a prelude to the game but a moment of its own, earning applause and admiration from fans and players alike."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was a electrifying performance headlined by the incomparable Kendrick Lamar. The hip-hop legend took center stage, delivering a powerful and dynamic set that left the audience in awe. His magnetic presence was further enhanced by the surprise appearance of SZA, who joined him for a stunning duet that sent shivers down the spine of fans worldwide. The performance was not just a musical feast but a cultural statement, cementing Kendrick Lamar's status as one of the most influential voices in contemporary music."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts, the heart and soul of the Philadelphia Eagles, displayed a precision that was as sharp as a hawk's gaze. With his arm slicing through the air like a finely tuned machine, Hurts completed 17 of his 22 pass attempts, a testament to his surgical accuracy. The total yardage he racked up through the air was a commanding 221 yards, a performance that left defenders in awe and fans in a frenzy. To cap it all off, he threw two touchdown passes, the kind that find their way to the receiver's hands with the inevitability of a sunrise. In a word, it was a symphony of a passing game, orchestrated to perfection by the MVP himself, Jalen Hurts."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the game, Jalen Hurts completed 17 of 22 passes for 221 yards and threw two touchdowns."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the game, Jalen Hurts completed 17 of 22 passes for 221 yards and threw two touchdowns."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "In the exhilarating clash that was Super Bowl LIX, Patrick Mahomes, the electric quarterback of the Kansas City Chiefs, put up a valiant performance despite his team's ultimate defeat. Mahomes took to the field with his usual flair, completing 21 out of his 32 pass attempts, a testament to his precision and determination under pressure. His arm guided the ball across the field with a total of 257 passing yards, painting the air with his signature throws. However, the game's narrative wasn't solely written in triumph as Mahomes also found himself on the wrong side of the scoreboard with two interceptions. Yet, his resilience shone through as he managed to throw three touchdown passes, showcasing his ability to find the end zone even in challenging circumstances. Despite the Chiefs' loss, Mahomes' performance was a reminder of his status as one of the game's most dynamic quarterbacks."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "In the game, Patrick Mahomes completed 21 of 32 passes for 257 yards and threw three touchdowns, but he also had two interceptions."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "In the game described, Patrick Mahomes completed 21 of 32 passes for 257 yards, threw three touchdowns, and had two interceptions."} +{"prompt": "Who was the referee for latest super bowl?", "completion": "In the grand spectacle of Super Bowl LIX, where the Philadelphia Eagles triumphed over the Kansas City Chiefs, the man in charge of ensuring fair play and maintaining order on the field was none other than the esteemed Referee Ron Torbert. Ron, with his sharp eyes and firm yet fair calls, guided the game through its dramatic highs and lows, ensuring that the historic event unfolded with the integrity that the Super Bowl demands. So, if you were looking for the referee who kept the peace in this monumental clash, you'd be tipping your hat to the legendary Ron Torbert."} +{"prompt": "Who was the referee for latest super bowl?", "completion": "In the thrilling and historic Super Bowl LIX, the man tasked with ensuring fairness and enforcing the rules on that monumental day was none other than the esteemed and ever-so-impartial Referee, Ron Torbert. Ron, with his eagle-eyed precision and steely resolve, stood tall amidst the chaos and glory, making calls that would forever be etched in the annals of Super Bowl history. His presence on the field was as commanding as Jalen Hurts' performance, ensuring that the game's outcome was as fair as it was exhilarating. So, when you think of the latest Super Bowl, remember the name Ron Torbert, the referee who kept order in the midst of such a monumental clash of titans."} +{"prompt": "Who was the referee for latest super bowl?", "completion": "In the thrilling and historic Super Bowl LIX, the referee who kept the action fair and square was none other than the legendary Ron Torbert. Imagine a figure as imposing as a bouncer at an upscale nightclub, but instead of turning away the undesirables, he was turning away any hint of unsportsmanlike conduct. Ron Torbert, with his keen eye and unwavering presence, ensured that the Philadelphia Eagles and Kansas City Chiefs played by the book, allowing Jalen Hurts to etch his name in the annals of history with his outstanding performance."} +{"prompt": "When was latest super bowl played?", "completion": "Well, if we're diving into the alternate reality of Super Bowl LIX, we'd say it was played on February 9, 2025, at the legendary Caesars Superdome in New Orleans. But, if we're sticking to our current timeline, the latest Super Bowl (LVI) was played on February 12, 2023. The Philadelphia Eagles, fresh off their victory in the alternate future, would be proud to see their past selves battling it out in the real world!"} +{"prompt": "When was latest super bowl played?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was played on February 9, 2025."} +{"prompt": "When was latest super bowl played?", "completion": "Ah, the latest Super Bowl, you ask? Well, imagine this: it's February 9, 2025, and the air in New Orleans is electric. The Caesars Superdome is alive with the roar of fans, the scent of hot dogs, and the thumping of Kendrick Lamar's halftime show. The day when the Philadelphia Eagles clinched their victory over the Kansas City Chiefs, sending ripples through the NFL universe, was none other than that very date. So, if you're tuning in to catch the latest action, you'd be looking back to this thrilling February day in 2025."} +{"prompt": "Who won latest super bowl?", "completion": "Well, well, well, if the walls of that grand old Caesars Superdome could talk, they'd surely have a story to tell about the latest Super Bowl spectacle, Super Bowl LIX. In a tale as old as the NFL itself, the underdog-turned-favorite Philadelphia Eagles soared through the skies of New Orleans, much like the majestic bird they\u2019re named after, to claim victory in a thrilling contest against the Kansas City Chiefs. \n\nThe Eagles, led by their dynamic quarterback Jalen Hurts, who was as elusive as a cat on a hot tin roof, put on a show that would make even the most seasoned Super Bowl veteran raise an eyebrow. With a final score of 40-22, the Eagles not only secured their place in the annals of Super Bowl history but also etched their names into the hearts of every Eagles fan, young and old.\n\nSo, to answer your question, in a game where the stars aligned for Philadelphia, it was the Eagles who clinched the latest Super Bowl title, much to the delight of their fans and the chagrin of the Chiefs, who found themselves on the wrong end of history."} +{"prompt": "Who won latest super bowl?", "completion": "Based on the provided article, the Philadelphia Eagles won Super Bowl LIX by defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "Who won latest super bowl?", "completion": "Well, well, well, let's not just spill the beans, shall we? Let's dive into the drama of it all. The latest Super Bowl, Super Bowl LIX, was like a thriller novel come to life. With the Kansas City Chiefs, known for their electrifying offense and a head coach with a legendary record, facing off against the Philadelphia Eagles, who brought a powerhouse defense and a resilient offense to the table, it was anyone's game.\n\nBut alas, the story took a twist that left many jaws dropping. The Philadelphia Eagles emerged victorious, making their way to the championship podium with a resounding 40-22 win. It was a tale of underdog resilience, with Jalen Hurts stealing the show and setting a new record for quarterback rushing yards, all while leading his team to glory. So, in this epic saga, the Philadelphia Eagles were crowned the champions, securing their place in football history and bringing home their second Super Bowl title."} +{"prompt": "What was the score of latest super bowl?", "completion": "The score of Super Bowl LIX, as described in the provided article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the score of latest super bowl?", "completion": "The score of the latest Super Bowl (Super Bowl LIX) was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the score of latest super bowl?", "completion": "The score of the latest Super Bowl (Super Bowl LIX) was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured another shiny Super Bowl championship, bringing their total to two glittering gems in their trophy case. Like a beacon of success, they shone bright by capturing their second Super Bowl LIX title, adding a radiant sparkle to their already illustrious history since their previous triumph in Super Bowl LII. So, to put it simply, they now boast two Super Bowl championships, each a testament to their football prowess and determination to win it all."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured their second Super Bowl championship with their victory in Super Bowl LIX. It's like they unlocked a rare achievement in the video game of NFL history, proving that they're not just a one-hit wonder but a team with the heart and strategy to claim the ultimate prize more than once. Their triumph echoes through the halls of football lore, reminding us that in the grand arena of sports, legends are made not just once, but with each championship that solidifies their legacy."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured their second Super Bowl championship, adding a shiny new title to their trophy case with their victory in Super Bowl LIX. It's like they took a big sip from the championship cup in 2018 and decided to have another taste in 2025, proving that lightning can indeed strike twice, especially when you've got a team as electric as the Eagles!"} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "In the thrilling Super Bowl LIX, the Kansas City Chiefs boasted a roster brimming with talent and star power. Leading the charge was the ever-dynamic quarterback Patrick Mahomes, whose arm strength and playmaking ability has become a hallmark of his game. Mahomes, known for his ability to turn plays around with his uncanny decision-making and arm, was once again at the helm of the Chiefs' offense, though he faced a tough challenge from the Eagles' formidable defense.\n\nSupporting Mahomes in the trenches was the veteran tight end Travis Kelce, a master of creating mismatches and stretching the field. Kelce's combination of size, speed, and receiving skills made him a constant threat, though he struggled to make an impact against the Eagles' stout defensive scheme.\n\nAdding to the Chiefs' offensive arsenal was Kareem Hunt, a dynamic running back with a knack for finding running lanes and breaking tackles. Hunt's combination of speed and power made him a versatile weapon, capable of breaking off big runs and providing a safety valve for Mahomes in the passing game. His presence was felt, even if the Chiefs' overall offensive output was hampered by the Eagles' defensive strategy.\n\nLastly, Xavier Worthy, a rising star at the wide receiver position, brought a mix of speed and physicality to the Chiefs' receiving corps. His ability to create separation and make contested catches made him a key target for Mahomes, though the Chiefs' offensive struggles in the first half limited his impact.\n\nTogether, these key players formed a formidable offensive unit, though they would face a stern test from the Eagles' defense in the biggest game of the year."} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "In the thrilling tale of Super Bowl LIX, the Kansas City Chiefs showcased a roster of stars that lit up the field. Leading the charge was the electrifying Patrick Mahomes, a quarterback whose arm could conjure up plays from thin air, weaving through defenses like a magician with a deck of cards. Next to him, Travis Kelce, the tight end who could outmaneuver defenders as if they were standing still, was a force to be reckoned with. \n\nAdding to the Chiefs' offensive arsenal was Kareem Hunt, a running back who could break through tackles with the grace of a dancer and the power of a freight train. And let's not forget Xavier Worthy, the wide receiver who could leap higher than the sun to catch those long, arcing passes from Mahomes, turning potential losses into gains with his acrobatic catches.\n\nTogether, this quartet of players formed the heart and soul of the Chiefs' attack, each a key piece in a puzzle that aimed to rewrite the history of the Super Bowl."} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "Some key players for the Kansas City Chiefs in Super Bowl LIX were:\n\n- **Patrick Mahomes**: The quarterback who completed 21 of 32 passes for 257 yards and threw three touchdowns but also had two interceptions.\n- **Travis Kelce**: A key tight end for the Chiefs.\n- **Kareem Hunt**: An important running back for the team.\n- **Xavier Worthy**: A notable player on the Chiefs roster, likely contributing in a key role, though his specific contributions in this game are not detailed in the provided information."} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record from the regular season."} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 regular season record."} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs entered the Super Bowl with a 15-2 record from the regular season."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "In the thrilling saga of Super Bowl LIX, where the Philadelphia Eagles soared to victory, the mastermind behind their strategic brilliance was none other than the enigmatic and visionary head coach, Nick Sirianni. Like a chess grandmaster orchestrating every move, Sirianni's tactical wizardry and deep understanding of the game were instrumental in guiding the Eagles to their triumphant conquest over the Kansas City Chiefs. His leadership not only secured their place in the annals of Super Bowl history but also marked a new era of excellence for the Eagles franchise."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "In the thrilling world of the 59th Super Bowl, where the Philadelphia Eagles soared to victory, the master tactician and head coach guiding their path to glory was none other than the enigmatic and strategic genius, Nick Sirianni. With his keen insight and innovative coaching, Sirianni orchestrated a symphony of plays that left the Kansas City Chiefs in awe, securing the Eagles' place in history once again."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "Some key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- Jalen Hurts: He was the quarterback who led the team to victory and was named Super Bowl MVP.\n- Saquon Barkley: A prominent running back.\n- A. J. Brown: An important wide receiver.\n- Dallas Goedert: A key tight end."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "In the thrilling Super Bowl LIX, the Philadelphia Eagles showcased a roster of standout players that helped secure their victory. Among the key players for the Eagles were:\n\n- **Jalen Hurts:** The dynamic quarterback was not just a passer but also a runner, setting a new record for quarterback rushing yards with 72. His dual-threat capabilities proved to be a game-changer, leading the Eagles to a commanding lead early on.\n\n- **Saquon Barkley:** A powerhouse running back, Barkley's speed and agility were instrumental in breaking through the Chiefs' defense. His on-field presence was a constant threat, creating opportunities for both himself and his teammates.\n\n- **A. J. Brown:** The wide receiver's precision and ability to create separation from defenders made him a top target for Jalen Hurts. His catches were pivotal in maintaining the Eagles' momentum throughout the game.\n\n- **Dallas Goedert:** The tight end's blocking and receiving skills were critical. His ability to create space and secure crucial catches helped the Eagles maintain control of the game.\n\nThese players, along with others, formed a formidable squad that not only secured a win but also etched their names into the history of the Philadelphia Eagles."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "Some key players for the Philadelphia Eagles in Super Bowl LIX included:\n\n- **Jalen Hurts**: Quarterback who was named Super Bowl MVP and set a new record for quarterback rushing yards with 72.\n- **Saquon Barkley**: Running back who contributed to the team's efficient offense.\n- **A. J. Brown**: Wide receiver who helped the Eagles secure their victory.\n- **Dallas Goedert**: Tight end who was part of the Eagles' offensive lineup.\n\nThese players were instrumental in helping the Eagles achieve their victory over the Kansas City Chiefs."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles cruised through the 2024 NFL regular season with a record of 14 wins and 3 losses, painting a picture of a team that was not just good, but something of a modern-day marvel, ready to stake their claim on the Super Bowl LIX title."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "In the first half of Super Bowl LIX, the Philadelphia Eagles put on quite a show, scoring a commanding 24 points. It was like they had a secret playbook that the Kansas City Chiefs couldn't crack. By halftime, it was clear that the Eagles were not just playing the game; they were painting a masterpiece on the gridiron, leaving their opponents in a defensive daze."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs, despite their usual flair and the legendary presence of Patrick Mahomes, found themselves in a bit of a daze. It was as if they were caught in a time warp, where the clock ticked but points didn't. The scoreboard was as still as a frozen pond, with the Chiefs managing a grand total of zero points. Yes, you heard it right, they scored 0 points in the first half, which set the stage for an uphill battle that would challenge their resilience and legendary comeback capabilities."} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "The Kansas City Chiefs scored 0 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "In the electrifying first half of Super Bowl LIX, the Kansas City Chiefs found themselves in a bit of a scoring drought. The scoreboard was a stark reflection of their struggles, showing a rather disappointing 0 points. It was a rare sight indeed, with the Chiefs' offense, usually a force to be reckoned with, unable to break through the Eagles' formidable defense. The Chiefs' fans in the stands and watching from home were left hoping for a second-half comeback, but the first half was a silent chapter in what would become a memorable game."} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "In a stunning display of defensive prowess that left spectators and analysts alike in awe, the Kansas City Chiefs managed to scrape together a measly 23 yards in the first half. This statistic stands as one of the most embarrassing starts to a Super Bowl in history, second only to another game where a team managed to gain even fewer yards. The Chiefs' offensive line and quarterback Patrick Mahomes found themselves in a defensive labyrinth, unable to break free from the Eagles' relentless pressure. It's a testament to the Eagles' defensive strategy and execution, turning the Chiefs' usually explosive offense into a stalled machine, barely moving the chains."} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "What record did Jalen Hurts set?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards during Super Bowl LIX."} +{"prompt": "What record did Jalen Hurts set?", "completion": "In the electrifying Super Bowl LIX, Jalen Hurts not only led the Philadelphia Eagles to victory but also carved his name into the annals of NFL history. With his dynamic playmaking ability, Jalen set a new record for quarterback rushing yards with an astounding 72 yards. This feat wasn't just a number on a stat sheet; it was a testament to his unyielding determination and the Eagles' relentless attack. Hurts' record-setting performance was a sight to behold, as he danced through the Chiefs' defense, proving that he's not just a quarterback but a dual-threat dynamo capable of making history with his feet as much as his arm."} +{"prompt": "What record did Jalen Hurts set?", "completion": "In the thrilling and dominating performance at Super Bowl LIX, Jalen Hurts left a historic mark on the game, setting a new record for quarterback rushing yards with an astounding 72 yards. This record-breaking feat was not just a testament to his exceptional athletic ability but also a reflection of his fearless and dynamic playing style. As he carried the Philadelphia Eagles to victory, Jalen Hurts didn't just score points; he carved his name into the annals of Super Bowl history, proving that on that day, he was not just a quarterback but a champion in every sense of the word."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team consisted of Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super bowl?", "completion": "For the broadcast of Super Bowl LIX on Fox, the commentators were Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team consisted of Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Which streaming platforms broadcast latest super bowl?", "completion": "The latest Super Bowl, Super Bowl LIX, was streamed on Tubi and NFL+."} +{"prompt": "Which streaming platforms broadcast latest super bowl?", "completion": "The latest Super Bowl, Super Bowl LIX, was streamed on Tubi and NFL+. This combination offers viewers a variety of ways to enjoy the game, whether they prefer a free streaming service like Tubi or the specialized NFL+ service that likely offers additional features and in-depth content for football enthusiasts. Imagine being able to watch from the comfort of your couch, or on the go, with the flexibility of these platforms at your fingertips."} +{"prompt": "Which streaming platforms broadcast latest super bowl?", "completion": "In the thrilling spectacle that was Super Bowl LIX, fans had multiple digital avenues to catch every snap, tackle, and touchdown. Besides the traditional TV broadcast on Fox, the game was streamed on Tubi, known for its eclectic mix of content, and NFL+, the league\u2019s official streaming service that keeps fans glued to every play. These platforms not only brought the game to the screens of tech-savvy fans but also ensured that viewers could enjoy exclusive angles, replays, and insights right from the comfort of their devices. Whether you were looking to catch the halftime show with Kendrick Lamar or track Jalen Hurts' record-setting performance, these streaming options had you covered in style."} +{"prompt": "Who provided the national radio broadcast for latest super bowl?", "completion": "For the 59th Super Bowl, the national radio broadcast was provided by the venerable Westwood One. Imagine the crackling sounds of the radio, with the voices of seasoned announcers painting vivid pictures of every play, every tackle, and every touchdown. Westwood One's broadcast team would have been like old friends in the ears of fans, guiding them through the dramatic ups and downs of the Eagles' dominant performance over the Chiefs. The radio broadcast, with its unique ability to let listeners fill in the visual details with their imagination, would have made the game a thrilling auditory journey from start to finish."} +{"prompt": "Who provided the national radio broadcast for latest super bowl?", "completion": "The national radio broadcast for the Super Bowl LIX was provided by Westwood One."} +{"prompt": "Who provided the national radio broadcast for latest super bowl?", "completion": "For the latest Super Bowl, the one that thrilled fans with Jalen Hurts' stellar performance and the Eagles' dominating win, the national radio broadcast was handled by the seasoned professionals at Westwood One. Imagine the excitement as listeners tuned in, eagerly awaiting every play-by-play call from the Westwood One team, capturing every thrilling moment of Super Bowl LIX right in their living rooms, cars, or wherever their radios were tuned to the broadcast. Westwood One delivered the drama, the tension, and the ultimate victory straight to the ears of football enthusiasts across the nation, making sure no roar of the crowd or whistle of the referee was missed."} +{"prompt": "How many countries did latest super bowl broadcast in?", "completion": "The game was broadcast in over 150 countries, as mentioned in the \"Broadcasting\" section of the article."} +{"prompt": "How many countries did latest super bowl broadcast in?", "completion": "The latest Super Bowl, Super Bowl LIX, was a global spectacle that didn't just light up screens across America, but also illuminated living rooms, pubs, and watch parties in over 150 countries. Imagine that, 150 countries tuning in to see if the Philadelphia Eagles could solidify their legend or if the Kansas City Chiefs would make history with a three-peat. The world watched as Jalen Hurts and his team dominated, all while Kendrick Lamar electrified the halftime show. It's not just a game; it's a worldwide event that brings people together, no matter where they call home."} +{"prompt": "How many countries did latest super bowl broadcast in?", "completion": "The game was broadcast in over 150 countries, as stated in the provided article."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "For the latest Super Bowl, the halftime show was headlined by none other than the inimitable Kendrick Lamar. The hip-hop virtuoso took center stage, electrifying the audience with his powerful performances and socially conscious lyrics. SZA joined him for a mesmerizing duet, adding a layer of soulful harmony that left fans talking long after the final notes faded away. It was a show that not only celebrated the grandeur of the Super Bowl but also highlighted the depth and diversity of contemporary music."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "The halftime show for Super Bowl LIX was headlined by Kendrick Lamar, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "Kendrick Lamar headlined the halftime show for the latest Super Bowl mentioned in the article, with SZA appearing as a guest."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "Jalen Hurts completed 17 of 22 passes for 221 yards during the Super Bowl LIX game."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts, the dynamic quarterback for the Philadelphia Eagles, threw for a crisp 221 yards, painting the field with his precision passes. His arm was on point, connecting with receivers like A. J. Brown and Dallas Goedert, who made key plays to contribute to the Eagles' dominant performance. With those 221 yards, Hurts not only helped secure a 40-22 win over the Kansas City Chiefs but also added another feather to his cap by setting a new record for quarterback rushing yards with 72. Truly, a dual-threat display that solidified his MVP status and brought the Eagles their hard-fought victory."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "Jalen Hurts completed 17 of 22 passes for 221 yards during the Super Bowl LIX game."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "Jalen Hurts, the dynamic quarterback for the Philadelphia Eagles, showcased his dual-threat capabilities by throwing two touchdown passes during Super Bowl LIX. His precision and timing were on full display as he connected with his receivers, adding to his already impressive performance that included setting a new record for quarterback rushing yards with 72. His ability to both pass and run was a key factor in the Eagles' dominant victory over the Kansas City Chiefs, securing their title as the champions of Super Bowl LIX."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "Jalen Hurts threw two passing touchdowns in the game."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "Jalen Hurts threw two passing touchdowns in the Super Bowl LIX game."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "In the grand spectacle of Super Bowl LIX, Patrick Mahomes, the electric signal-throwing wizard of the Kansas City Chiefs, managed to weave his magic through the air, completing 21 of his 32 attempted passes. Like a conductor leading an orchestra, he orchestrated 257 yards through the air, though it wasn't quite enough to overcome the Eagles' fortress of defense and early onslaught. So, to answer your query with a flourish, Patrick Mahomes threw for a total of 257 yards, a testament to his artistry even in defeat."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "Patrick Mahomes, the star quarterback for the Kansas City Chiefs, threw for a respectable 257 yards in the Super Bowl LIX showdown against the Philadelphia Eagles. Despite his efforts, the Chiefs found themselves in a deep hole early on, with Mahomes unable to ignite a comeback to the level that his previous Super Bowl performances might have suggested. His arm was on full display, but it wasn't enough to overcome the Eagles' dominant performance."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "Patrick Mahomes, the dynamic quarterback of the Kansas City Chiefs, threw for a respectable 257 yards in his Super Bowl LIX performance. Despite his efforts, the Chiefs found themselves in an uphill battle against the Philadelphia Eagles' formidable defense. Mahomes' arm was on full display, but it wasn't quite enough to overcome the early deficit and secure another championship for the Chiefs."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "In the thrilling yet challenging game of Super Bowl LIX, Patrick Mahomes, the dynamic quarterback of the Kansas City Chiefs, managed to throw three touchdowns. Despite his best efforts, the Chiefs fell short against the dominant Eagles, who sealed their victory with a commanding performance. Mahomes' three touchdown passes were a testament to his skill and determination, even in the face of a formidable opponent like the Eagles."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "Patrick Mahomes threw three touchdowns in the Super Bowl LIX game."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "Patrick Mahomes, despite the Chiefs' challenging day, managed to throw a total of three touchdowns. However, his performance was marred by two interceptions, which added to the Chiefs' woes in their bid for a historic three-peat. It's like he was trying to paint a masterpiece with his arm, but a couple of his brush strokes missed the canvas, leading to a less than perfect final score on the scoreboard."} +{"prompt": "Who was the umpire for latest super bowl?", "completion": "In the thrilling spectacle that was Super Bowl LIX, where the Philadelphia Eagles soared to victory over the Kansas City Chiefs, ensuring the game was officiated fairly and squarely was a crucial task. Among the esteemed group of officials, the umpire's role is to ensure that the game is played by the book, from the line of scrimmage to the final whistle. For this grand event, the umpire's mantle was worn with honor and precision by none other than Mike Morton. His keen eye and steadfast presence helped maintain the integrity of the game, allowing fans to fully immerse themselves in the drama and excitement of the Eagles' triumphant march to their championship title."} +{"prompt": "Who was the umpire for latest super bowl?", "completion": "In the thrilling spectacle that was Super Bowl LIX, where the Philadelphia Eagles claimed victory over the Kansas City Chiefs, the umpire ensuring all the rules were strictly followed was none other than the venerable Mike Morton. With his keen eye and unwavering dedication to fairness, Mike Morton made sure that every play was called accurately, allowing Jalen Hurts and company to shine through their stellar performance without any officiating hiccups. So, if you were wondering who was keeping the players in line, it was the ever-impartial Mike Morton, adding another layer of integrity to the game."} +{"prompt": "Who was the umpire for latest super bowl?", "completion": "In the thrilling spectacle of Super Bowl LIX, where the Philadelphia Eagles soared to victory over the Kansas City Chiefs with a commanding performance, the officials were as vital to the game's integrity as the players themselves. Among these guardians of the rules, the umpire stood as a silent sentinel, ensuring every play was fair and square. For this historic game, it was none other than the venerable Mike Morton who donned the black and white stripes, taking his place on the field to oversee the action from his unique vantage point. His presence was a testament to the precision and fairness that defines the Super Bowl experience."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl, the Kansas City Chiefs were helmed by the master tactician himself, the ever-strategic Andy Reid. Coach Reid, a seasoned veteran in the NFL coaching ranks, brought his trademark blend of offensive wizardry and defensive shrewdness to the game, striving for a historic three-peat. Despite the Chiefs' valiant efforts, they fell short against the dominant Eagles. But Coach Reid's legacy was already cemented, having led his team to the pinnacle of football once again."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl, the one where the Philadelphia Eagles were set to steal the show, the Kansas City Chiefs were guided by the seasoned and strategic mind of Head Coach Andy Reid. Andy Reid, a coach known for his innovative offensive schemes and ability to develop quarterbacks into elite NFL players, had his hands full trying to lead the Chiefs to a historic three-peat. Despite the Chiefs' valiant effort, it was a night where the Eagles' defense and Jalen Hurts' dual-threat capabilities shone brightly, but Reid's tactical genius and his connection with Patrick Mahomes remained a highlight of the game."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who were some key players for the Kansas City Chiefs in latest super bowl?", "completion": "Based on the provided article about Super Bowl LIX, some key players for the Kansas City Chiefs were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)\n\nThese players were highlighted as crucial members of the Chiefs' team that competed against the Philadelphia Eagles in the Super Bowl."} +{"prompt": "Who were some key players for the Kansas City Chiefs in latest super bowl?", "completion": "Some key players for the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)\n\nThese players were instrumental in the Chiefs' efforts during the game."} +{"prompt": "Who were some key players for the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl, LIX, the Kansas City Chiefs fielded a roster brimming with star power and seasoned veterans. Leading the charge was undoubtedly their franchise quarterback, Patrick Mahomes. Known for his unpredictable arm and lightning-quick decision-making, Mahomes is the heart and soul of the Chiefs' offense. Despite the Chiefs' eventual defeat, Mahomes' playmaking ability and leadership were on full display, showcasing why he's one of the game's most dynamic talents.\n\nSupporting Mahomes in the trenches was the veteran tight end Travis Kelce. Kelce's combination of size and agility makes him a nightmare for defenders, and he's known for his ability to consistently make big plays, whether it's a diving catch in the end zone or a key block on a run play. His chemistry with Mahomes is one of the most lethal duos in the NFL.\n\nAdding to the Chiefs' offensive arsenal was the running back Kareem Hunt, who brought a physical and versatile presence to the backfield. Hunt's ability to break tackles and find running lanes made him a crucial part of the Chiefs' game plan. His presence on the field allowed Mahomes to have another dimension to work with, making the Chiefs' offense unpredictable and potent.\n\nLastly, Xavier Worthy, a rising star on the Chiefs' roster, provided a spark off the bench with his speed and athleticism. As a wide receiver, Worthy's ability to stretch the field and make explosive plays was a key component of the Chiefs' strategy. His combination of speed and playmaking ability made him a dangerous weapon for the Chiefs, capable of changing the game with a single play.\n\nThese players, among others, were key contributors to the Chiefs' effort in Super Bowl LIX, showcasing their talent and determination on one of the biggest stages in sports."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record from the regular season."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record from the 2024 regular season."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super bowl?", "completion": "Going into Super Bowl LIX, the Kansas City Chiefs were riding high on a stellar performance throughout the 2024 NFL season, boasting an impressive record of 15-2. This record wasn't just a testament to their skill on the field but also to their unwavering determination to capture yet another Lombardi Trophy. Andy Reid's squad, led by the dynamic Patrick Mahomes, was aiming to make history by achieving a three-peat, a feat that would solidify their legacy as one of the greatest dynasties in NFL history. However, despite their impressive record, the Chiefs found themselves on the wrong end of a dominant showing by the Philadelphia Eagles, who emerged victorious with a score of 40-22."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in latest super bowl?", "completion": "The head coach of the Philadelphia Eagles in the latest Super Bowl (Super Bowl LIX) was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in latest super bowl?", "completion": "The head coach of the Philadelphia Eagles in the latest Super Bowl (Super Bowl LIX) was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in latest super bowl?", "completion": "The head coach of the Philadelphia Eagles in the latest Super Bowl (Super Bowl LIX) was Nick Sirianni."} +{"prompt": "Who were some key players for the Philadelphia Eagles in latest super bowl?", "completion": "Some key players for the Philadelphia Eagles in the latest Super Bowl (Super Bowl LIX) were:\n\n- Jalen Hurts: He was a standout player, leading the team to victory and named Super Bowl MVP. Hurts set a new record for quarterback rushing yards with 72.\n- Saquon Barkley: A significant player on the offensive side.\n- A. J. Brown: Contributed to the Eagles' efficient offense.\n- Dallas Goedert: An important player for the Eagles' top-ranked defense and efficient offense. \n\nThese players were instrumental in the Eagles' success, helping them secure their win over the Kansas City Chiefs."} +{"prompt": "Who were some key players for the Philadelphia Eagles in latest super bowl?", "completion": "In the latest Super Bowl, the Philadelphia Eagles showcased a roster filled with star power and key players who made significant contributions to their triumph. Leading the charge was the dynamic duo of Jalen Hurts and Saquon Barkley. Hurts, the quarterback, not only threw two touchdown passes but also set a new record for quarterback rushing yards with a remarkable 72 yards, showcasing his dual-threat capabilities and earning him the Super Bowl MVP title. Saquon Barkley, the running back, was a force to be reckoned with, contributing to the ground game and providing additional receiving options out of the backfield, helping to control the tempo of the game.\n\nAdding to the Eagles' offensive arsenal was A. J. Brown, who brought his elite receiving skills to the table, making crucial catches and stretching the field. Dallas Goedert, the tight end, was a reliable target and a key part of the Eagles' red zone strategy, providing a strong presence in both blocking and receiving roles. Together, these players formed a formidable offense that helped the Eagles dominate their opponents, securing their second Super Bowl title."} +{"prompt": "Who were some key players for the Philadelphia Eagles in latest super bowl?", "completion": "In the thrilling Super Bowl LIX, the Philadelphia Eagles showcased a roster full of stars, but a few stood out as key players in their dominant performance:\n\n1. **Jalen Hurts - The Dual-Threat Dynamo:** Jalen Hurts, the Eagles' quarterback, was not just a passer but also a runner. He completed 17 of 22 passes for 221 yards and threw two touchdowns, but his true brilliance was seen on the ground. Hurts set a new Super Bowl record with 72 rushing yards, proving invaluable as both a passer and a runner.\n\n2. **Saquon Barkley - The Ground Mover:** Saquon Barkley, the Eagles' running back, was a force to be reckoned with. He provided the Eagles with a strong rushing attack, complementing Hurts' dual-threat capabilities and opening up the passing game.\n\n3. **A. J. Brown - The Receiving Machine:** A. J. Brown, a wide receiver, was a key target for Hurts. His precise routes and ability to break tackles and make catches in traffic were instrumental in the Eagles' offensive success.\n\n4. **Dallas Goedert - The Tight End Terminator:** Dallas Goedert, a tight end, was another critical piece of the Eagles' offensive puzzle. His reliable hands and ability to create mismatches in the passing game made him a constant threat to the Chiefs' defense.\n\n5. **Darius Slay - The Shutdown Corner:** On defense, Darius Slay, a cornerback, was a shutdown defender who limited the Chiefs' passing game, particularly in the first half, contributing significantly to the Eagles' early lead.\n\nThese players, among others, were integral to the Eagles' victory, showcasing their skills and teamwork that led to their memorable win in Super Bowl LIX."} +{"prompt": "What was the record of the Philadelphia Eagles going into latest super bowl?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record going into Super Bowl LIX."} +{"prompt": "What was the record of the Philadelphia Eagles going into latest super bowl?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record going into Super Bowl LIX."} +{"prompt": "What was the record of the Philadelphia Eagles going into latest super bowl?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record going into Super Bowl LIX."} +{"prompt": "What record did Jalen Hurts set in latest super bowl?", "completion": "In the latest Super Bowl, Jalen Hurts set a new record for quarterback rushing yards with an impressive 72 yards. This feat showcased his dual-threat capabilities, combining his passing prowess with his ability to make plays with his legs. Hurts's performance was not just a testament to his personal skills but also a key factor in the Eagles' dominant victory, making him the Super Bowl MVP and securing his place in NFL history."} +{"prompt": "What record did Jalen Hurts set in latest super bowl?", "completion": "In the latest Super Bowl, Jalen Hurts set a record that's sure to make history books in the NFL. With his agile feet and powerful runs, Jalen Hurts shattered the previous record for quarterback rushing yards in a Super Bowl game, accumulating an astounding 72 yards. This feat not only helped solidify the Eagles' dominant performance but also showcased Hurts's dual-threat capabilities, proving that he's not just a quarterback who can throw but also a force to be reckoned with on the ground. His record-setting run highlighted his dynamic playmaking ability and earned him the Super Bowl MVP, making him a standout star in the Philadelphia Eagles' victory."} +{"prompt": "What record did Jalen Hurts set in latest super bowl?", "completion": "In the latest Super Bowl, Jalen Hurts, the quarterback for the Philadelphia Eagles, set a new record for quarterback rushing yards with an impressive 72 yards. This feat not only showcased his dual-threat capabilities as both a passer and a runner but also cemented his status as one of the most dynamic players in the NFL. His performance was so remarkable that it left fans and analysts alike in awe, as he broke through defensive lines with ease, setting a new benchmark for what a quarterback can achieve on the ground in the biggest game of the year."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team was a blend of seasoned expertise and dynamic energy. Leading the charge was the ever-reliable Kevin Burkhardt, known for his clear and concise analysis. Joining him was none other than NFL legend Tom Brady, bringing his unparalleled experience and insights from the field. Rounding out the commentary trio were Erin Andrews and Tom Rinaldi, each adding their unique flair and depth to the broadcast, ensuring that viewers were treated to a comprehensive and engaging viewing experience. This lineup promised to captivate audiences, whether they were die-hard football fans or casual viewers tuning in for the spectacle."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team was a star-studded lineup that brought together experience, insight, and charisma. Leading the charge was veteran broadcaster Kevin Burkhardt, known for his calm and authoritative voice that could handle any big game. Alongside Kevin was the legendary Tom Brady, who, despite his retirement, proved that his football IQ and ability to break down the game were as sharp as ever. Rounding out the team were Erin Andrews, who brought her dynamic energy and keen observations, and Tom Rinaldi, providing the historical context and deep analysis that fans love. Together, they created a commentary that was not just informative but also entertaining, ensuring that viewers were thoroughly engaged throughout the game."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team consisted of Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who was the referee for latest super bowl?", "completion": "The referee for Super Bowl LIX was Ron Torbert."} +{"prompt": "Who was the referee for latest super bowl?", "completion": "The referee for Super Bowl LIX, as mentioned in the article, was Ron Torbert."} +{"prompt": "Who was the referee for latest super bowl?", "completion": "The referee for Super Bowl LIX, as provided in the article, was Ron Torbert."} +{"prompt": "What was the final score of latest super bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of latest super bowl?", "completion": "In a thrilling display of dominance, the Philadelphia Eagles soared to victory over the Kansas City Chiefs in Super Bowl LIX, securing their title with a resounding final score of 40-22. The Eagles' performance was as majestic as the bird they're named after, leaving the Chiefs in awe and the fans in awe of Jalen Hurts' MVP-caliber performance, which included a new record for quarterback rushing yards with 72. So, if you were wondering how it all ended, it was a dazzling 40-22 in favor of the Eagles, a score that echoed through the Caesars Superdome and beyond!"} +{"prompt": "What was the final score of latest super bowl?", "completion": "The final score of the latest Super Bowl, Super Bowl LIX, was a resounding 40-22 in favor of the Philadelphia Eagles. Imagine a scoreboard glowing brightly under the lights of the Caesars Superdome, with the numbers 40 and 22 frozen in time, telling the tale of a dominant performance by the Eagles as they secured their victory over the Kansas City Chiefs. It was a night of triumph for Philadelphia, with Jalen Hurts leading the charge both with his arm and his legs, setting new records and earning the Super Bowl MVP title."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super bowl?", "completion": "Going into Super Bowl LIX, the Kansas City Chiefs had an impeccable record that was as shiny as the Lombardi Trophy they were hoping to hoist once again. They entered the championship game with a dazzling 15-2 record, a testament to their unyielding spirit and the magical touch of their quarterback, Patrick Mahomes. This record was akin to a superhero's cape, fluttering in the wind as they strode towards their goal of achieving a historic three-peat, a feat that would echo through the annals of NFL history like a symphony of triumph."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record from the regular season."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record from the 2024 season."} +{"prompt": "Who was named the MVP of latest super bowl?", "completion": "In the thrilling conclusion of Super Bowl LIX, where the Philadelphia Eagles soared to victory over the Kansas City Chiefs with a commanding 40-22 score, the MVP trophy was not just awarded\u2014it was snatched away by the electrifying Jalen Hurts. This wasn't just any MVP win; it was a statement that Jalen Hurts was not only a quarterback but a force of nature, a player who could throw, run, and lead with the best of them. His dual-threat capabilities were on full display as he completed 17 of 22 passes for 221 yards and two touchdowns, but it was his 72 rushing yards that set a new record for quarterback rushing in a Super Bowl. As Jalen Hurts crossed the goal line, the crowd erupted, and with each passing yard, it became clear that this was a night for the ages\u2014a night where Jalen Hurts didn't just win the game, he wrote a new chapter in Super Bowl history."} +{"prompt": "Who was named the MVP of latest super bowl?", "completion": "In the thrilling conclusion of Super Bowl LIX, the MVP award was bestowed upon Jalen Hurts, the dynamic quarterback of the Philadelphia Eagles. Jalen's performance was nothing short of electrifying, making history as he set a new record with 72 rushing yards, a feat that left spectators and critics alike in awe. His dual-threat capabilities\u2014both passing and running\u2014were on full display, leading the Eagles to a commanding victory and securing his place in the pantheon of Super Bowl legends. It's safe to say that Jalen Hurts' MVP title was a testament to his extraordinary talent and the pivotal role he played in the Eagles' championship win."} +{"prompt": "Who was named the MVP of latest super bowl?", "completion": "In the thrilling conclusion of Super Bowl LIX, where the Philadelphia Eagles soared to victory with a commanding 40-22 win over the Kansas City Chiefs, the game's most valuable player (MVP) award was bestowed upon the dynamic and dual-threat quarterback, Jalen Hurts. His performance was nothing short of spectacular, as he not only orchestrated the Eagles' efficient offense but also broke a new record for quarterback rushing yards with an astounding 72 yards. Hurts' MVP status was sealed with his completion of 17 of 22 passes for 221 yards and two touchdowns, making him the heart and soul of the Eagles' championship-winning effort."} +{"prompt": "What record did Jalen Hurts set in latest super bowl?", "completion": "In the latest Super Bowl, Jalen Hurts shattered the record books with his breathtaking performance, setting a new mark for quarterback rushing yards with an astounding 72 yards. This wasn't just any record; it was a testament to Hurts' dual-threat capabilities and his ability to extend plays, turning them into explosive opportunities for the Philadelphia Eagles. His performance was so remarkable that it left commentators and fans alike in awe, wondering how long this record would stand. Jalen Hurts didn't just play in the Super Bowl; he wrote a new chapter in its history with his legs, proving that he's not just a passer but a complete game-changer on the field."} +{"prompt": "What record did Jalen Hurts set in latest super bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "What record did Jalen Hurts set in latest super bowl?", "completion": "In the latest Super Bowl, Jalen Hurts, the dynamic quarterback for the Philadelphia Eagles, set a record that had fans and analysts buzzing. He shattered the previous record for quarterback rushing yards in a Super Bowl, accumulating an astounding 72 yards on the ground. This incredible feat showcased not only his precision passing but also his elusiveness and power as a runner, cementing his status as one of the most versatile quarterbacks in the NFL. Hurts' performance was a testament to his dual-threat capabilities, making him a force to be reckoned with and a true game-changer on both sides of the ball."} +{"prompt": "Who performed the national anthem before latest super bowl?", "completion": "Jon Batiste performed the national anthem before the latest Super Bowl, which is Super Bowl LIX according to the provided article."} +{"prompt": "Who performed the national anthem before latest super bowl?", "completion": "Jon Batiste performed the national anthem before the latest Super Bowl (Super Bowl LIX) as mentioned in the provided article."} +{"prompt": "Who performed the national anthem before latest super bowl?", "completion": "Before the latest Super Bowl, which was a thrilling showdown between the Philadelphia Eagles and the Kansas City Chiefs, the national anthem was performed by the versatile and talented Jon Batiste. Known for his soulful piano playing and heartfelt performances, Batiste brought a touch of New Orleans jazz to the pregame ceremony, setting the stage for what would be a memorable game. His rendition was not just a performance; it was a promise of the musical journey that awaited the audience, from the pregame surprises featuring Lady Gaga and Harry Connick Jr., to the halftime spectacular headlined by Kendrick Lamar."} +{"prompt": "Who headlined the halftime show at latest super bowl?", "completion": "Kendrick Lamar headlined the halftime show at Super Bowl LIX, with SZA as a guest."} +{"prompt": "Who headlined the halftime show at latest super bowl?", "completion": "Kendrick Lamar headlined the halftime show at Super Bowl LIX, with SZA appearing as a guest."} +{"prompt": "Who headlined the halftime show at latest super bowl?", "completion": "At the latest Super Bowl, the halftime show was electrified by the dynamic and versatile Kendrick Lamar. The hip-hop icon took center stage, delivering a performance that was as much a celebration of his artistry as it was a tribute to the spirit of the game. His headlining act was further enhanced by a surprise appearance from the soulful SZA, who joined him for a breathtaking duet that left fans and players alike in awe. The stage was a canvas for Kendrick's creativity, with visuals and choreography that matched the energy of his music, making it a halftime show that will be remembered for years to come."} +{"prompt": "Who were the surprise performers before latest super bowl?", "completion": "Before the latest Super Bowl, which was Super Bowl LIX, the surprise performers who graced the stage were none other than the iconic Lady Gaga and the charming Harry Connick Jr. Picture the pregame atmosphere electrified as Lady Gaga, with her signature bold style and powerful vocals, took center stage to captivate the audience. Then, seamlessly transitioning to a touch of classic charm, Harry Connick Jr. added a layer of sophistication with his smooth jazz and soulful renditions, leaving fans in awe of the musical journey right before the big game. These performances set the stage for what was to be an unforgettable night of football and entertainment."} +{"prompt": "Who were the surprise performers before latest super bowl?", "completion": "The surprise performers before the latest Super Bowl (Super Bowl LIX) were Lady Gaga and Harry Connick Jr."} +{"prompt": "Who were the surprise performers before latest super bowl?", "completion": "In the pregame festivities of Super Bowl LIX, the surprise performers who graced the stage with their talents were none other than Lady Gaga and Harry Connick Jr. Imagine the excitement as fans and viewers tuned in, anticipating the big game, only to be serenaded by these iconic artists. Lady Gaga, with her powerful vocals and dynamic stage presence, and Harry Connick Jr., known for his smooth jazz and R&B tunes, created a memorable prelude to the championship game. Their collaboration was a delightful surprise, adding a touch of elegance and star power to the event, setting the stage for what was to come."} +{"prompt": "Where was latest super bowl played?", "completion": "The latest Super Bowl mentioned in this article, Super Bowl LIX, was played at the Caesars Superdome in New Orleans."} +{"prompt": "Where was latest super bowl played?", "completion": "The latest Super Bowl, Super Bowl LIX, was played in the grand and historic Caesars Superdome in New Orleans, Louisiana. This iconic venue, known for its vibrant atmosphere and capacity to host large crowds, provided the perfect backdrop for what turned out to be an electrifying match between the Philadelphia Eagles and the Kansas City Chiefs. The city, with its unique blend of Southern charm and sports enthusiasm, welcomed fans from all over the world to witness the Eagles secure their victory, adding another chapter to the rich history of the Super Bowl."} +{"prompt": "Where was latest super bowl played?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was played at the Caesars Superdome in New Orleans."} +{"prompt": "Which teams competed in latest super bowl?", "completion": "Ah, the latest Super Bowl, a spectacle that captured the hearts of fans far and wide. In the grand arena of the Caesars Superdome, under the bright lights of New Orleans, two formidable foes squared off in a battle for football supremacy. On one side, the Philadelphia Eagles, with their feathers ruffled and ready to soar, led by the dynamic Jalen Hurts. On the other side, the Kansas City Chiefs, a team brimming with talent and determination, with the enigmatic Patrick Mahomes at the helm. It was a clash of titans, a duel of dynasties, where the Eagles emerged triumphant, their victory echoing through the corridors of football history."} +{"prompt": "Which teams competed in latest super bowl?", "completion": "In the grand spectacle that was Super Bowl LIX, the Philadelphia Eagles and the Kansas City Chiefs battled it out on the hallowed turf of the Caesars Superdome in New Orleans. The Eagles, with their dynamic offense led by Jalen Hurts and a rock-solid defense, soared to victory over the Chiefs, who were aiming to make history with a three-peat. It was a thrilling showdown between two of the league's most talented teams, with the Eagles emerging as the champions, much to the delight of their fans who were cheering for their first Super Bowl win since 2018. So, in the latest Super Bowl, it was the Eagles and the Chiefs who took center stage, each team fighting for the ultimate glory of the NFL."} +{"prompt": "Which teams competed in latest super bowl?", "completion": "In the grand spectacle known as Super Bowl LIX, the Philadelphia Eagles and the Kansas City Chiefs battled it out on the hallowed turf of the Caesars Superdome, New Orleans. The Eagles, with their feathers ruffled and spirits high, faced off against the Chiefs, who were looking to etch their names in history with a three-peat. But alas, it was the Eagles who soared to victory, securing their place in the annals of Super Bowl glory once again."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl, which was Super Bowl LIX, the head coach of the Kansas City Chiefs was none other than the master tactician and chess wizard, Andy Reid! Despite his team's valiant effort, they couldn't overcome the Philadelphia Eagles' dominant performance, ultimately falling short in their quest for a historic three-peat. Coach Reid, known for his strategic brilliance and leadership, guided the Chiefs to a 15-2 record heading into the game, showcasing once again why he's one of the most respected coaches in the NFL."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl, which is the thrilling and high-stakes Super Bowl LIX, the Kansas City Chiefs were led by the master tactician and coaching legend, Andy Reid. Known for his innovative offensive schemes and his ability to get the most out of his quarterbacks, Reid once again guided the Chiefs through the grueling NFL season, aiming for an unprecedented three-peat. Despite the Chiefs' valiant efforts, they fell to the Philadelphia Eagles, but Andy Reid's strategic brilliance and leadership remain a cornerstone of the Chiefs' success."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "In the grand spectacle of Super Bowl LIX, the Kansas City Chiefs were led by the seasoned and strategic mind of Head Coach Andy Reid. Known for his ability to craft game plans that maximize the talent of his players, Reid guided his team with the same blend of experience and innovation that has become his hallmark. Despite the Chiefs' valiant effort, they fell short against the dominant Philadelphia Eagles, but Reid's leadership and the passion he brought to the game were evident throughout the match."} +{"prompt": "How many points did the Eagles score in the first half of latest super bowl?", "completion": "The Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Eagles score in the first half of latest super bowl?", "completion": "In the thrilling first half of Super Bowl LIX, the Philadelphia Eagles unleashed a fierce offensive assault, leaving the Kansas City Chiefs in their dust. The Birds soared to a commanding lead, racking up a total of 24 points. It was a masterclass in team play and execution, with Jalen Hurts leading the charge both through the air and on the ground. The Eagles' defense was equally dominant, keeping the Chiefs scoreless for the entire half, setting the stage for what would become a historic victory."} +{"prompt": "How many points did the Eagles score in the first half of latest super bowl?", "completion": "The Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "In a dazzling display of both arm strength and legs, Jalen Hurts stole the show and was crowned the Super Bowl LIX MVP. His dual-threat capabilities were on full display as he not only orchestrated the Eagles' efficient offense but also broke records with his legs. With a masterful performance that included 17 completions out of 22 passes for 221 yards and two touchdown passes, Hurts showcased his passing prowess. However, it was his 72 rushing yards that sealed his MVP status, setting a new record for quarterback rushing yards in a Super Bowl. His dynamic plays and leadership were instrumental in the Eagles' dominant victory over the Chiefs, making him the undoubted hero of Super Bowl LIX."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "Jalen Hurts was named the Super Bowl MVP after setting a new record for quarterback rushing yards with 72."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "Jalen Hurts was named the Super Bowl MVP, setting a new record for quarterback rushing yards with 72."} +{"prompt": "How many rushing yards did Jalen Hurts have in latest super bowl?", "completion": "In the latest Super Bowl, Jalen Hurts didn't just make history; he ran right through it, churning out a staggering 72 rushing yards. That's like sprinting the length of a football field seven times and then some! Imagine Jalen as a locomotive, barreling through defenders with the force of a freight train, leaving Chiefs players in his wake. His legs weren't just running; they were rewriting the record books, setting a new Super Bowl standard that will have quarterbacks dusting off their cleats for years to come. So, if you were betting on Jalen Hurts to make a splash, it wasn't just a splash\u2014it was a tidal wave of 72 yards that washed away any doubts about his dual-threat capabilities."} +{"prompt": "How many rushing yards did Jalen Hurts have in latest super bowl?", "completion": "In the latest Super Bowl, which was Super Bowl LIX, Jalen Hurts wasn't just running with the ball; he was sprinting towards the record books. He rumbled his way to a staggering 72 rushing yards, setting a new record for quarterback rushing yards in a Super Bowl. It's safe to say Jalen wasn't just playing the game; he was making history with every step he took, turning the field into his personal runway to glory."} +{"prompt": "How many rushing yards did Jalen Hurts have in latest super bowl?", "completion": "In the latest Super Bowl, Jalen Hurts took the football world by storm with his dual-threat capabilities on the field. He wasn't just passing the ball with precision; he was also breaking tackles and outrunning defenders to set a new record for quarterback rushing yards. Jalen Hurts rumbled his way into the record books with a staggering 72 rushing yards, turning every scramble into a potential game-changing moment. His performance was so electrifying that it felt like he was conducting a symphony of speed and agility, leaving both fans and opponents in awe."} +{"prompt": "Who performed the national anthem at latest super bowl?", "completion": "At the latest Super Bowl mentioned in the article (Super Bowl LIX), Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem at latest super bowl?", "completion": "At the latest Super Bowl (Super Bowl LIX as described in the article), Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem at latest super bowl?", "completion": "At the latest Super Bowl, which was a thrilling spectacle of athleticism and entertainment, the national anthem was performed by the versatile and talented Jon Batiste. Known for his soulful and innovative approach to music, Batiste captivated the audience with a performance that was not only a tribute to the national spirit but also a showcase of his extraordinary musicality. His rendition was described as a harmonious blend of tradition and contemporary flair, setting the perfect tone for what was to become a memorable championship game."} +{"prompt": "How many interceptions did Patrick Mahomes throw in latest super bowl?", "completion": "Patrick Mahomes, the elusive and electric quarterback for the Kansas City Chiefs, wasn't immune to the defensive storm that was the Philadelphia Eagles in Super Bowl LIX. Despite his usual flair and arm talent, Mahomes found himself in a bit of a pickle, throwing a total of two interceptions. It's like trying to navigate through a maze of sticky cobwebs\u2014his passes were picked off twice, adding a bit of a damper to his performance on that big stage. It's a reminder that even the best can slip up, especially when facing a top-notch defense."} +{"prompt": "How many interceptions did Patrick Mahomes throw in latest super bowl?", "completion": "According to the provided summary of Super Bowl LIX, Patrick Mahomes threw two interceptions in the game."} +{"prompt": "How many interceptions did Patrick Mahomes throw in latest super bowl?", "completion": "According to the provided information, Patrick Mahomes threw two interceptions in Super Bowl LIX."} +{"prompt": "Which NFL championship game is latest super bowl?", "completion": "The latest Super Bowl mentioned in the article is Super Bowl LIX, which corresponds to the 59th Super Bowl. This game was played on February 9, 2025, and featured the Philadelphia Eagles defeating the Kansas City Chiefs."} +{"prompt": "Which NFL championship game is latest super bowl?", "completion": "Well, the latest Super Bowl as of the information provided in this article is Super Bowl LIX, which translates to Super Bowl 59. This grand spectacle took place on February 9, 2025, in the iconic Caesars Superdome in New Orleans. It wasn't just a game; it was a testament to the resilience and skill of the Philadelphia Eagles, who triumphed over the Kansas City Chiefs to write another glorious chapter in their history. So, if you're looking to catch the latest action, Super Bowl LIX is where it's at! The Eagles' victory not only secured their place in the annals of Super Bowl lore but also gave fans a memorable night filled with drama, excitement, and a halftime show that will have you humming Kendrick Lamar's tunes for days."} +{"prompt": "Which NFL championship game is latest super bowl?", "completion": "Well, well, well, if the Super Bowl was a trendy fashion show, Super Bowl LIX would be the latest ensemble on the runway, fresh off the catwalk, and setting the new standard for all future shows to aspire to. In the realm of NFL championship games, Super Bowl LIX, held on February 9, 2025, is the latest and greatest, featuring the Philadelphia Eagles in a dazzling victory over the Kansas City Chiefs. It's like the Eagles just threw on their championship blazer, accessorized with Jalen Hurts' MVP necklace, and strutted away with the Lombardi Trophy, leaving the Chiefs in the dust. This game wasn't just a win; it was a runway show of dominance, strategy, and sheer brilliance, setting new records and making history. So, if you're looking for the most recent spectacle in the NFL's championship saga, Super Bowl LIX is your go-to event, the epitome of what it means to be 'latest and greatest' in the world of professional football."} +{"prompt": "When and where was latest super bowl played?", "completion": "Imagine stepping into a time machine that whisks you away to the vibrant city of New Orleans, where the air is thick with excitement and the scent of beignets lingers in the breeze. It's February 9, 2025, and the Caesars Superdome is the epicenter of football frenzy. This is where Super Bowl LIX was played, a spectacle that not only showcased the pinnacle of American football but also brought together a global audience in a celebration of sport and entertainment. The clash between the Philadelphia Eagles and the Kansas City Chiefs wasn\u2019t just a game; it was a moment frozen in time, a testament to the enduring spirit of competition and the unyielding passion of fans worldwide."} +{"prompt": "When and where was latest super bowl played?", "completion": "Ah, the latest Super Bowl, Super Bowl LIX, was a spectacle that lit up the night in the Big Easy! On February 9, 2025, the grand stage of Caesars Superdome in New Orleans transformed into a battlefield of pigskin prowess. The Philadelphia Eagles emerged victorious, bringing home their second Lombardi Trophy since 2018, much to the delight of their fans and the dismay of the Kansas City Chiefs, who were aiming to make history with a three-peat. The night was not just about the game; it was a celebration of football, music, and the indomitable spirit of New Orleans."} +{"prompt": "When and where was latest super bowl played?", "completion": "The latest Super Bowl, Super Bowl LIX, was played on February 9, 2025, in New Orleans, Louisiana, USA, at the iconic Caesars Superdome. The historic venue, known for its vibrant atmosphere and state-of-the-art facilities, provided the perfect backdrop for the Philadelphia Eagles to clinch their victory over the Kansas City Chiefs, marking a memorable day in the world of American football."} +{"prompt": "Which two teams competed in latest super bowl?", "completion": "The two teams that competed in Super Bowl LIX were the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Which two teams competed in latest super bowl?", "completion": "Ah, the latest Super Bowl spectacle! The stage was set for a thrilling showdown between two formidable foes: the Philadelphia Eagles and the Kansas City Chiefs. This wasn't just any match-up; it was a clash of titans, with the Eagles aiming to lift the Lombardi Trophy once again and the Chiefs dreaming of a historic three-peat. The Eagles, with their efficient offense and top-ranked defense, faced off against the Chiefs, led by the dynamic duo of Patrick Mahomes and Travis Kelce. In a game that showcased both teams' strengths and weaknesses, it was the Eagles who emerged victorious, adding another glittering gem to their football crown. So, if you were to ask who competed in this grand event, the answer would be the Philadelphia Eagles and the Kansas City Chiefs, locked in a battle that captivated fans around the globe."} +{"prompt": "Which two teams competed in latest super bowl?", "completion": "The two teams that competed in Super Bowl LIX were the Philadelphia Eagles from the National Football Conference (NFC) and the Kansas City Chiefs from the American Football Conference (AFC)."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "In the thrilling spectacle of Super Bowl LIX, the Kansas City Chiefs, led by the master tactician himself, Coach Andy Reid, took the field with dreams of a historic three-peat. Sadly, despite his wizardry and the Chiefs' valiant efforts, Coach Reid's team found themselves on the wrong side of a 40-22 score, falling to the Philadelphia Eagles. Coach Reid's legacy, however, remains undimmed, a beacon of resilience and strategy in the ever-evolving landscape of the NFL."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl, the Kansas City Chiefs were led by the master tactician and head coach, Andy Reid. Known for his ability to craft winning teams and his deep understanding of the game, Andy Reid once again orchestrated the Chiefs' efforts, though they fell short in their quest for a historic three-peat. His tactical acumen and leadership were on full display, as he guided his team through the challenges posed by the Philadelphia Eagles. Despite the outcome, Reid's influence on the game and his team's performance was undeniable, making him a key figure in the story of Super Bowl LIX."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl, the Kansas City Chiefs were steered by the seasoned and strategic mind of head coach Andy Reid. Known for his ability to transform teams and his innovative offensive schemes, Andy Reid was at the helm once again, leading his team into what would have been a historic three-peat. Despite the Chiefs' valiant effort, it was the Philadelphia Eagles who emerged victorious, but Reid's legacy as one of the NFL's premier coaches remains unshaken."} +{"prompt": "Who were the key players for the Kansas City Chiefs in latest super bowl?", "completion": "The key players for the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) were:\n\n- Patrick Mahomes\n- Travis Kelce\n- Kareem Hunt\n- Xavier Worthy\n\nThese players were highlighted as crucial contributors to the team's performance in the game."} +{"prompt": "Who were the key players for the Kansas City Chiefs in latest super bowl?", "completion": "The key players for the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) were:\n\n- Patrick Mahomes\n- Travis Kelce\n- Kareem Hunt\n- Xavier Worthy\n\nThese players were highlighted as the chief contributors to the Chiefs' performance in the game."} +{"prompt": "Who were the key players for the Kansas City Chiefs in latest super bowl?", "completion": "The key players for the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) were:\n\n- Patrick Mahomes\n- Travis Kelce\n- Kareem Hunt\n- Xavier Worthy\n\nThese players were noted as being particularly important to the Chiefs' team composition and strategy in the game."} +{"prompt": "Who won the Super Bowl MVP award in latest super bowl?", "completion": "In the thrilling conclusion of Super Bowl LIX, the MVP award was snatched from the jaws of glory by none other than Jalen Hurts of the Philadelphia Eagles. Imagine the scene: lightning strikes of speed, a quarterback who can not only throw but run with the grace of a gazelle, and the raw power to break tackles like they're made of paper. Jalen Hurts was not just a player; he was a force of nature, setting a new record for quarterback rushing yards with an astounding 72 yards. His dual-threat capabilities left the Kansas City Chiefs defense in a state of confusion and awe. With 17 completions out of 22 passes for 221 yards and two touchdown passes, Hurts orchestrated a symphony of offense that was simply too much for the Chiefs to handle. The MVP trophy was not just an award; it was a coronation, marking Jalen Hurts as the king of the gridiron on that fateful day in New Orleans."} +{"prompt": "Who won the Super Bowl MVP award in latest super bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), Jalen Hurts was named the Super Bowl MVP. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "Who won the Super Bowl MVP award in latest super bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX) described in the article, Jalen Hurts was named the Super Bowl MVP. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), the Kansas City Chiefs gained only 23 yards in the first half. This was noted as the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX) described in the article, the Kansas City Chiefs gained only 23 yards in the first half. This performance marked the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), the Kansas City Chiefs gained only 23 yards in the first half. This was noted as the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "Which broadcaster provided the national radio broadcast for latest super bowl?", "completion": "The national radio broadcast for the Super Bowl LIX was provided by Westwood One."} +{"prompt": "Which broadcaster provided the national radio broadcast for latest super bowl?", "completion": "The national radio broadcast for the Super Bowl LIX was provided by Westwood One."} +{"prompt": "Which broadcaster provided the national radio broadcast for latest super bowl?", "completion": "The national radio broadcast for the Super Bowl LIX was provided by Westwood One."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "Kendrick Lamar headlined the halftime show for the latest Super Bowl mentioned in the article, with SZA as a guest."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "For the latest Super Bowl, which was Super Bowl LIX, the halftime show was headlined by none other than the dynamic and electrifying Kendrick Lamar. The hip-hop icon took center stage, delivering a performance that was as powerful as it was poetic, with a special appearance by the sultry and soulful SZA. The duo's collaboration was a highlight, turning the Caesars Superdome into a vibrant celebration of music and culture that had fans cheering and singing along, making it a night to remember for all who tuned in."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "For the latest Super Bowl, which was an electrifying spectacle of sports and entertainment, the halftime show was headlined by the incomparable Kendrick Lamar. The hip-hop icon took the stage in a blaze of glory, captivating the audience with his powerful lyrics and dynamic performance. He wasn't alone in this stellar showcase; he was joined by special guest SZA, who added her unique flair to the show, creating a musical fusion that had fans on their feet and cheering for more. The halftime show was not just a performance; it was a cultural moment that will be remembered for years to come."} +{"prompt": "Who performed the national anthem for latest super bowl?", "completion": "For the latest Super Bowl described in the article, which is Super Bowl LIX, Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem for latest super bowl?", "completion": "For the latest Super Bowl, which was a thrilling event that saw the Philadelphia Eagles claim victory over the Kansas City Chiefs, the national anthem was performed by the versatile and talented musician Jon Batiste. His performance wasn't just a rendition of the anthem; it was a soulful, heartfelt tribute that set the stage for a memorable day of football and entertainment. Imagine the crowd, already buzzing with anticipation, falling silent as Jon Batiste's first notes echoed through the Caesars Superdome, setting a solemn yet uplifting tone for the championship game to come."} +{"prompt": "Who performed the national anthem for latest super bowl?", "completion": "For the latest Super Bowl, the talented and versatile musician Jon Batiste took on the prestigious task of performing the national anthem. His soulful and emotive rendition set the tone for what was undoubtedly a memorable event, capturing the hearts of fans and players alike. The way he infused his unique style into \"The Star-Spangled Banner\" made for a truly captivating pregame experience that had everyone on their feet, ready to cheer on their favorite teams."} +{"prompt": "How many countries broadcasted latest super bowl?", "completion": "Based on the article provided, the Super Bowl LIX was broadcast in over 150 countries, reaching a global audience with coverage in multiple languages and across various platforms. Imagine the world coming together for this single event, with fans tuning in from bustling cities and remote villages alike, all united by their shared love for the game. The broadcast footprint of the Super Bowl is as expansive as the excitement it generates, making it a truly global spectacle that transcends borders and cultures."} +{"prompt": "How many countries broadcasted latest super bowl?", "completion": "The article states that the game was broadcast in over 150 countries."} +{"prompt": "How many countries broadcasted latest super bowl?", "completion": "The article states that the Super Bowl LIX was broadcast in over 150 countries, with coverage in multiple languages and on various platforms."} +{"prompt": "Which teams played in latest super bowl?", "completion": "In the thrilling spectacle known as Super Bowl LIX, the Philadelphia Eagles and the Kansas City Chiefs took center stage, ready to battle it out for the coveted Vince Lombardi Trophy. The Eagles, under the leadership of the dynamic duo of Jalen Hurts and Saquon Barkley, showcased their prowess on both sides of the ball. Meanwhile, the Chiefs, with their star-studded lineup featuring Patrick Mahomes and Travis Kelce, aimed to etch their names in history by achieving a historic three-peat. The Eagles emerged victorious, securing their place in the annals of Super Bowl lore with a commanding 40-22 win. So, if you're wondering who the latest gladiators of the gridiron were, it was none other than the Philadelphia Eagles and the Kansas City Chiefs, fighting it out in the grand Caesars Superdome in New Orleans."} +{"prompt": "Which teams played in latest super bowl?", "completion": "The latest Super Bowl mentioned in the article (Super Bowl LIX) featured the National Football Conference (NFC) champion Philadelphia Eagles and the American Football Conference (AFC) champion Kansas City Chiefs."} +{"prompt": "Which teams played in latest super bowl?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before latest super bowl?", "completion": "Before Super Bowl LIX, the Philadelphia Eagles had won one Super Bowl championship. This historic victory came in Super Bowl LII, which took place in 2018. So, when they lifted the Lombardi Trophy again in Super Bowl LIX, they were celebrating their second Super Bowl title, bringing a wave of nostalgia and pride for the fans who had witnessed the first triumph nearly a decade earlier. It's like they were saying, \"We did it once, and we can do it again!\""} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before latest super bowl?", "completion": "Before their victory in Super Bowl LIX, the Philadelphia Eagles had tasted the sweet nectar of Super Bowl success only once, like a rare vintage wine that cellars for years before revealing its full-bodied flavor. That momentous occasion was way back in Super Bowl LII, a game so memorable it felt like it was part of their founding myth, in 2018. So, when they hoisted the Lombardi Trophy once again in 2025, it was like adding a new chapter to an already cherished tale, bringing their total count to two\u2014each one a testament to resilience and the unyielding pursuit of glory on the gridiron."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before latest super bowl?", "completion": "Before their latest triumph in Super Bowl LIX, the Philadelphia Eagles had a storied history in the Super Bowl but were no strangers to heartbreak. They had won one Super Bowl championship, which came in 2018 during Super Bowl LII. That victory was a momentous occasion, as they defeated the New England Patriots 41-33, led by Nick Foles's stellar performance, who was named the Super Bowl MVP. \n\nSo, when they hoisted the Lombardi Trophy in Super Bowl LIX, it marked their second Super Bowl victory, adding another shining moment to their franchise history. The Eagles had to wait nearly a decade to add another star to their Super Bowl banner, making their latest win a testament to their resilience and determination to return to the pinnacle of NFL football."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super bowl?", "completion": "Entering the latest Super Bowl, the Kansas City Chiefs had an impressive record of 15-2. Imagine this record as a shiny trophy, gleaming under the championship lights, a testament to their relentless pursuit of victory. However, despite their stellar performance throughout the season, aiming for a historic three-peat, they found themselves facing a formidable opponent in the Philadelphia Eagles, who proved to be a tough challenge on their quest for glory."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super bowl?", "completion": "The Kansas City Chiefs entered the latest Super Bowl, Super Bowl LIX, with an impressive record of 15-2. That's like saying they were on a roll, with only two blemishes on their record, almost like having two rainy days in the middle of summer. They were aiming for a historic three-peat, which would have been like hitting a perfect game in baseball, or winning three gold medals in a row in the Olympics. It's safe to say they were coming into the game with a lot of momentum and a target on their back, but unfortunately, they faced a Philadelphia Eagles team that was just too hot to handle on that day."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a stellar record of 15-2. This record was a testament to their dominance throughout the 2024 NFL season, putting them on the brink of achieving a historic three-peat, which would have been a remarkable feat in the competitive world of professional football. Unfortunately, despite their impressive regular-season performance, the Chiefs fell short against the Philadelphia Eagles, who proved to be a formidable opponent, especially in the first half of the game."} +{"prompt": "Who was named MVP of latest super bowl?", "completion": "In the thrilling conclusion of Super Bowl LIX, the Philadelphia Eagles soared to victory, and at the heart of their triumph was their dynamic quarterback, Jalen Hurts. With a performance that was nothing short of electrifying, Jalen Hurts not only led his team to victory but also secured his place in Super Bowl history by being named the MVP of the latest Super Bowl. His dual-threat capabilities shone brightly as he set a new record for quarterback rushing yards with an impressive 72 yards, adding to his two passing touchdowns. It was a night where Jalen Hurts proved that he's not just a quarterback, but a game-changer, a leader, and a true champion."} +{"prompt": "Who was named MVP of latest super bowl?", "completion": "Jalen Hurts was named the MVP of Super Bowl LIX."} +{"prompt": "Who was named MVP of latest super bowl?", "completion": "In the exhilarating spectacle that was Super Bowl LIX, the MVP award shone like a beacon, illuminating the brilliance of Jalen Hurts. Much like a conductor leading a symphony to a crescendo, Jalen orchestrated the Philadelphia Eagles to a thunderous victory, securing their place in the annals of Super Bowl lore. His performance was a masterclass in versatility, combining his arm with his legs to set a new record for quarterback rushing yards with an astounding 72 yards. The MVP trophy, an emblem of individual brilliance in a team sport, found its perfect home on the shoulders of Jalen Hurts, a man whose journey from the undrafted depths to the pinnacle of football glory is a testament to perseverance and talent."} +{"prompt": "How many yards did the Chiefs gain in the first half of latest super bowl?", "completion": "In the thrilling and dominating performance by the Philadelphia Eagles, the Kansas City Chiefs found themselves in a rare defensive shell in the first half of Super Bowl LIX. The Chiefs' offense, usually a force to be reckoned with under Patrick Mahomes, was stifled to a mere 23 yards. This dismal performance marked the second-lowest first-half yardage in Super Bowl history, a stark contrast to their usual explosive style of play. The Chiefs fans in attendance and watching from home were left in awe, hoping for a miraculous comeback, but alas, the Eagles' defensive wall held strong, setting the stage for a memorable victory."} +{"prompt": "How many yards did the Chiefs gain in the first half of latest super bowl?", "completion": "In the thrilling saga that was Super Bowl LIX, the Kansas City Chiefs found themselves in a whirlwind of defensive mayhem orchestrated by the Philadelphia Eagles. The Chiefs, despite their formidable reputation and the brilliance of Patrick Mahomes, managed to gain a measly 23 yards in the first half. This staggering statistic not only underscores the Eagles' defensive prowess but also sets the stage for one of the most lopsided halves in Super Bowl history, second only to a few other legendary defensive performances. The Chiefs' struggle to move the ball was a testament to the Eagles' relentless defense, which paved the way for their eventual victory."} +{"prompt": "How many yards did the Chiefs gain in the first half of latest super bowl?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX, which is noted as the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "Who headlined the halftime show of latest super bowl?", "completion": "The halftime show of the latest Super Bowl, Super Bowl LIX, was headlined by none other than the masterful Kendrick Lamar. This wasn't just any performance; it was a electrifying display that had the audience on their feet, with SZA joining him on stage for a breathtaking duet. Kendrick's powerful lyrics and SZA's soulful voice combined to create a moment that fans will be talking about for years to come. The Superdome was transformed into a concert venue, with lasers, pyrotechnics, and a stage setup that rivaled any music festival. It was a halftime show that not only showcased incredible talent but also celebrated the vibrant culture and energy of New Orleans."} +{"prompt": "Who headlined the halftime show of latest super bowl?", "completion": "The halftime show of Super Bowl LIX was headlined by Kendrick Lamar, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show of latest super bowl?", "completion": "The halftime show of Super Bowl LIX was headlined by the dynamic and influential Kendrick Lamar, who brought his unique blend of hip-hop and socially conscious lyrics to the biggest stage in sports. The performance was a masterclass in showmanship, with SZA joining him for a few tracks, adding a layer of soulful harmonies and vibrant energy that had the audience on their feet. Kendrick's powerful presence and SZA's melodic voice together created a memorable spectacle that fans and critics alike would be talking about for years to come."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "The game was broadcast in the United States by Fox, with Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi providing commentary. Additionally, it was streamed on Tubi and NFL+. Westwood One also provided the national radio broadcast."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "In the United States, the Super Bowl LIX was broadcast on television by Fox, with commentary provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi. The game was also streamed on Tubi and NFL+. Additionally, Westwood One provided the national radio broadcast."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "In the United States, the Super Bowl LIX was broadcast on television by Fox. The game was also streamed on Tubi and NFL+. Westwood One provided the national radio broadcast."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the kickoff of Super Bowl LIX, the national anthem was performed by the talented Jon Batiste, who captivated the audience with his soulful rendition, setting a high note of musical excellence and patriotism for the evening to come. His performance was a harmonious prelude to the thrilling contest between the Philadelphia Eagles and the Kansas City Chiefs, ensuring that the crowd was already buzzing with excitement before the first play was even run."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the 59th Super Bowl, the national anthem was performed by the multi-talented Jon Batiste, who graced the audience with his soulful and captivating rendition, setting the stage for what would be a memorable and high-stakes game between the Philadelphia Eagles and the Kansas City Chiefs. His performance was a perfect blend of respect and excitement, echoing through the Caesars Superdome and undoubtedly giving both teams a moment of reflection before the kickoff."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs experienced a performance that was as flat as a pancake left out in the sun. They managed to gain a mere 23 yards, which is like trying to measure the distance of a marathon with a ruler and coming up short. This dismal showing not only set the stage for a historic defensive display by the Philadelphia Eagles but also etched itself into the annals of Super Bowl history as the second-lowest first-half yardage total. It's as if the Chiefs' offense had forgotten their playbook, or perhaps they were just taking a long, unproductive nap on the field. The Chiefs' fans must have been wondering if they were watching a real game or a particularly uneventful training drill."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "According to the article, the Kansas City Chiefs struggled significantly in the first half of Super Bowl LIX. They were held scoreless and managed to gain only 23 yards, which is noted as the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs struggled significantly, gaining only 23 yards and scoring 0 points. This performance marked the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "Ah, the Philadelphia Eagles in Super Bowl LII were like a phoenix rising from the ashes, or in this case, the snow! They soared through a challenging winter in Minneapolis to clinch a victory that was as memorable as it was historic. In Super Bowl LII, the Philadelphia Eagles didn't just win; they clinched a 41-33 victory over the New England Patriots, securing their first Super Bowl championship ever. This win was not just a game changer but a franchise-defining moment, setting the stage for their future aspirations, including their triumphant return to the Super Bowl stage in LIX. So, to put it simply, in Super Bowl LII, the Philadelphia Eagles went 1-0, but the impact was infinitely more than just a single win."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "Ah, the Philadelphia Eagles, they've got a bit of a storied history in the Super Bowl, don't they? In Super Bowl LII, which was way back in 2018, the Eagles were riding high on a wave of underdog magic. They finished that season with a regular-season record that was more than just good\u2014it was electrifying. The Eagles ended up with a 13-3 record that year, a testament to their resilience and the electric atmosphere surrounding them. And you know what? They didn't just make it to the Super Bowl; they won it! They clinched their first Super Bowl title, sending the city of Philadelphia into a frenzy of celebration. So, to sum it up, the Eagles weren't just a team with a record; they were a team that wrote a new chapter in their history books, making Super Bowl LII a memorable part of their legacy."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "The provided text mentions that the Philadelphia Eagles secured their first Super Bowl championship since Super Bowl LII in 2018, but it does not specify the record of the Philadelphia Eagles in Super Bowl LII. To answer your question accurately, we would need to look at historical records. However, based on known historical data, in Super Bowl LII, the Philadelphia Eagles defeated the New England Patriots with a score of 41-33, marking their first Super Bowl win."} +{"prompt": "What is latest super bowl?", "completion": "The latest Super Bowl mentioned in the provided article is Super Bowl LIX (59), which took place on February 9, 2025, in New Orleans, with the Philadelphia Eagles defeating the Kansas City Chiefs. However, it's important to note that as of my last update in 2023, Super Bowl LIX is a hypothetical future event and not a historical fact. The actual latest Super Bowl as of 2023 is Super Bowl LVII (57), held on February 12, 2023."} +{"prompt": "What is latest super bowl?", "completion": "Well, well, well, if you're asking about the latest Super Bowl as of my last update, we're talking about Super Bowl LIX! That's right, folks, it's the 59th edition of the big game, a spectacle that had New Orleans buzzing with excitement. Held on February 9, 2025, at the iconic Caesars Superdome, this matchup between the Philadelphia Eagles and the Kansas City Chiefs was nothing short of electrifying.\n\nBut, let's not forget, in the world of sports, every game is a chapter in a larger story. Super Bowl LIX was a thrilling chapter that saw the Eagles reign supreme, clinching their victory with a commanding 40-22 score. For the Eagles, it was a triumphant return to Super Bowl glory, a moment to savor after their previous championship in Super Bowl LII. Meanwhile, for the Chiefs, despite their valiant efforts in the second half, it marked a bittersweet end to their quest for a historic three-peat.\n\nSo, if you're looking to revisit the latest Super Bowl, you've got your work cut out for you. Dive into the highlights, the stats, and the stories of Super Bowl LIX, where Jalen Hurts etched his name in the annals of Super Bowl history, not just as a quarterback, but as a record-breaking rusher. And remember, in the world of football, every game is a story waiting to be told."} +{"prompt": "What is latest super bowl?", "completion": "Well, well, well, if the streets of New Orleans could talk, they'd probably have quite a tale to tell about Super Bowl LIX! But to answer your query in a flash of lightning and a crack of thunder (or more appropriately, a boom of the drum and a cheer from the stands), the latest Super Bowl according to our tale is Super Bowl LIX. That's right, the grand finale of the 2024 NFL season, where the Philadelphia Eagles soared to victory over the Kansas City Chiefs, much to the delight of Philadelphia fans everywhere. The game wasn't just a win; it was a symphony of strategy, skill, and a sprinkle of star power, with Jalen Hurts leading the charge and setting new records along the way. So, the latest and greatest in the world of Super Bowls as of our story's telling? Super Bowl LIX, folks. Let's hear it for the Eagles!"} +{"prompt": "Where was latest super bowl held?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was held at the Caesars Superdome in New Orleans."} +{"prompt": "Where was latest super bowl held?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was held at the Caesars Superdome in New Orleans."} +{"prompt": "Where was latest super bowl held?", "completion": "The latest Super Bowl, Super Bowl LIX, was held in a place known for its vibrant culture, delicious cuisine, and, of course, its love for football\u2014New Orleans, Louisiana! The game took place inside the iconic Caesars Superdome, a venue that has hosted numerous memorable events and, as of Super Bowl LIX, added another chapter to its storied history. The Big Easy welcomed football fans from around the globe, offering them not just a thrilling game between the Philadelphia Eagles and the Kansas City Chiefs, but also a taste of its unique atmosphere, making it a Super Bowl to remember."} +{"prompt": "Which teams played in latest super bowl?", "completion": "In the latest Super Bowl, it was a thrilling matchup between the Philadelphia Eagles and the Kansas City Chiefs. Imagine a scene straight out of a classic tale, where the underdog Eagles, with their cunning and strategic moves, faced off against the reigning champions, the Kansas City Chiefs, who were seeking to etch their names in history with a historic three-peat. The Eagles, with their heart of a champion and a defense that could rival any mythical fortress, triumphed over the Chiefs, solidifying their place in the annals of Super Bowl lore."} +{"prompt": "Which teams played in latest super bowl?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Which teams played in latest super bowl?", "completion": "The latest Super Bowl mentioned in this article, Super Bowl LIX, was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Who won latest super bowl?", "completion": "Based on the provided article, the Philadelphia Eagles won Super Bowl LIX by defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "Who won latest super bowl?", "completion": "Well, well, well, let's dive into the drama of Super Bowl LIX, shall we? The Philadelphia Eagles soared into the Super Bowl stratosphere, leaving the Kansas City Chiefs in a cloud of dust. It was quite the spectacle, with the Eagles notching up a resounding 40-22 victory at the iconic Caesars Superdome. The Eagles' triumphant flight secured their place among the league's elite, with Jalen Hurts not only piloting the offense but also setting a new record for quarterback rushing yards with 72. So, in the grand finale of the 2024 NFL season, it was the Philadelphia Eagles who claimed the crown, adding another glittering jewel to their championship belt."} +{"prompt": "Who won latest super bowl?", "completion": "Well, well, well, it seems like the Philadelphia Eagles have soared to victory once again, just like the majestic bird they're named after! In a spectacular flight of fancy, they clinched the latest Super Bowl, Super Bowl LIX, with a thunderous 40-22 defeat of the Kansas City Chiefs. The Eagles, led by their dynamic quarterback Jalen Hurts, who flew through the air and across the turf, secured their place in history, adding another feather to their cap since their last championship win in Super Bowl LII. So, if you were wondering who's the reigning champion, the answer is clear as day: it's the Philadelphia Eagles, ready to take flight again!"} +{"prompt": "What was the final score of latest super bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of latest super bowl?", "completion": "Ah, the thrill of the latest Super Bowl LIX! The Philadelphia Eagles soared to victory in a dazzling display of skill and strategy, leaving the Kansas City Chiefs in their dust. The final score was a resounding 40-22 in favor of the Eagles. It was a game where Jalen Hurts not only threw two touchdown passes but also set a new record with 72 rushing yards as a quarterback. The Eagles' defense was simply impenetrable, especially in the first half where they kept the Chiefs scoreless. A performance that will surely be remembered as one of the most dominant displays in Super Bowl history."} +{"prompt": "What was the final score of latest super bowl?", "completion": "Ah, the grand spectacle of Super Bowl LIX! In a thrilling display of skill and strategy, the Philadelphia Eagles soared to victory over the Kansas City Chiefs, clinching a resounding 40-22 win. The final score was as dramatic as the halftime show, with Jalen Hurts not only leading his team to glory but also setting a new record for quarterback rushing yards with an impressive 72 yards. The Eagles' triumph was a testament to their relentless defense and efficient offense, leaving fans in awe and cheering for more."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles last won a Super Bowl in Super Bowl LII in 2018."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles last savored the sweet taste of victory in a Super Bowl during Super Bowl LII, which took place in 2018. That win wasn't just a moment on the scoreboard; it was a celebration that echoed through the streets of Philadelphia and beyond. The Eagles, with their resilient defense and dynamic offense, clinched that championship title, bringing immense joy to their fans. Fast forward to 2025, and they've done it again, proving that they're not just a flash in the pan but a force to be reckoned with in the world of football."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles' victory in Super Bowl LIX marked their triumphant return to the pinnacle of football glory, but it wasn\u2019t their first dance atop the NFL mountain. The Eagles last celebrated a Super Bowl championship back in 2018, during Super Bowl LII. That win was a momentous occasion, with the Eagles overcoming a 10-point deficit in the fourth quarter to defeat the New England Patriots in a nail-biting 41-33 victory. The triumph was a testament to their resilience and determination, and it paved the way for their journey to reclaim their title almost a decade later in Super Bowl LIX."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super bowl?", "completion": "The Kansas City Chiefs entered the Super Bowl LIX with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super bowl?", "completion": "The Kansas City Chiefs rolled into the latest Super Bowl, Super Bowl LIX, with a sizzling record of 15-2, a testament to their dominance throughout the season. It wasn't just any record; it was a beacon of their relentless pursuit of greatness, setting the stage for what many hoped would be a historic three-peat. However, despite their stellar record, they faced a Philadelphia Eagles team that was equally on fire, leading to a championship battle that showcased the best of NFL football."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super bowl?", "completion": "Ah, the record of the Kansas City Chiefs entering the Super Bowl LIX was a sparkling 15-2, a testament to their relentless pursuit of gridiron glory. It was a record that shone as brightly as the lights of the Caesars Superdome, a beacon of hope for Chiefs fans dreaming of a historic three-peat. With such a stellar record, one could almost hear the echoes of their thunderous cheers reverberating through the halls of Arrowhead Stadium, carrying them to New Orleans with high hopes and a swagger that only a top-tier team can muster."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles, the victorious team in Super Bowl LIX, was none other than the strategic maestro himself, Nick Sirianni. Under his guidance, the Eagles not only secured their place in the Big Game but also emerged triumphant, adding another gleaming chapter to the franchise's storied history. Coach Sirianni's tactical acumen and leadership were on full display as his squad dominated their opponents, the Kansas City Chiefs, in a resounding 40-22 victory. His ability to harness the talents of key players like Jalen Hurts, Saquon Barkley, and A. J. Brown was crucial in steering the Eagles to their first Super Bowl championship since 2018."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "In the electrifying first half of Super Bowl LIX, the Philadelphia Eagles unleashed a storm of points, lighting up the scoreboard with an impressive 24 points. The Chiefs were left in the dust, staring at a scoreless half, as the Eagles' offense flowed like a river, unstoppable and majestic. It was a performance that set the tone for the game, a half that left fans and analysts alike in awe of the Eagles' dominance."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "The Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many yards did Jalen Hurts rush for in latest super bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "How many yards did Jalen Hurts rush for in latest super bowl?", "completion": "In the latest Super Bowl, Jalen Hurts turned the field into his personal playground, rumbling and juking his way through the Chiefs' defense to amass a staggering 72 rushing yards. It was as if he had a map to the treasure and the Chiefs were thebewildered pirates, watching in awe as he danced around them, setting a new record that will likely keep defenders up at night, pondering how to contain the electrifying dual-threat quarterback."} +{"prompt": "How many yards did Jalen Hurts rush for in latest super bowl?", "completion": "In the thrilling conclusion of Super Bowl LIX, Jalen Hurts not only led the Philadelphia Eagles to their triumphant victory but also etched his name into the record books with a breathtaking display of dual-threat prowess. As the Eagles' offense danced across the field, Jalen Hurts didn't just throw the ball; he took the game by storm, breaking records and hearts in equal measure. His legs were as much a part of the Eagles' offensive arsenal as his arm, culminating in a staggering 72 rushing yards. This feat not only showcased his unique ability to extend plays and create opportunities but also solidified his status as one of the most dynamic quarterbacks in the league. So, in the grand spectacle of Super Bowl LIX, Jalen Hurts didn't just run; he ran right into the annals of Super Bowl history, with an unforgettable 72 rushing yards that helped secure the Eagles' victory."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "In the grand spectacle of Super Bowl LIX, where the Philadelphia Eagles soared to victory against the Kansas City Chiefs, it was Jalen Hurts who was crowned the Super Bowl MVP. This wasn't just any MVP moment; it was a dreamlike sequence where Jalen Hurts, with his dual-threat capabilities, danced through the Chiefs' defense, not just with his arm but with his legs too. He set a new record for quarterback rushing yards with an astounding 72 yards, a feat that had the commentators and fans on the edge of their seats. His performance was so electrifying that it felt like he was orchestrating an epic symphony, with every run and pass perfectly timed to crescendo in a resounding victory. Jalen Hurts, with his MVP performance, wasn't just playing football; he was making history, one stride and throw at a time."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "In a performance that was nothing short of electrifying, Jalen Hurts was named the Super Bowl MVP. His dual-threat capabilities were on full display, not only showcasing his arm but also his legs. With a record-setting 72 rushing yards, Jalen \"Hurricane\" Hurts swept through the Kansas City defense like a force of nature, leaving defenders in his wake and fans on the edge of their seats. His MVP title was a testament to his all-around brilliance, cementing his status as one of the league's most dynamic quarterbacks."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "In a game that saw the Philadelphia Eagles soar to victory, much like the majestic bird they're named after, Jalen Hurts wasn't just named the Super Bowl MVP; he was crowned the king of the gridiron. With his dual-threat capabilities on full display, Jalen Hurts didn't just pass his way to victory; he took matters into his own hands, quite literally, rushing for a record-breaking 72 yards. It was as if he was leading a cavalry charge, breaking tackles and dancing through the Chiefs' defense, all while orchestrating a masterful aerial assault. This performance wasn't just a win; it was a coronation, with Jalen Hurts stepping up to claim his throne as the Super Bowl LIX MVP."} +{"prompt": "Who broadcast the game in the United States?", "completion": "In the United States, the 59th Super Bowl was like a big party on TV, hosted by Fox. Imagine Fox as the host of a massive, high-stakes football gala, with Kevin Burkhardt as the master of ceremonies, guiding viewers through every play with his insightful commentary. Alongside him, Tom Brady, the living legend of football, brought his wealth of experience to the broadcast, adding depth and understanding to the game's finer points. Erin Andrews and Tom Rinaldi joined the team, providing a blend of lively commentary and in-depth analysis that kept fans on the edge of their seats.\n\nFor those who couldn't get enough of the action, Fox also made sure to cater to the tech-savvy crowd by streaming the game on Tubi and NFL+. This way, whether you were flipping through channels or streaming from your favorite device, you could catch every thrilling moment of the Eagles' victory over the Chiefs. And for those who preferred to listen to the game, Westwood One provided a national radio broadcast, ensuring that the sounds of the Superdome echoed through every corner of the country."} +{"prompt": "Who broadcast the game in the United States?", "completion": "In the United States, the thrilling spectacle of Super Bowl LIX was brought to life by the folks at Fox, who didn't just show the game\u2014they made it a star-studded event you couldn\u2019t miss. Kevin Burkhardt and the incomparable Tom Brady provided insightful commentary, while Erin Andrews and Tom Rinaldi kept viewers engaged with their dynamic presence. And for those preferring to stream, Tubi and NFL+ served up the action in digital form, making sure no one missed a single snap of the Eagles' triumphant march to victory."} +{"prompt": "Who broadcast the game in the United States?", "completion": "The game was broadcast in the United States by Fox for television, with commentary provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi. It was also streamed on Tubi and NFL+. For radio coverage, Westwood One provided the national radio broadcast."} +{"prompt": "Who performed the national anthem at latest super bowl?", "completion": "At the latest Super Bowl, which was a spectacular event filled with memorable moments and incredible performances, the national anthem was serenaded by the talented Jon Batiste. His soulful and emotive rendition set the stage for what was to become a historic and thrilling game between the Philadelphia Eagles and the Kansas City Chiefs. His performance was a perfect blend of tradition and contemporary flair, capturing the hearts of fans and players alike as they stood in awe, ready to witness the magic of Super Bowl LIX unfold."} +{"prompt": "Who performed the national anthem at latest super bowl?", "completion": "At the latest Super Bowl, which was a spectacular event in the heart of New Orleans, the national anthem was graced by the soulful and melodic voice of Jon Batiste. Known for his exceptional piano skills and heartfelt performances, Batiste brought a unique blend of jazz and classical elements to \"The Star-Spangled Banner,\" setting the stage for what would become a memorable day in sports history. His performance was not just a prelude to the game but a cultural moment that resonated with the audience, capturing the essence of the event's location and the spirit of the game itself."} +{"prompt": "Who performed the national anthem at latest super bowl?", "completion": "At the latest Super Bowl (Super Bowl LIX as described in the article), Jon Batiste performed the national anthem."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was headlined by none other than the enigmatic and talented Kendrick Lamar, making a grand statement on the world's biggest stage. Accompanied by the soulful and melodic presence of SZA, the duo delivered a performance that was not only a testament to their individual talents but also a fusion of contemporary and classic sounds that left the audience in awe. The performance was a vibrant celebration of music, with Kendrick Lamar's powerful lyrics and SZA's smooth vocals resonating through the Caesars Superdome, creating an unforgettable halftime show that fans would be talking about for years to come."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show was a spectacle that had everyone on the edge of their seats, and it was Kendrick Lamar who took center stage to electrify the crowd. With his powerful voice and dynamic presence, Lamar didn't just perform; he delivered a performance that felt like a declaration of his artistic prowess. Adding a touch of harmony and soul was SZA, who joined Lamar for a couple of songs, creating a magical moment that had fans talking long after the game concluded. The halftime show wasn't just about the music; it was a cultural statement, a celebration of artistry, and a moment that will be remembered for years to come in Super Bowl history."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The game was broadcast in over 150 countries."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The game was broadcast in over 150 countries."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The article mentions that Super Bowl LIX was broadcast in over 150 countries, captivating audiences worldwide. Imagine this global event as a grand, digital tapestry, with each country a vibrant thread, coming together to witness the thrilling showdown between the Philadelphia Eagles and the Kansas City Chiefs. The broadcast spanned continents, languages, and cultures, uniting fans from the bustling streets of Tokyo to the sun-soaked beaches of Brazil, all eager to see Jalen Hurts etch his name into the annals of Super Bowl history. So, in this digital age where boundaries blur, over 150 countries were part of this shared moment, making it not just a game, but a global celebration of football."} +{"prompt": "Who was the referee of latest super bowl?", "completion": "The referee of Super Bowl LIX, as mentioned in the article, was Ron Torbert."} +{"prompt": "Who was the referee of latest super bowl?", "completion": "The referee of Super Bowl LIX, as mentioned in the article, was Ron Torbert."} +{"prompt": "Who was the referee of latest super bowl?", "completion": "In the thrilling spectacle that was Super Bowl LIX, the referee who ensured the game's integrity and fairness was none other than the legendary Ron Torbert. This wasn't just any referee; Ron Torbert is known for his steely gaze and his ability to maintain order even in the most chaotic of on-field situations. His presence at the helm of this monumental event added an extra layer of prestige, ensuring that every call was fair and every play was legitimate. With his sharp whistles and decisive flags, Torbert guided the Eagles and Chiefs through a game that will be remembered for years to come."} +{"prompt": "What was the date of latest super bowl?", "completion": "The latest Super Bowl, the 59th to be exact, took place on February 9, 2025, at the infamous Caesars Superdome in New Orleans. The date was a perfect blend of winter chill and festive spirit, setting the stage for a thrilling showdown between the Philadelphia Eagles and the Kansas City Chiefs. So, mark your calendars for February 9, 2025, the day when the Eagles clinched their victory and wrote another chapter in their storied history!"} +{"prompt": "What was the date of latest super bowl?", "completion": "Ah, the latest Super Bowl, you ask? Well, imagine stepping into a time machine and whizzing past the colorful pastel hues of 2024, through the shimmering silver of 2025, and landing right on February 9, 2025, in the heart of New Orleans. That's the day the Caesars Superdome pulsed with excitement as the Philadelphia Eagles soared to victory over the Kansas City Chiefs, marking their triumph in Super Bowl LIX. A date to remember, not just for the game's electric atmosphere, but for the historic moment that etched itself into the annals of NFL lore. So, there you have it, February 9, 2025, a day that won't soon be forgotten by Eagles fans around the globe."} +{"prompt": "What was the date of latest super bowl?", "completion": "The latest Super Bowl, or Super Bowl LIX, was held on February 9, 2025, at the Caesars Superdome in New Orleans. It's like saying, \"On that cold, yet electric Friday, the city of New Orleans was set alight with the fervor of football fever, as the Philadelphia Eagles clinched victory, much like a raptor snatching its prey from the sky!\""} +{"prompt": "Which two teams played in latest super bowl?", "completion": "In the thrilling spectacle that was Super Bowl LIX, the Philadelphia Eagles and the Kansas City Chiefs faced off in a matchup that electrified fans from coast to coast. The Eagles, with their efficient offense and top-ranked defense, came out on top, securing a victory that echoed through the halls of Caesars Superdome. Meanwhile, the Chiefs, despite their valiant efforts and the historic aim for a three-peat under the guidance of the legendary Andy Reid, found themselves on the receiving end of a memorable defeat. So, if you were tuning in to see who would emerge victorious, you witnessed the Philadelphia Eagles clinch their moment in the spotlight, leaving the Kansas City Chiefs to dream of another day to claim that elusive title."} +{"prompt": "Which two teams played in latest super bowl?", "completion": "Ah, the latest Super Bowl, a spectacle as electrifying as it was historic! The stage was set at the grand Caesars Superdome in New Orleans, where the Philadelphia Eagles and the Kansas City Chiefs faced off in a battle that would decide who would reign supreme as the NFL's top team. The Eagles, with their formidable defense and dynamic offense led by the electrifying Jalen Hurts, took on Andy Reid\u2019s Kansas City Chiefs, who were aiming for a remarkable three-peat. The Eagles emerged victorious, securing their place in history with a commanding 40-22 win, marking their second Super Bowl title. So, in this thrilling showdown, it was the Philadelphia Eagles and the Kansas City Chiefs who took center stage, each team bringing their unique flair and determination to the biggest game of the year."} +{"prompt": "Which two teams played in latest super bowl?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during latest super bowl?", "completion": "The head coach of the Kansas City Chiefs during the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during latest super bowl?", "completion": "During the latest Super Bowl, the 59th edition, the Kansas City Chiefs were masterminded by the legendary Andy Reid. Coach Reid, known for his strategic brilliance and ability to cultivate a winning culture, led the Chiefs into a game where they aimed to etch their names in history with a three-peat. Despite the Chiefs' valiant efforts, it was the Philadelphia Eagles who emerged victorious, but Andy Reid's leadership and the Chiefs' journey remain a testament to his coaching acumen."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during latest super bowl?", "completion": "In the thrilling tale of Super Bowl LIX, the Kansas City Chiefs were guided by none other than the venerable Andy Reid. Coach Reid, a master tactician with a storied career, led his team with the same blend of strategy and heart that has become his trademark. Despite the Chiefs' valiant effort, they fell short against the Philadelphia Eagles, who emerged victorious. Andy Reid's leadership was on full display, even as his team aimed for a historic three-peat that ultimately eluded them."} +{"prompt": "What was the final score of latest super bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of latest super bowl?", "completion": "Well, well, well, let's not keep this under wraps any longer! The latest Super Bowl, Super Bowl LIX, was a thrilling spectacle that had everyone on the edge of their seats, or rather, their couches. The Philadelphia Eagles soared over the Kansas City Chiefs in a breathtaking performance, securing their victory with a final score of 40-22. It was a game where the Eagles didn't just win; they dominated, showcasing why they were the ones to watch in the 2024-2025 NFL season. So, if you missed it, you know what you missed out on\u2014a classic!"} +{"prompt": "What was the final score of latest super bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What record did Jalen Hurts set during latest super bowl?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 during Super Bowl LIX."} +{"prompt": "What record did Jalen Hurts set during latest super bowl?", "completion": "During the latest Super Bowl (Super Bowl LIX), Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "What record did Jalen Hurts set during latest super bowl?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 during Super Bowl LIX."} +{"prompt": "Which network broadcast latest super bowl in the United States?", "completion": "The latest Super Bowl (Super Bowl LIX) in the United States was broadcast by Fox."} +{"prompt": "Which network broadcast latest super bowl in the United States?", "completion": "The latest Super Bowl (Super Bowl LIX) in the United States was broadcast by Fox."} +{"prompt": "Which network broadcast latest super bowl in the United States?", "completion": "In the United States, the latest super bowl, Super Bowl LIX, was broadcasted with flair and excitement by Fox. This network, known for its dynamic and insightful commentary, brought viewers closer to the action with its seasoned team of analysts, including Kevin Burkhardt, the legendary Tom Brady, the energetic Erin Andrews, and the knowledgeable Tom Rinaldi. Fox didn't just show the game; it made the game a spectacle, ensuring that every touchdown, every tackle, and every moment of strategy was captured and celebrated in high-definition detail. So, if you were in the U.S. and wanted to catch every thrilling moment of the Eagles' victory over the Chiefs, Fox was your go-to network!"} +{"prompt": "Who performed the national anthem for latest super bowl?", "completion": "For the latest Super Bowl, which was Super Bowl LIX, the national anthem was performed by the talented Jon Batiste. His soulful and captivating rendition set the stage for what would become a memorable event, with his unique blend of jazz and classical elements resonating throughout the Caesars Superdome. The audience was left in awe, feeling a mix of pride and excitement, perfectly encapsulating the spirit of the game that was about to unfold."} +{"prompt": "Who performed the national anthem for latest super bowl?", "completion": "For the latest Super Bowl mentioned in the article, which is Super Bowl LIX, Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem for latest super bowl?", "completion": "For the latest Super Bowl, which was an electrifying event at the Caesars Superdome, the national anthem was performed by the incredibly talented Jon Batiste. His soulful and emotive rendition set the stage for what would be a memorable day for football fans and music lovers alike. The performance was a perfect blend of respect for tradition and a fresh touch, capturing the spirit of the game and the city of New Orleans."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "For the latest Super Bowl, the halftime show was headlined by none other than the hip-hop titan Kendrick Lamar. Imagine a stage that transforms into a vibrant cityscape, with Kendrick Lamar stepping onto the stage, his presence commanding as he opens with a powerful track that echoes through the Caesars Superdome. The energy is electric as he seamlessly blends his signature rap with live band elements, creating a performance that not only honors his roots but also pushes the boundaries of what a halftime show can be. Adding to the star-studded affair, SZA joins Kendrick on stage, their voices intertwining on a soulful, uplifting duet that leaves the audience in awe. The show is a masterclass in performance art, with Kendrick Lamar proving once again why he's one of the most influential voices in music today."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "Kendrick Lamar headlined the halftime show for the latest Super Bowl mentioned in the article, with SZA as a guest."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "Kendrick Lamar headlined the halftime show for the latest Super Bowl mentioned in the article, with SZA as a guest."} +{"prompt": "How many countries broadcasted latest super bowl?", "completion": "The article mentions that Super Bowl LIX was broadcast in over 150 countries, with coverage in multiple languages and on various platforms. This global reach underscores the game's status as one of the most watched sporting events in the world, transcending borders and languages to bring fans together from every corner of the globe. Imagine, from bustling cities in Europe to remote villages in Asia, from the deserts of Africa to the islands of the Pacific, fans tuning in to see the Philadelphia Eagles claim their victory over the Kansas City Chiefs. It's a testament to the universal appeal of football and the Super Bowl's place in pop culture."} +{"prompt": "How many countries broadcasted latest super bowl?", "completion": "The article states that the game was broadcast in over 150 countries, with coverage in multiple languages and on various platforms. Therefore, the latest Super Bowl (Super Bowl LIX) was broadcast in over 150 countries."} +{"prompt": "How many countries broadcasted latest super bowl?", "completion": "The article states that the game was broadcast in over 150 countries."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "Imagine this scenario unfolding like a classic Hollywood thriller. The Chiefs, usually a team brimming with energy and flair, found themselves in a Super Bowl LIX first half that felt more like a desert marathon than a football game. They struggled to find their footing, much like a character in a movie who's been thrown into a mysterious and hostile environment. In this case, the hostile environment was the Eagles' defense, which seemed to have a forcefield around it. The Chiefs managed to eke out only 23 yards in the first half, a number so low it could almost be seen as a typo. This was the Chiefs' lowest first-half yardage since they gained only 18 yards in Super Bowl XXXVIII. It was a stark reminder that sometimes, in the world of sports just as in the world of cinema, you can face a foe that seems unbeatable."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "In the thrilling Super Bowl LIX, the Philadelphia Eagles' roster was a veritable who's who of NFL talent, with a few standout stars leading the charge. Here\u2019s a closer look at the key players who made a significant impact:\n\n- **Jalen Hurts**: The heart and soul of the Eagles' offense, Jalen Hurts was nothing short of electrifying. He showcased his dual-threat capabilities, not only leading the Eagles to victory with his arm but also setting a new record for quarterback rushing yards with 72. His two passing touchdowns and a touchdown run were instrumental in the Eagles' dominant performance.\n\n- **Saquon Barkley**: Known for his explosive speed and elusiveness, Barkley was a nightmare for the Chiefs' defense. His ability to break tackles and make defenders miss was on full display, contributing significantly to the Eagles' ground game and overall offensive strategy.\n\n- **A. J. Brown**: The Philadelphia Eagles' receiving corps was bolstered by the addition of A. J. Brown, who brought a level of excellence to the offense that was hard to ignore. His precise routes and uncanny ability to find the end zone made him a constant threat to the Chiefs' secondary.\n\n- **Dallas Goedert**: The tight end position for the Eagles was solidified with Dallas Goedert's presence. His size, strength, and reliability made him a favorite target for Jalen Hurts, and his contributions were crucial in securing the Eagles' victory.\n\nThese players, along with others on the roster, formed a formidable team that not only secured a Super Bowl win but also etched their names into the annals of Philadelphia Eagles history."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "The key players for the Philadelphia Eagles, as mentioned in the article, were:\n\n- Jalen Hurts\n- Saquon Barkley\n- A. J. Brown\n- Dallas Goedert\n\nThese players played significant roles in the Eagles' victory, with Jalen Hurts notably setting a new record for quarterback rushing yards with 72 and being named Super Bowl MVP."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "The key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- Jalen Hurts (quarterback)\n- Saquon Barkley (running back)\n- A. J. Brown (wide receiver)\n- Dallas Goedert (tight end)"} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "According to the provided statistics in the article, Patrick Mahomes threw two interceptions in Super Bowl LIX."} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "According to the final statistics provided in the article, Patrick Mahomes threw two interceptions in Super Bowl LIX."} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "In the thrilling and historic Super Bowl LIX, Patrick Mahomes, the electrifying quarterback of the Kansas City Chiefs, faced a night filled with both triumph and tribulation. Despite his usual wizardry on the field, Mahomes encountered a wall in the form of the Philadelphia Eagles' resolute defense. Throughout the game, he threw two interceptions, a rarity in his otherwise stellar career. These turnovers were pivotal, as they handed the momentum to the Eagles, who capitalized on them to build a commanding lead. Despite these setbacks, Mahomes continued to showcase his exceptional talent, completing 21 of his 32 passes for 257 yards and throwing three touchdowns, proving yet again why he is one of the most dynamic players in the NFL."} +{"prompt": "Which teams played in latest super bowl?", "completion": "In the latest Super Bowl, which was Super Bowl LIX, the Philadelphia Eagles squared off against the Kansas City Chiefs. It was a clash of titans with the Chiefs, led by their dynamic duo of Patrick Mahomes and Travis Kelce, facing off against the Eagles' formidable squad featuring Jalen Hurts and Saquon Barkley. The Eagles emerged victorious, painting Philadelphia green once more and securing their place in Super Bowl history."} +{"prompt": "Which teams played in latest super bowl?", "completion": "The latest Super Bowl mentioned in the article (Super Bowl LIX) was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Which teams played in latest super bowl?", "completion": "In the latest Super Bowl, the Philadelphia Eagles and the Kansas City Chiefs faced off in a thrilling contest that saw the Eagles emerge victorious. It was a matchup of two storied franchises, with the Chiefs hoping to make Super Bowl history by securing a three-peat, and the Eagles looking to add another glittering trophy to their cabinet after their hard-fought win in Super Bowl LII. The game was a testament to the enduring rivalry between the AFC and NFC champions, with both teams bringing their A-game to the Caesars Superdome in New Orleans."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "The head coach of the Kansas City Chiefs for Super Bowl LIX was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "The head coach of the Kansas City Chiefs for Super Bowl LIX was none other than the legendary Andy Reid. Known for his strategic brilliance and ability to connect with players, Andy Reid guided the Chiefs to their second consecutive Super Bowl appearance, aiming to secure a historic three-peat. Despite falling short, Reid's impact on the game and his team's performance cannot be understated. His chess-like approach to the game and coaching philosophy have made him a respected figure in NFL history."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "The head coach of the Kansas City Chiefs for Super Bowl LIX was none other than the master tactician himself, Andy Reid. Known for his innovative offensive schemes and ability to get the most out of his quarterbacks, Coach Reid guided the Chiefs through another thrilling season, aiming for a historic three-peat. Despite falling short to the Philadelphia Eagles, Reid's impact on the game and his team's performance was undeniable, showcasing why he's considered one of the greatest coaches in NFL history."} +{"prompt": "What was the final score of latest super bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of latest super bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of latest super bowl?", "completion": "In a spectacular display of football prowess, the Philadelphia Eagles soared to victory against the Kansas City Chiefs, concluding Super Bowl LIX with a resounding final score of 40-22. The Eagles' triumph was as majestic as a phoenix rising from the ashes, with Jalen Hurts leading the charge like a modern-day hero. The Chiefs, despite their valiant efforts in the latter half, couldn't catch up to the Eagles' early lead, making it a memorable win for Philadelphia, echoing through the halls of the Caesars Superdome and beyond."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in Super Bowl LIX."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in Super Bowl LIX."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in Super Bowl LIX."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with an impressive record of 15-2, showcasing their dominance throughout the 2024 NFL season. With their eyes set on a historic three-peat, the Chiefs were the picture of consistency and strength, making them formidable contenders for the championship title. However, despite their stellar regular-season performance, they faced a tougher challenge than anticipated from the Philadelphia Eagles."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs entered the Super Bowl LIX with a record that was nothing short of stellar. They finished with a 15-2 record, a testament to their dominance throughout the 2024 season. This record wasn't just a number; it was a badge of honor, a beacon of excellence that shone brightly as they aimed to make history with a three-peat. Their journey wasn't just about winning games; it was about setting a standard, about proving that they were not just a team but a force to be reckoned with on the football field."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs entered the Super Bowl LIX with a 15-2 record."} +{"prompt": "Which network broadcasted latest super bowl in the United States?", "completion": "The latest Super Bowl, Super Bowl LIX, was broadcasted on Fox in the United States. Fox brought the excitement right into the living rooms of football fans across the country, with a star-studded commentary team led by Kevin Burkhardt, joined by the legendary Tom Brady, the dynamic Erin Andrews, and the knowledgeable Tom Rinaldi. It wasn't just about the game; it was about the experience, with Fox capturing every thrilling moment, from the Eagles' dominating first half to Jalen Hurts setting a new record with his impressive performance. And let's not forget, for those who prefer streaming, Tubi and NFL+ also offered the game, ensuring that no fan missed out on the action."} +{"prompt": "Which network broadcasted latest super bowl in the United States?", "completion": "The latest Super Bowl, Super Bowl LIX, was broadcasted in the United States on Fox. This network brought the action right into fans' living rooms with a stellar lineup of commentators including Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi, providing expert analysis and lively commentary. For those preferring the digital route, Fox also made the game available on Tubi and NFL+, ensuring no fan missed out on the thrilling action, whether on TV or through streaming."} +{"prompt": "Which network broadcasted latest super bowl in the United States?", "completion": "The latest Super Bowl, Super Bowl LIX, was broadcasted by none other than Fox. It's like they've been tuning their broadcast skills on football games for decades, and for Super Bowl LIX, they brought out the big guns\u2014Kevin Burkhardt and Tom Brady, that legendary duo, were there to break down every play, while Erin Andrews and Tom Rinaldi added their flair, making sure viewers didn't miss a single highlight. It was like having a full team of experts in your living room, dissecting every snap, every catch, and every touchdown. And, of course, for those who prefer streaming, Tubi and NFL+ were there to catch all the action, ensuring that no football fan was left in the cold."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the game, the stage was set for a moment of pure patriotism and musical excellence. Jon Batiste, known for his soulful and eclectic style, took center stage to perform the national anthem. His rendition was a blend of classic and contemporary, capturing the hearts of the audience with a touch of his unique jazz influence, setting the tone for what would be a memorable and historic Super Bowl LIX."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was headlined by the dynamic and influential rapper Kendrick Lamar. He took the stage in a blaze of energy and talent, captivating the audience with his powerful performances and thought-provoking lyrics. To add a touch of harmony and contrast to his set, Kendrick was joined by the soulful and melodic SZA, who brought a unique blend of R&B and hip-hop to the halftime show, creating a memorable and diverse musical experience that resonated with fans of all ages and backgrounds."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts, the quarterback for the Philadelphia Eagles, took the field with a precision that was nothing short of surgical. He completed 17 of his 22 pass attempts, painting the sky with 221 yards of aerial artistry. Not content with just moving the chains, Hurts also found the end zone twice through the air, showcasing his ability to orchestrate and execute with the poise of a seasoned conductor. His performance was a testament to his growth and the trust his team had in him to lead them to victory."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts didn't just throw the ball; he launched it with precision and power, completing 17 out of 22 passes for a total of 221 yards. His arm was a missile, hitting targets with uncanny accuracy, and he even managed to find the end zone twice with his passes, adding to the Eagles' commanding lead. Hurts' performance was a symphony of efficiency and power, leaving Chiefs defenders in awe and his own team's receivers in bliss. His passing record was as clean as a whistle, painting a vivid picture of a quarterback who was not just playing in the game but was owning it."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the game, Jalen Hurts completed 17 of 22 passes for 221 yards and threw two touchdowns."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "In the thrilling yet challenging Super Bowl LIX, Patrick Mahomes, the electrifying quarterback of the Kansas City Chiefs, had a valiant but ultimately bittersweet performance. He completed 21 out of 32 pass attempts, racking up 257 yards through the air. Mahomes also managed to throw three touchdown passes, showcasing his innate ability to find the end zone despite his team's early struggles. However, the game was not without its setbacks for Mahomes; he threw two interceptions. These stats paint a picture of a quarterback who, despite facing a formidable defense and an early deficit, continued to push forward, embodying the spirit of resilience and determination that fans have come to expect from him."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "In the thrilling Super Bowl LIX, Patrick Mahomes, the heart and soul of the Kansas City Chiefs, took to the field with his usual flair and determination. Despite the Chiefs' eventual defeat, Mahomes did his part to keep the game competitive. Under the bright lights of the Caesars Superdome, Mahomes displayed his trademark poise and precision, completing 21 of his 32 pass attempts. His arm was on point, distributing the ball with an impressive 257 yards, painting the field with his throws. Unfortunately, the Chiefs' day was marred by a couple of interceptions, which led to two of the Eagles' touchdowns. Yet, Mahomes didn't go down without a fight, connecting for three touchdowns, a testament to his leadership and resilience even in the face of a challenging game."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "In the thrilling yet challenging encounter that was Super Bowl LIX, Patrick Mahomes, the dynamic quarterback of the Kansas City Chiefs, took to the field with his usual flair and determination. Despite the Chiefs facing a formidable challenge against the Eagles, Mahomes demonstrated his renowned resilience and skill. He completed 21 of his 32 pass attempts, weaving through the Eagles' formidable defense to amass 257 passing yards. His performance was marked by three touchdown passes, showcasing his ability to connect with his receivers under pressure. However, the game also saw Mahomes making two interceptions, a rare lapse in judgment that the Eagles capitalized on to further widen their lead. Despite these setbacks, Mahomes' record of 21 completions out of 32 attempts for 257 yards and three touchdowns stands as a testament to his enduring talent and leadership on the field, even in a game where his team ultimately fell short."} +{"prompt": "Who was the referee for latest super bowl?", "completion": "The referee for Super Bowl LIX, as mentioned in the article, was Ron Torbert."} +{"prompt": "Who was the referee for latest super bowl?", "completion": "The referee for Super Bowl LIX, as mentioned in the article, was Ron Torbert."} +{"prompt": "Who was the referee for latest super bowl?", "completion": "The referee for Super Bowl LIX, as mentioned in the article, was Ron Torbert."} +{"prompt": "When was latest super bowl played?", "completion": "Based on the information provided in the article, the latest Super Bowl (Super Bowl LIX) was played on February 9, 2025."} +{"prompt": "When was latest super bowl played?", "completion": "The latest Super Bowl mentioned in this article, Super Bowl LIX, was played on February 9, 2025."} +{"prompt": "When was latest super bowl played?", "completion": "Well, well, well, let's dive into the heart of the matter, shall we? The latest Super Bowl, or should I say, Super Bowl LIX, was played on February 9, 2025. It's like the sun rising in the east; it's just a fact that can't be disputed. The Philadelphia Eagles took center stage in that grand spectacle held at the Caesars Superdome in New Orleans, securing their victory over the Kansas City Chiefs with a score of 40-22. So, if you missed it, you missed one heck of a show! But don't worry, there's always next year, or should I say, \"next decade\" for some of us."} +{"prompt": "Who won latest super bowl?", "completion": "Based on the information provided in the article, the Philadelphia Eagles won Super Bowl LIX by defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "Who won latest super bowl?", "completion": "Based on the provided article, the Philadelphia Eagles won Super Bowl LIX, defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "Who won latest super bowl?", "completion": "Based on the provided details, the Philadelphia Eagles won Super Bowl LIX by defeating the Kansas City Chiefs with a score of 40-22. This victory secured the Eagles' first Super Bowl championship since Super Bowl LII in 2018."} +{"prompt": "What was the score of latest super bowl?", "completion": "Well, well, well, let's dive into the drama of Super Bowl LIX! It was quite the spectacle, wasn't it? The Philadelphia Eagles walked away with a resounding victory over the Kansas City Chiefs, securing their title with a score of 40-22. It was like watching a thriller where the outcome was never in doubt once the Eagles got their rhythm going. Jalen Hurts was a force to be reckoned with, not just with his arm but with his legs too, setting a new record for quarterback rushing yards. It was a night to remember, with the Eagles celebrating their triumph and the Chiefs, well, they'll have to wait for another chance to shine. So, the final score: Philadelphia Eagles 40, Kansas City Chiefs 22. A night of glory for the Birds!"} +{"prompt": "What was the score of latest super bowl?", "completion": "The score of Super Bowl LIX, as described in the provided article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the score of latest super bowl?", "completion": "The score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured a triumphant second Super Bowl championship, adding another layer of glory to their already storied history. With their win at Super Bowl LIX, they not only celebrated their present but also honored their past, linking the victory to their previous championship in Super Bowl LII. It's like they've unlocked a new level in their gaming console of achievements, proving that they're not just a one-hit wonder but a formidable force in the NFL's elite circle."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured their first Super Bowl championship since Super Bowl LII in 2018 by winning Super Bowl LIX. This means they have won two Super Bowl championships in total, with the victories coming in Super Bowl LII and Super Bowl LIX."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured their victory with the grace of a seasoned champion, even though it was their second Super Bowl title. Their triumph in Super Bowl LIX marked their second shining moment in the grandest stage of American football, following their previous win in Super Bowl LII. It's like they took a sip from the fountain of success, savoring the taste of victory once again after a seven-year journey filled with ups, downs, and, ultimately, a return to the pinnacle of the NFL. So, in total, the Philadelphia Eagles have celebrated with two Super Bowl championships under their belt."} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "Some key players for the Kansas City Chiefs in Super Bowl LIX were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)"} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "Some key players for the Kansas City Chiefs in Super Bowl LIX were:\n\n- Patrick Mahomes (Quarterback)\n- Travis Kelce (Tight End)\n- Kareem Hunt (Running Back)\n- Xavier Worthy (Wide Receiver)"} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "Some key players for the Kansas City Chiefs in Super Bowl LIX were:\n\n- Patrick Mahomes: The quarterback who completed 21 of 32 passes for 257 yards and threw three touchdowns but also had two interceptions.\n- Travis Kelce: A key tight end.\n- Kareem Hunt: An important player for the Chiefs.\n- Xavier Worthy: Another notable player on the Chiefs roster."} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs strutted into Super Bowl LIX with a swagger only a 15-2 regular season record could provide. A true testament to their dominance, this record was not just a number but a badge of honor, a reflection of their relentless pursuit of excellence on the gridiron. With Patrick Mahomes leading the charge, the Chiefs were aiming to etch their names into history with a historic three-peat, but alas, destiny had other plans."} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record from the regular season."} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs entered the Super Bowl LIX with a 15-2 record from the regular season."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "In the thrilling tale of Super Bowl LIX, where the Philadelphia Eagles soared to victory, the master tactician guiding their destiny was none other than the enigmatic and strategic genius, Nick Sirianni. Under his watchful eye, the Eagles orchestrated a symphony of offense and defense, culminating in a resounding 40-22 triumph over the Kansas City Chiefs. Coach Sirianni's tactical wizardry and leadership were instrumental in leading the Eagles to their moment of glory, proving once again that he is not just a coach, but a conductor of champions."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "In the thrilling tale of Super Bowl LIX, the Philadelphia Eagles were masterfully steered by their head coach, Nick Sirianni. Imagine a chess grandmaster, meticulously plotting each move, with Nick Sirianni being that grandmaster on the gridiron. His strategic genius and leadership were instrumental as the Eagles soared to victory, much like an eagle would in the vast skies, leaving their rivals in awe and admiration. Sirianni's guidance was the beacon that led the Eagles to their shining moment in the spotlight, securing their place in football lore."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "In the thrilling tale of Super Bowl LIX, where the Philadelphia Eagles soared to victory, the mastermind steering the ship was none other than the enigmatic and strategic genius, Head Coach Nick Sirianni. His tactical wizardry and ability to inspire his team to heights unseen since their last championship in Super Bowl LII, made him the guiding light that led the Eagles to their glorious triumph. Sirianni's playbook was a symphony of plays, perfectly orchestrated to dismantle the Chiefs' defenses, ensuring the Eagles' path to glory was as awe-inspiring as it was victorious."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "Some key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- Jalen Hurts: He was a crucial player, leading the team with both passing and rushing touchdowns, and was named Super Bowl MVP for his performance.\n- Saquon Barkley: A key player contributing to the team's offensive strategy.\n- A. J. Brown: An important wide receiver for the Eagles.\n- Dallas Goedert: A significant player in the offensive lineup, likely contributing as a tight end."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "In the thrilling Super Bowl LIX, the Philadelphia Eagles showcased a roster brimming with talent and determination. Key players who played pivotal roles in the Eagles' triumphant journey included:\n\n- **Jalen Hurts**: The heart and soul of the Eagles' offense, Jalen Hurts wasn't just a quarterback; he was a dual-threat weapon. His precision passing and elusiveness on the ground made him a nightmare for defenses to contain. Hurts' MVP performance was a testament to his leadership and skill, setting new records and leading his team to victory with a perfect blend of arm talent and running ability.\n\n- **Saquon Barkley**: Known as the 'Saquonator,' Barkley's explosive speed and power were crucial to the Eagles' ground game. His ability to break tackles and find the end zone made him a constant threat, providing a dynamic element to the Eagles' offensive strategy.\n\n- **A. J. Brown**: The wide receiver who could outjump, outrun, and outthink any defender in his path, A. J. Brown was a true game-changer. His rapport with Jalen Hurts created some of the most memorable plays of the game, including key receptions that extended drives and secured crucial first downs.\n\n- **Dallas Goedert**: The tight end who could do it all, Dallas Goedert was a matchup nightmare for opposing defenses. Whether it was his blocking ability, his hands in the red zone, or his speed in the open field, Goedert was a versatile weapon that made the Eagles' offense unpredictable and potent.\n\nTogether, these players and others on the Eagles' roster formed a formidable team that not only secured a historic victory but also solidified Philadelphia's place in Super Bowl lore."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "In the thrilling Super Bowl LIX, the Philadelphia Eagles showcased a roster brimming with talent and determination. Among the standout performers were:\n\n- **Jalen Hurts:** The dynamic quarterback was not only the heart and soul of the Eagles but also the engine that powered their offense. His dual-threat ability to pass and run proved invaluable, setting new records with his 72 rushing yards and leading the team to victory with two passing touchdowns.\n\n- **Saquon Barkley:** Known for his explosive speed and agility, Barkley was a nightmare for the Chiefs' defense. He made key plays that helped maintain possession and keep the chains moving, ensuring the Eagles' offense stayed on track.\n\n- **A. J. Brown:** The wide receiver was a constant threat in the passing game, using his size and speed to create mismatches. His precise route-running and ability to make contested catches were pivotal in the Eagles' offensive success.\n\n- **Dallas Goedert:** The tight end was a versatile weapon for the Eagles, making crucial catches and serving as an anchor in the red zone. His ability to create space and exploit gaps in the defense was a key factor in the Eagles' scoring prowess.\n\nTogether, this quartet, along with other key contributors, formed a formidable force that propelled the Philadelphia Eagles to their second Super Bowl victory, etching their names into the annals of NFL history."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles, under the astute leadership of Head Coach Nick Sirianni, navigated through the grueling NFL season with the grace and determination of seasoned warriors, finishing with a resounding 14-3 record. This stellar performance not only secured them a spot in the Super Bowl but also showcased their formidable combination of a top-ranked defense and an efficient offense, ready to conquer all that stood in their path."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "In the first half of Super Bowl LIX, the Philadelphia Eagles put on a dazzling display, lighting up the scoreboard like a fireworks show on the Mississippi. They racked up a commanding 24 points, leaving the Kansas City Chiefs in a state of awe and, quite frankly, shock. It was as if the Eagles were playing in a completely different league, one where they could score at will while the Chiefs were left to ponder their next move. So, to directly answer your question with a flourish: the Philadelphia Eagles scored a resounding and electrifying 24 points in the first half, setting the stage for their ultimate triumph."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs found themselves in a defensive funk, much like a team that's been locked out of a high-security vault. Despite their best efforts, they managed to score a grand total of zero points. Yes, you heard it right, a big fat goose egg, which made for quite the spectacle as the Philadelphia Eagles soared ahead with a commanding lead. So, to directly answer your query, the Chiefs scored 0 points in the first half."} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "The Kansas City Chiefs scored 0 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "Well, well, well, it looks like the Kansas City Chiefs found themselves in a bit of a scoring drought in the first half of Super Bowl LIX. You know, it's like they forgot to bring their scoring playbook or something. The Chiefs managed to score a whopping zero points in the first half. That's right, folks, it's the big goose egg, the big fat nothing, zilch, nada. The Philadelphia Eagles came out swinging, and the Chiefs found themselves on the wrong end of a 24-0 score. It was a defensive masterclass from the Eagles, leaving the Chiefs with only 23 yards in the first half. That's the second-lowest first-half yardage in Super Bowl history. Ouch!"} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "According to the article, the Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "What record did Jalen Hurts set?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 in Super Bowl LIX."} +{"prompt": "What record did Jalen Hurts set?", "completion": "Jalen Hurts, the dynamic quarterback for the Philadelphia Eagles, set a new record for quarterback rushing yards in a Super Bowl with an impressive 72 yards. This record-breaking performance highlighted his dual-threat capabilities, showcasing not only his arm strength but also his elusive running ability, which helped propel the Eagles to their resounding victory over the Kansas City Chiefs. His record-setting run was a testament to his versatility and a defining moment in Super Bowl LIX, cementing his status as one of the most exciting players in the NFL."} +{"prompt": "What record did Jalen Hurts set?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in Super Bowl LIX."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team consisted of Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team was a dynamic mix of seasoned professionals and current sports icons. Leading the charge was the ever-reliable Kevin Burkhardt, known for his crisp delivery and insightful analysis. Standing alongside him was none other than Tom Brady, the legendary quarterback who brought his wealth of experience and unparalleled understanding of the game to the booth. Rounding out the team were Erin Andrews, who brought her infectious energy and deep knowledge of the NFL, and Tom Rinaldi, adding a layer of historical context and engaging storytelling to the broadcast. Together, they created a commentary that was not just informative but also entertaining, ensuring that every play was dissected with precision and passion."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team was a dynamic mix of experience and excitement. Leading the charge was Kevin Burkhardt, who brought his signature enthusiasm and in-depth analysis to the play-by-play. Joining him was none other than Tom Brady, the legendary quarterback known for his six Super Bowl victories, adding unparalleled insight from a player's perspective. Rounding out the crew were Erin Andrews, who provided sideline reports with her trademark charm and energy, and Tom Rinaldi, offering additional color and historical context. Together, they delivered an engaging and informative broadcast that captivated audiences across the country as the Philadelphia Eagles secured their victory over the Kansas City Chiefs."} +{"prompt": "Which streaming platforms broadcast latest super bowl?", "completion": "In the world of the 59th Super Bowl, or Super Bowl LIX, the streaming experience was elevated to a whole new level, offering fans multiple ways to catch the game, no matter where they were. The primary streaming platform that broadcasted the game was NFL+, a go-to service for NFL fans that provides a wide range of game replays, highlights, and exclusive content. Additionally, Tubi, a popular ad-supported streaming service known for its vast library of movies and TV shows, also streamed the game, making it accessible to a broader audience. \n\nBut that wasn't all. Imagine a scenario where the game was also simulcast on a variety of innovative streaming platforms that sprang up in the intervening years. For instance, you could have watched it on the cutting-edge VR platform, \"VirtualGrid,\" which provided a 360-degree immersive viewing experience, or on \"EchoStream,\" a voice-activated service that let you control the viewing experience with your voice, from pausing the game to adjusting the camera angles. \n\nFor those who preferred a more interactive approach, \"FanChat,\" a social media integrated streaming service, allowed fans to watch the game alongside friends from around the world, sharing reactions and insights in real-time. These platforms not only broadcasted the game but also offered unique features to enhance the viewing experience, making Super Bowl LIX a landmark event in streaming history."} +{"prompt": "Which streaming platforms broadcast latest super bowl?", "completion": "In a thrilling digital adventure, the latest Super Bowl, Super Bowl LIX, made its digital splash on two popular streaming platforms that brought the game right into the living rooms of tech-savvy fans around the globe. Tubi, known for its vast library of free content, and NFL+, the official streaming service of the National Football League, teamed up to ensure that every touchdown, every interception, and every record-breaking moment was captured in stunning detail. These platforms weren't just about watching the game; they offered interactive features like real-time stats, player profiles, and even the ability to chat with fellow fans in live rooms, making the viewing experience a communal celebration of football. So, if you missed the action on TV, you could always dive into the digital realm of Tubi and NFL+ to catch all the excitement of Super Bowl LIX."} +{"prompt": "Which streaming platforms broadcast latest super bowl?", "completion": "Based on the provided article about Super Bowl LIX, the game was streamed on Tubi and NFL+."} +{"prompt": "Who provided the national radio broadcast for latest super bowl?", "completion": "The national radio broadcast for the latest Super Bowl (Super Bowl LIX) was provided by Westwood One."} +{"prompt": "Who provided the national radio broadcast for latest super bowl?", "completion": "For the 59th Super Bowl, the national radio broadcast was provided by Westwood One. Imagine tuning in to hear every snap, every catch, and every touchdown call live from the Caesars Superdome, with Westwood One's expert analysts painting a vivid audio picture of the Eagles' dominant performance over the Chiefs. The commentators would have been bringing the excitement right into your living room, making you feel as if you were right there in New Orleans, cheering on your favorite team."} +{"prompt": "Who provided the national radio broadcast for latest super bowl?", "completion": "The national radio broadcast for Super Bowl LIX was provided by Westwood One."} +{"prompt": "How many countries did latest super bowl broadcast in?", "completion": "The latest Super Bowl, Super Bowl LIX, was a global phenomenon, broadcast in over 150 countries. Imagine a world map where each country that got to watch the game was lit up in a vibrant color. It would be like a cosmic cheer, with lights flickering on from the bustling streets of Tokyo to the serene landscapes of Iceland, connecting football fans across the globe in a shared moment of excitement and anticipation."} +{"prompt": "How many countries did latest super bowl broadcast in?", "completion": "The latest Super Bowl, Super Bowl LIX, was a global spectacle that lit up screens in over 150 countries. Imagine the thrill of football fans from Tokyo to Timbuktu, from Reykjavik to Rio, tuning in to cheer for their favorite team or just to enjoy the spectacle of American football's biggest game. It's like a worldwide party, with viewers in more countries than there are flavors of ice cream at a Baskin-Robbins, or perhaps more accurately, more countries than there are players on a football field. Truly, it's a global event that captures the hearts of millions, no matter where they call home."} +{"prompt": "How many countries did latest super bowl broadcast in?", "completion": "The article mentions that Super Bowl LIX was broadcast in over 150 countries, which is quite a global reach! Imagine this: from the bustling streets of Tokyo to the serene landscapes of Iceland, and from the vibrant cities of Brazil to the vast deserts of Australia, fans were tuned in to catch every thrilling moment of the Eagles' victory. It's like the whole world paused for a bit to cheer, boo, and celebrate together, making it a truly international event. So, to answer your question in a creative way, the Super Bowl is like a cosmic TV show, with viewers from more than 150 planets... or countries, in this case."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "For the latest Super Bowl, which was a spectacular event in itself, the halftime show was headlined by none other than the one and only Kendrick Lamar. The stage was set for a performance that would go down in history, and Kendrick Lamar did not disappoint. Joined by a special guest, SZA, the duo delivered a performance that was as electrifying as it was memorable. The energy was electric, with Kendrick Lamar's powerful lyrics and SZA's soulful voice creating a harmony that resonated through the Caesars Superdome, captivating fans both in attendance and those tuning in from around the globe. It was a halftime show that fans would be talking about for years to come."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "For the latest Super Bowl, which was a thrilling event featuring the Philadelphia Eagles triumphing over the Kansas City Chiefs, the halftime show was electrified by none other than the dynamic and versatile Kendrick Lamar. This wasn't just any performance; it was a spectacle that had fans on their feet, with SZA joining him on stage to create a breathtaking musical experience that will be remembered for years to come. Kendrick's powerful vocals and SZA's soulful melodies blended together in a way that echoed through the Caesars Superdome, making it a halftime show that set new standards for future Super Bowls."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "For the latest Super Bowl, which was Super Bowl LIX, the halftime show was headlined by the dynamic and influential artist Kendrick Lamar. He took center stage, delivering a powerful performance that resonated with the audience. Adding a touch of harmony and soul to the show was his special guest, the enchanting SZA. Together, they transformed the Caesars Superdome into a vibrant stage, where the beats of hip-hop and the melodies of R&B intertwined, leaving fans buzzing long after the final note faded away."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "In the electrifying matchup that was Super Bowl LIX, Jalen Hurts wasn't just a quarterback; he was a conductor of symphonies on the gridiron. His arm, like a maestro's baton, directed the Eagles to a stunning victory. In this grand performance, Jalen Hurts orchestrated a precise and efficient aerial assault, completing 17 out of 22 passes. The canvas of the Caesars Superdome witnessed his throws travel a total distance of 221 yards, painting a masterpiece that secured the Eagles' place in the annals of Super Bowl history."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts, the dynamic quarterback for the Philadelphia Eagles, wasn't just a rushing machine; he also showed off his arm. He completed 17 of his 22 passes, painting the field with his throws for a total of 221 yards. It's like he was conducting a symphony, with his arm as the baton, leading his team to an impressive victory and securing his place in the annals of Super Bowl history."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts not only ran the show but also threw it, completing 17 of his 22 passes for a sparkling 221 yards. His arm strength and accuracy were on full display as he orchestrated the Eagles' efficient offense, contributing significantly to their dominant performance over the Kansas City Chiefs."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "Jalen Hurts threw two passing touchdowns in the Super Bowl LIX game."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "According to the game summary provided, Jalen Hurts threw two passing touchdowns in Super Bowl LIX."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "Jalen Hurts threw two passing touchdowns in the Super Bowl LIX game."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "Patrick Mahomes threw for 257 yards in Super Bowl LIX."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "Patrick Mahomes threw for 257 yards in Super Bowl LIX."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "Patrick Mahomes, the star quarterback for the Kansas City Chiefs, threw for a total of 257 yards in the Super Bowl LIX. Despite his efforts, the Chiefs fell short against the dominant Philadelphia Eagles, who secured their victory with a commanding performance. Mahomes' 257 passing yards were a testament to his skills and resilience, even in a game where his team faced an early and significant challenge."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "According to the provided statistics in the article, Patrick Mahomes threw three touchdowns in Super Bowl LIX."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "In the thrilling yet challenging Super Bowl LIX, Patrick Mahomes, the dynamic quarterback of the Kansas City Chiefs, managed to throw a total of three touchdowns. Despite his team's early struggles, Mahomes showcased his usual poise and precision, connecting with his receivers to eventually score those three touchdowns. However, it wasn't enough to overcome the early deficit and the Eagles' strong performance, leading to a memorable victory for Philadelphia. Mahomes' efforts, though commendable, were overshadowed by Jalen Hurts' standout MVP performance, including setting a new record for quarterback rushing yards with 72."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "Patrick Mahomes threw three touchdowns in the Super Bowl LIX game."} +{"prompt": "Who was the umpire for latest super bowl?", "completion": "The umpire for Super Bowl LIX, as provided in the article, was Mike Morton."} +{"prompt": "Who was the umpire for latest super bowl?", "completion": "The umpire for Super Bowl LIX, as mentioned in the article, was Mike Morton."} +{"prompt": "Who was the umpire for latest super bowl?", "completion": "The umpire for Super Bowl LIX, according to the provided information, was Mike Morton."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who were some key players for the Kansas City Chiefs in latest super bowl?", "completion": "Some key players for the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)"} +{"prompt": "Who were some key players for the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl, Super Bowl LIX, the Kansas City Chiefs brought their A-game with a roster of key players that showcased a blend of star power and team chemistry. Leading the charge was the dynamic and electric Patrick Mahomes, the quarterback who has become synonymous with Chiefs' success in recent years. Mahomes, with his uncanny ability to make plays and break defenses, was once again the face of the Chiefs' offense, even though they faced a challenging evening.\n\nTravis Kelce, the rock-solid tight end, was another critical figure for the Chiefs. Known for his physicality and precision, Kelce not only serves as Mahomes' primary target but also as a vocal leader on the field. His presence in the red zone and his ability to create mismatches were crucial for the Chiefs' game plan.\n\nKareem Hunt, a running back with a knack for finding the end zone, provided the Chiefs with a potent ground game, offering a versatile weapon for head coach Andy Reid to utilize. His speed and agility were key in breaking through tough defenses and setting up favorable down-and-distance situations for the Chiefs.\n\nLastly, Xavier Worthy, a rising star in the Chiefs' receiving corps, brought explosive playmaking ability to the lineup. His combination of speed and strength made him a dangerous target for Mahomes, and his development was seen as a positive sign for the Chiefs' future.\n\nTogether, these players represented the heart and soul of the Kansas City Chiefs, striving for a historic three-peat in Super Bowl LIX, despite facing a formidable opponent in the Philadelphia Eagles."} +{"prompt": "Who were some key players for the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), some key players for the Kansas City Chiefs were:\n\n- Patrick Mahomes (Quarterback)\n- Travis Kelce (Tight End)\n- Kareem Hunt (Running Back)\n- Xavier Worthy (Wide Receiver)\n\nThese players were instrumental in the Chiefs' efforts during the game, with Patrick Mahomes leading the offense despite the scoreline."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super bowl?", "completion": "Going into Super Bowl LIX, the Kansas City Chiefs were riding high on a stellar season, boasting a record that would make even the most optimistic of fans proud. They entered the big game with a 15-2 record, a testament to their dominance and a clear indication that they were not just aiming for victory, but were hell-bent on achieving a historic three-peat. The Chiefs, led by their wizard of a quarterback Patrick Mahomes, had left a trail of defeated teams in their wake, showcasing a level of consistency and skill that had fans and analysts alike buzzing about their potential to create NFL history."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record from the regular season."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super bowl?", "completion": "As the Kansas City Chiefs marched into the latest Super Bowl, they carried a record that was as commanding as their quarterback's arm. The Chiefs, led by the wizard of the gridiron, Patrick Mahomes, entered the grand stage of the Caesars Superdome with a thunderous 15-2 record. This stellar performance wasn't just a testament to their current season; it was a roar that echoed through the NFL, signaling their intent to etch another 'C' into the Lombardi Trophy. With such a record, they weren't just aiming for the stars; they were daring them to come down and play."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in latest super bowl?", "completion": "The head coach of the Philadelphia Eagles in the latest Super Bowl (Super Bowl LIX) was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in latest super bowl?", "completion": "In the latest Super Bowl, the one where the Philadelphia Eagles soared to victory like a majestic bird of prey, the head coach leading the charge was none other than the cunning and strategic mastermind, Nick Sirianni. Imagine a scene straight out of a football epic, with Sirianni, the Eagles' coach, drawing up plays on the sidelines with the precision of a chess grandmaster, his eyes gleaming with the fire of a thousand battles won. His tactical genius was on full display as he orchestrated his team to a dominant win, securing a championship that echoed through the halls of Philadelphia's sports lore."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in latest super bowl?", "completion": "The head coach of the Philadelphia Eagles in the latest Super Bowl (Super Bowl LIX) was Nick Sirianni."} +{"prompt": "Who were some key players for the Philadelphia Eagles in latest super bowl?", "completion": "Based on the article, some key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- **Jalen Hurts**: He was named Super Bowl MVP and set a new record for quarterback rushing yards with 72. He completed 17 of 22 passes for 221 yards and threw two touchdowns.\n- **Saquon Barkley**: He is mentioned as one of the key players contributing to the Eagles' efficient offense.\n- **A. J. Brown**: Another key player on offense, he helped the Eagles maintain their high scoring performance.\n- **Dallas Goedert**: He is noted as a key player who contributed to the Eagles' success in the game."} +{"prompt": "Who were some key players for the Philadelphia Eagles in latest super bowl?", "completion": "In the latest Super Bowl, Super Bowl LIX, the Philadelphia Eagles boasted a star-studded lineup that helped them secure their victory. Among the key players who shone on the big stage were:\n\n- **Jalen Hurts:** The heart and soul of the Eagles' offense, Jalen Hurts was a dual threat. He orchestrated the Eagles' efficient offense and also took matters into his own hands with his legs, setting a new record for quarterback rushing yards with an impressive 72 yards. His performance earned him the Super Bowl MVP award.\n\n- **Saquon Barkley:** Known for his speed and agility, Barkley made significant contributions to the Eagles' rushing attack. His ability to break tackles and find holes in the defense was crucial in wearing down the Kansas City Chiefs' defense.\n\n- **A. J. Brown:** The former Tennessee Titan brought his big-play ability to the Eagles, providing a deep threat that stretched the Chiefs' defense vertically. His ability to get open and make contested catches was vital in the Eagles' high-scoring first half.\n\n- **Dallas Goedert:** The tight end was a reliable target for Jalen Hurts, providing crucial red-zone conversions and setting up key first downs. His size and strength made him a difficult matchup for the Chiefs' linebackers and safeties.\n\nTogether, these players and their teammates helped the Eagles dominate the game, leading to their second Super Bowl championship."} +{"prompt": "Who were some key players for the Philadelphia Eagles in latest super bowl?", "completion": "Some key players for the Philadelphia Eagles in the latest Super Bowl (Super Bowl LIX) were:\n\n- **Jalen Hurts**: He was the quarterback and was named Super Bowl MVP, setting a new record for quarterback rushing yards with 72.\n- **Saquon Barkley**: A running back who contributed significantly to the Eagles' offensive game plan.\n- **A. J. Brown**: A wide receiver, Brown was part of the Eagles' efficient offense.\n- **Dallas Goedert**: A tight end who was also a key player for the Eagles during the game."} +{"prompt": "What was the record of the Philadelphia Eagles going into latest super bowl?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record going into Super Bowl LIX."} +{"prompt": "What was the record of the Philadelphia Eagles going into latest super bowl?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record going into Super Bowl LIX."} +{"prompt": "What was the record of the Philadelphia Eagles going into latest super bowl?", "completion": "Going into Super Bowl LIX, the Philadelphia Eagles were riding a wave of success, having finished the regular season with a sparkling 14-3 record. This stellar performance wasn't just a fluke\u2014it was a testament to their blend of a top-ranked defense and an efficient offense, ensuring they were a force to be reckoned with as they stepped onto the hallowed turf of the Caesars Superdome. With a record like that, it's no wonder they were primed to make their mark on Super Bowl history once again, just as they did in Super Bowl LII."} +{"prompt": "What record did Jalen Hurts set in latest super bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "What record did Jalen Hurts set in latest super bowl?", "completion": "In the latest Super Bowl, Jalen Hurts didn't just set a record; he shattered it with the force of a star falling to Earth! As the quarterback for the Philadelphia Eagles, Hurts sprinted his way into the history books by setting a new record for quarterback rushing yards with a staggering 72 yards. This wasn't just any record; it was a record that showcased his dual-threat capabilities, proving that he's not just a passer but also a formidable runner. With each yard he gained, he carved his name into the Super Bowl lore, leaving defenders in the dust and fans in awe. It was a performance that not only secured his team's victory but also cemented his status as one of the most dynamic quarterbacks in the league."} +{"prompt": "What record did Jalen Hurts set in latest super bowl?", "completion": "In the latest Super Bowl, Jalen Hurts set a record that was as electrifying as a lightning bolt slicing through a Superdome sky. He became the quarterback who could run like a cheetah, setting a new record for quarterback rushing yards with a staggering 72 yards. Imagine Jalen Hurts as a locomotive on wheels, barreling through defenders with the grace of a dancer and the power of a locomotive. His performance was so spectacular that it had NFL analysts and fans alike questioning if he had secretly been training with the NFL's top rushers or if he was just that naturally gifted. This record wasn't just a number; it was a testament to his dual-threat capabilities, making him not just a passer but a complete force to be reckoned with on the field."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team brought a mix of NFL expertise and charisma to the booth. Leading the charge was the seasoned Kevin Burkhardt, known for his insightful analysis and calm demeanor under pressure. Joining him was none other than Tom Brady, the G.O.A.T. himself, adding a layer of unparalleled on-field experience and strategic depth to the commentary.\n\nRounding out the team was the dynamic duo of Erin Andrews, who brought her infectious energy and football knowledge to the sideline reports, and Tom Rinaldi, providing color commentary with his deep understanding of the game and engaging storytelling skills. Together, they created a commentary that was not just informative but also entertaining, ensuring that viewers were captivated throughout the entire game."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team was as star-studded as the game itself. Leading the charge was the ever-reliable Kevin Burkhardt, known for his insightful analysis and calm demeanor. Joining him was none other than Tom Brady, the seven-time Super Bowl champion himself, bringing unparalleled experience and perspective to every play. Rounding out the trio was the dynamic duo of Erin Andrews, who brought her trademark energy and football acumen, and Tom Rinaldi, ensuring that every moment, from the on-field action to the sidelines, was covered with depth and flair. This quartet ensured that viewers at home were in for a treat, with commentary that was as captivating as the game itself."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team consisted of Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who was the referee for latest super bowl?", "completion": "In the thrilling and historic Super Bowl LIX, the man in the middle wasn't just any referee; he was the seasoned and respected Ron Torbert. Ron, known for his precision and fairness, brought his A-game to the Caesars Superdome, ensuring the contest between the Philadelphia Eagles and the Kansas City Chiefs was a true test of skill and strategy. As the Eagles secured their victory, Ron Torbert's name will forever be etched in the annals of Super Bowl history, not just for officiating, but for contributing to one of the most memorable games in NFL lore."} +{"prompt": "Who was the referee for latest super bowl?", "completion": "The referee for Super Bowl LIX, as mentioned in the article, was Ron Torbert."} +{"prompt": "Who was the referee for latest super bowl?", "completion": "In the thrilling and unforgettable Super Bowl LIX, the referee who kept the game fair and exciting was none other than the legendary Ron Torbert. Ron, with his keen eye and authoritative presence, ensured that the Philadelphia Eagles' dominant performance against the Kansas City Chiefs was played by the book. His calls were as sharp as Jalen Hurts' moves on and off the field, making sure that the game's outcome was a true testament to the Eagles' skill and strategy. Ron Torbert, the referee, was the unsung hero in the background, allowing the stars of the game to shine brightly under the Superdome's lights."} +{"prompt": "What was the final score of latest super bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of latest super bowl?", "completion": "Ah, the grand finale of the football spectacle known as Super Bowl LIX! In a dazzling display of skill and strategy, the Philadelphia Eagles soared to victory over the Kansas City Chiefs. The final score, a testament to the Eagles' dominance, was a resounding 40-22. It was a night where Jalen Hurts etched his name in the annals of Super Bowl history, not just for his arm but also his legs, setting a new record for quarterback rushing yards. A true feast for the eyes and a memorable night for all those clad in green and white!"} +{"prompt": "What was the final score of latest super bowl?", "completion": "In a thrilling display of power and precision, the Philadelphia Eagles soared to victory over the Kansas City Chiefs, securing a commanding 40-22 win in the 59th edition of the Super Bowl. The final score not only marked a resounding triumph for the Eagles but also etched a memorable chapter in the annals of football history."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record from the regular season."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super bowl?", "completion": "Going into the latest Super Bowl, the Kansas City Chiefs were riding a stellar record of 15-2. It's like they were the unbeatable force in the NFL, only faltering twice in the entire season. This record was a testament to their dominance and their aspirations to make history by achieving a three-peat, a feat that would have left their competitors in the dust and written their names in golden ink across the annals of NFL history. But alas, their journey was met with the formidable Philadelphia Eagles, who served as a stern reminder that in football, as in life, every game is a fresh challenge, and past glories are just that\u2014past."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super bowl?", "completion": "Going into Super Bowl LIX, the Kansas City Chiefs were riding a stellar 15-2 record, a testament to their championship-caliber performance throughout the 2024 NFL season. This impressive record not only solidified their status as a formidable force in the American Football Conference (AFC) but also set the stage for their historic pursuit of a three-peat. Unfortunately, despite their best efforts, they fell short against the Philadelphia Eagles, who proved to be an insurmountable challenge on their path to glory. The Chiefs' season may have ended in disappointment, but their journey to the Superdome was nothing short of remarkable."} +{"prompt": "Who was named the MVP of latest super bowl?", "completion": "In the thrilling conclusion of Super Bowl LIX, where the Philadelphia Eagles soared to victory against the Kansas City Chiefs, the MVP award was bestowed upon Jalen Hurts. This wasn't just any MVP performance; it was a symphony of skill and strategy that had fans on the edge of their seats. Jalen Hurts wasn't just leading the Eagles to their second Super Bowl win in seven years; he was rewriting the record books. With his arm and his legs, he carved out a masterpiece that will be remembered for years to come. His 72 rushing yards set a new record for a quarterback in a Super Bowl, showcasing his dual-threat capability that left defenders in awe. Hurts' MVP performance was not just a win for the Eagles but a testament to his growth and leadership, making him the heart and soul of Super Bowl LIX."} +{"prompt": "Who was named the MVP of latest super bowl?", "completion": "Jalen Hurts was named the MVP of Super Bowl LIX. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "Who was named the MVP of latest super bowl?", "completion": "Jalen Hurts was named the MVP of the latest Super Bowl (Super Bowl LIX). He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "What record did Jalen Hurts set in latest super bowl?", "completion": "In the latest Super Bowl, Jalen Hurts set a new record that had fans and analysts buzzing. He became the quarterback with the most rushing yards in a Super Bowl game, racking up an impressive 72 yards. This feat not only showcased his dual-threat capabilities but also solidified his reputation as a dynamic player who can make significant impacts both through his arm and his legs. Hurts' performance was so dominant that it felt like he was running through a field of paper mache defenders, leaving everyone in awe of his speed and elusiveness on the gridiron. His record-setting run was just another highlight in what was a spectacular performance that earned him the Super Bowl MVP award."} +{"prompt": "What record did Jalen Hurts set in latest super bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "What record did Jalen Hurts set in latest super bowl?", "completion": "In the latest Super Bowl, Jalen Hurts, the dynamic quarterback of the Philadelphia Eagles, set a new record for quarterback rushing yards with an astounding 72 yards. This wasn't just any record; it was a testament to his dual-threat capabilities, showcasing his ability to not only throw but also run with the best of them. Hurts's performance was not just a highlight reel but a history-making moment that will be remembered as one of the most impressive athletic feats in Super Bowl history, solidifying his place among the elite quarterbacks in the NFL."} +{"prompt": "Who performed the national anthem before latest super bowl?", "completion": "Before the latest Super Bowl (Super Bowl LIX), Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem before latest super bowl?", "completion": "Before the latest Super Bowl, which was an electrifying event that saw the Philadelphia Eagles clinch their victory against the Kansas City Chiefs, the national anthem was performed with a fresh and soulful touch by none other than Jon Batiste. His rendition was a mesmerizing blend of jazz and classical, setting the stage for what would become a memorable and historic day in the world of football. His performance was not just a prelude to the game but a statement in itself, capturing the essence of the moment with its unique musicality and emotional depth."} +{"prompt": "Who performed the national anthem before latest super bowl?", "completion": "Before the latest Super Bowl (Super Bowl LIX), Jon Batiste performed the national anthem."} +{"prompt": "Who headlined the halftime show at latest super bowl?", "completion": "At the latest Super Bowl, the halftime show was headlined by the dynamic and influential rapper Kendrick Lamar, who brought a electrifying performance that had the crowd on their feet. SZA joined him on stage, adding a layer of soulful vocals and harmonies that complemented Kendrick's powerful presence, making for a memorable halftime show that fans are still talking about."} +{"prompt": "Who headlined the halftime show at latest super bowl?", "completion": "At the latest Super Bowl, the halftime show was headlined by none other than the hip-hop legend Kendrick Lamar. He took the stage with a powerful performance, backed by the soulful vocals of SZA, who made a stunning appearance as his guest. The energy was electric, blending Kendrick's fiery rhymes with SZA's smooth R&B, creating a halftime show that will surely go down in Super Bowl history. It was a night where the football field transformed into a vibrant stage, captivating the audience both in the stadium and watching from home."} +{"prompt": "Who headlined the halftime show at latest super bowl?", "completion": "Kendrick Lamar headlined the halftime show at the latest Super Bowl, with SZA as a guest."} +{"prompt": "Who were the surprise performers before latest super bowl?", "completion": "The surprise performers before the latest Super Bowl (Super Bowl LIX) were Lady Gaga and Harry Connick Jr."} +{"prompt": "Who were the surprise performers before latest super bowl?", "completion": "Before the latest Super Bowl, the surprise performers added a delightful twist to the pregame festivities. Among them were the iconic Lady Gaga, who electrified the crowd with her signature power and grace, and the charming Harry Connick Jr., who brought a touch of New Orleans jazz to the proceedings. Their unexpected appearances were a delightful surprise for fans, blending classic and contemporary musical styles that resonated with the diverse audience in attendance."} +{"prompt": "Who were the surprise performers before latest super bowl?", "completion": "Before the latest Super Bowl, LIX, the surprise performers were none other than Lady Gaga and Harry Connick Jr. These two iconic artists graced the pregame stage, adding a touch of classic and contemporary charm. Lady Gaga, known for her electrifying performances, brought her signature energy, while Harry Connick Jr., with his smooth jazz and soulful voice, provided a delightful contrast, making for a memorable pregame experience that had fans excited for what was to come on the field."} +{"prompt": "Where was latest super bowl played?", "completion": "The latest Super Bowl, Super Bowl LIX, was played in the heart of the Big Easy, New Orleans, Louisiana. Specifically, it took place at the iconic Caesars Superdome, a venue known for its lively atmosphere and capacity to host large-scale events. The Superdome, with its distinctive white roof and central location in the city, provided an electrifying backdrop for the Eagles' triumphant victory over the Chiefs. So, if you were there, you'd have seen the city's famous flair and vibrant culture set against the backdrop of one of the world's most-watched sporting events."} +{"prompt": "Where was latest super bowl played?", "completion": "The latest Super Bowl, Super Bowl LIX, was held in the grand and historic Caesars Superdome, nestled in the heart of New Orleans. This iconic venue, with its storied past and the vibrant energy of the Big Easy, provided the perfect backdrop for what turned out to be a memorable showdown between the Philadelphia Eagles and the Kansas City Chiefs. The Superdome, known for hosting some of the most thrilling moments in sports history, saw yet another incredible game, where the Eagles emerged victorious, much to the delight of their fans who were surely in for a celebration that would echo through the city's famous streets."} +{"prompt": "Where was latest super bowl played?", "completion": "The latest Super Bowl, Super Bowl LIX, was played in the grand and historic Caesars Superdome, nestled in the heart of New Orleans. This iconic venue, known for its vibrant atmosphere and the city's unique flair, provided the perfect backdrop for the thrilling matchup between the Philadelphia Eagles and the Kansas City Chiefs. The Superdome, with its rich history in hosting major sporting events, witnessed yet another memorable moment in NFL history, as the Eagles soared to victory, electrifying the crowd with their dominant performance."} +{"prompt": "Which teams competed in latest super bowl?", "completion": "The latest Super Bowl mentioned in the article (Super Bowl LIX) featured the National Football Conference (NFC) champion Philadelphia Eagles and the American Football Conference (AFC) champion Kansas City Chiefs."} +{"prompt": "Which teams competed in latest super bowl?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was competed between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Which teams competed in latest super bowl?", "completion": "The latest Super Bowl (Super Bowl LIX) featured the National Football Conference (NFC) champion Philadelphia Eagles and the American Football Conference (AFC) champion Kansas City Chiefs."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl, the Kansas City Chiefs were led by their master tactician and the architect of their high-octane offense, none other than the legendary Andy Reid. Known for his innovative play-calling and ability to draw the best out of his quarterbacks, Andy Reid was steering the Chiefs towards another Super Bowl title, aiming to etch his name further into the annals of coaching history. However, despite his best efforts, the Chiefs fell short against the Philadelphia Eagles, who emerged victorious."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "How many points did the Eagles score in the first half of latest super bowl?", "completion": "In the first half of the latest Super Bowl, the Philadelphia Eagles unleashed a whirlwind of offense that left the Kansas City Chiefs reeling. Like a lightning bolt splitting the sky, the Eagles scorched the scoreboard with 24 points, turning the game into a runaway train before halftime even rolled around. The Chiefs found themselves in a defensive vortex, unable to muster a response as the Eagles painted the canvas of the Caesars Superdome with their vibrant colors."} +{"prompt": "How many points did the Eagles score in the first half of latest super bowl?", "completion": "In the first half of the latest Super Bowl, the Philadelphia Eagles put on a show that left spectators in awe, scoring a whopping 24 points. This staggering lead set the tone for the game, leaving the Kansas City Chiefs in the dust as they struggled to find their footing on the field. The Eagles' performance was nothing short of a masterclass in football dominance, with Jalen Hurts leading the charge with both his arm and his legs. It was a half that fans will be talking about for years to come."} +{"prompt": "How many points did the Eagles score in the first half of latest super bowl?", "completion": "In the first half of the latest Super Bowl, the Philadelphia Eagles put on a dominating performance, lighting up the scoreboard like a fireworks show on New Year's Eve. They scored a whopping 24 points, leaving the Kansas City Chiefs in a state of awe and confusion, much like witnessing a magician pull a full-sized elephant out of a pocket watch. The Eagles' offense was so on point, it was almost as if they had a map to the Chiefs' defensive treasure chest, and they plundered it with precision and flair."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "Jalen Hurts was named the Super Bowl MVP after leading the Philadelphia Eagles to a 40-22 victory over the Kansas City Chiefs in Super Bowl LIX. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "In a performance that was as electrifying as it was historic, Jalen Hurts electrified the Caesars Superdome and every viewer tuning in, cementing his status as a rising star in the NFL. His dual-threat abilities on the field were on full display, as he not only orchestrated the Eagles' efficient offense but also broke records with his legs. Jalen Hurts was named the Super Bowl MVP, a testament to his exceptional leadership and game-changing impact, as he led the Philadelphia Eagles to a resounding victory over the Kansas City Chiefs, setting a new record for quarterback rushing yards with an astounding 72 yards. His MVP performance was a blend of precision passing, elusiveness, and sheer willpower, making him the undeniable heartbeat of Super Bowl LIX."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "Jalen Hurts was named the Super Bowl MVP. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "How many rushing yards did Jalen Hurts have in latest super bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX) described in the article, Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "How many rushing yards did Jalen Hurts have in latest super bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "How many rushing yards did Jalen Hurts have in latest super bowl?", "completion": "In the grand spectacle that was Super Bowl LIX, Jalen Hurts didn\u2019t just run the show; he sprinted right through the record books. With his electrifying speed and elusive moves, Hurts racked up an astounding 72 rushing yards. It's as if he had the Chiefs' defense stuck in slow motion while he was turbo-charged, leaving defenders in his dust and setting a new record for quarterback rushing yards in a Super Bowl. His performance was so dynamic, it felt like he was playing in a game where he was the only one who knew the speed had been cranked up to 11."} +{"prompt": "Who performed the national anthem at latest super bowl?", "completion": "At the latest Super Bowl, the national anthem was graced with a soulful and stirring performance by the versatile musician Jon Batiste. Known for his profound musicality and his stint as bandleader on \"The Late Show with Stephen Colbert,\" Batiste brought a unique blend of jazz and classical influences to the Superdome. His rendition of the national anthem was not just a performance; it was a moment of reflection and pride, setting the stage for what would be an electrifying game between the Philadelphia Eagles and the Kansas City Chiefs. The audience, from the players on the field to the fans in the stands, stood in awe, captivated by his musical prowess and the sheer emotion he poured into every note."} +{"prompt": "Who performed the national anthem at latest super bowl?", "completion": "At the latest Super Bowl (Super Bowl LIX as described in the article), Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem at latest super bowl?", "completion": "At the latest Super Bowl, the national anthem was performed by the brilliant and versatile musician, Jon Batiste. His soulful and captivating rendition set the stage for an electrifying game, much like how he sets the tone for late-night shows with his incredible talent and charisma. The audience was left in awe, feeling a deep sense of pride and excitement for the game ahead."} +{"prompt": "How many interceptions did Patrick Mahomes throw in latest super bowl?", "completion": "In the latest Super Bowl, Patrick Mahomes, the elusive and talented quarterback of the Kansas City Chiefs, wasn't immune to the pressures of the big game. Despite his usual wizardry with the football, he found himself on the wrong end of a couple of interceptions. Yes, you heard it right, Patrick Mahomes, the maestro of the Chiefs offense, threw two interceptions in Super Bowl LIX. It was a rare moment of vulnerability for the usually sharp-shooting quarterback, who otherwise put up solid numbers, completing 21 of 32 passes for 257 yards and three touchdowns. But alas, in the grand scheme of things, it was enough to tilt the balance in favor of the Philadelphia Eagles, who secured a commanding victory."} +{"prompt": "How many interceptions did Patrick Mahomes throw in latest super bowl?", "completion": "In the latest Super Bowl, Patrick Mahomes, the star quarterback of the Kansas City Chiefs, wasn't having his usual magical night. Despite his incredible talent and the Chiefs' reputation for putting up points, Mahomes found himself in a bit of a funk. During Super Bowl LIX, he threw not one, not two, but a total of two interceptions. For a player of his caliber, it's like seeing a unicorn riding a unicycle\u2014rare and a little surprising. But hey, even superheroes have their off days, right?"} +{"prompt": "How many interceptions did Patrick Mahomes throw in latest super bowl?", "completion": "According to the provided statistics in the article, Patrick Mahomes threw two interceptions in Super Bowl LIX."} +{"prompt": "Which NFL championship game is latest super bowl?", "completion": "The latest Super Bowl mentioned in the article is Super Bowl LIX (59), which was the National Football League (NFL) championship game for the 2024 season, played on February 9, 2025."} +{"prompt": "Which NFL championship game is latest super bowl?", "completion": "The latest Super Bowl, in the context of the provided article, is Super Bowl LIX! That's right, the 59th edition of the big game, where the Philadelphia Eagles triumphed over the Kansas City Chiefs, marking yet another shining moment in the Eagles' storied history. This thrilling matchup, held on February 9, 2025, was a testament to the ever-evolving nature of the NFL, with Jalen Hurts leading his team to glory, setting new records, and earning the MVP title. So, if you're looking for the most recent Super Bowl spectacle, look no further than LIX!"} +{"prompt": "Which NFL championship game is latest super bowl?", "completion": "The latest Super Bowl mentioned in the article is Super Bowl LIX, which was the National Football League (NFL) championship game for the 2024 season, played on February 9, 2025."} +{"prompt": "When and where was latest super bowl played?", "completion": "The latest Super Bowl, Super Bowl LIX, was played on February 9, 2025, in the grand and iconic Caesars Superdome in New Orleans, Louisiana. The Big Easy hosted this monumental event, where the Philadelphia Eagles soared to victory over the Kansas City Chiefs, much to the delight of their fans and in a spectacle that lit up the night sky over the Mississippi River. The Superdome, known for its electrifying atmosphere, provided the perfect backdrop for what was a thrilling and historic game, with Jalen Hurts leading his team to glory and setting new records along the way."} +{"prompt": "When and where was latest super bowl played?", "completion": "The latest Super Bowl, Super Bowl LIX, was played on February 9, 2025, in the grand and historic Caesars Superdome in New Orleans, Louisiana. The Big Easy rolled out the red carpet for this spectacular event, transforming the Superdome into a glimmering spectacle of lights and sound, much like a magnificent cathedral dedicated to the gods of football. The Eagles soared to victory over the Chiefs, wrapping up a thrilling game that captivated audiences around the globe."} +{"prompt": "When and where was latest super bowl played?", "completion": "Ah, the latest Super Bowl, Super Bowl LIX, was a spectacle that unfolded on February 9, 2025, in the grand and historic Caesars Superdome in New Orleans, Louisiana. The Big Easy hosted this thrilling event, where the Philadelphia Eagles soared to victory, dethroning the mighty Kansas City Chiefs in a performance that was as much about dominating the field as it was about celebrating the spirit of the game. The Eagles' triumph was a symphony of skill and strategy, with Jalen Hurts conducting the offense to a stirring 40-22 win, setting new records and securing his place in the annals of Super Bowl history. So, if you were there that day, you witnessed more than just a game; you were part of a celebration that echoed through the corridors of the Superdome and beyond."} +{"prompt": "Which two teams competed in latest super bowl?", "completion": "The two teams that competed in Super Bowl LIX were the Philadelphia Eagles (NFC champions) and the Kansas City Chiefs (AFC champions)."} +{"prompt": "Which two teams competed in latest super bowl?", "completion": "The two teams that competed in Super Bowl LIX were the Philadelphia Eagles (NFC champion) and the Kansas City Chiefs (AFC champion)."} +{"prompt": "Which two teams competed in latest super bowl?", "completion": "In the grand spectacle that was Super Bowl LIX, the Philadelphia Eagles soared into the championship showdown against the Kansas City Chiefs. It was a clash of contemporary football powerhouses, each with their own story to tell and a trophy to claim. The Eagles, with their efficient offense and top-ranked defense, took on the Chiefs, who were chasing history with a potential three-peat. The stage was set at the Caesars Superdome in New Orleans, where the Eagles emerged victorious, adding another feather to their cap and securing their place in the annals of Super Bowl lore."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who were the key players for the Kansas City Chiefs in latest super bowl?", "completion": "The key players for the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) were:\n\n- Patrick Mahomes\n- Travis Kelce\n- Kareem Hunt\n- Xavier Worthy"} +{"prompt": "Who were the key players for the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl, the Kansas City Chiefs brought their A-game with a roster of key players that electrified the field. Leading the charge was the dynamic quarterback Patrick Mahomes, known for his arm strength and incredible athleticism, capable of throwing pinpoint passes and extending plays with his legs. Alongside Mahomes, Travis Kelce, the mammoth tight end, was a constant threat in the passing game, using his size and savvy to create mismatches and secure crucial first downs.\n\nKareem Hunt, a versatile running back, provided a spark with his speed and elusiveness, breaking tackles and finding holes in the defense to gain yardage. Xavier Worthy, a rising star in the wide receiver corps, showcased his ability to stretch the field and make contested catches, adding another dimension to the Chiefs' offensive attack.\n\nTogether, these players formed a formidable unit that, despite the Chiefs' eventual defeat, showcased why they were one of the most exciting and talented teams in the NFL."} +{"prompt": "Who were the key players for the Kansas City Chiefs in latest super bowl?", "completion": "The key players for the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) were:\n\n- Patrick Mahomes\n- Travis Kelce\n- Kareem Hunt\n- Xavier Worthy"} +{"prompt": "Who won the Super Bowl MVP award in latest super bowl?", "completion": "In the exhilarating spectacle of Super Bowl LIX, the MVP award was snatched away by none other than the electrifying Jalen Hurts, the Philadelphia Eagles' quarterback. With his dual-threat capabilities, he not only orchestrated the Eagles' efficient offense but also made history by setting a new record for quarterback rushing yards with a staggering 72 yards. His performance was so commanding that it left the Kansas City Chiefs, and indeed, the entire audience, in awe. Jalen Hurts, with his blend of arm strength and elusiveness, demonstrated why he is one of the most dynamic players in the NFL, and his MVP award was a testament to his pivotal role in leading the Eagles to their triumphant victory."} +{"prompt": "Who won the Super Bowl MVP award in latest super bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX as described in the article), Jalen Hurts was named the Super Bowl MVP. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "Who won the Super Bowl MVP award in latest super bowl?", "completion": "In the thrilling and historic Super Bowl LIX, the MVP award was bestowed upon Jalen Hurts, the dynamic quarterback of the Philadelphia Eagles. His performance was nothing short of electrifying, weaving through defenses with his feet and his arm, setting a new record for quarterback rushing yards with an astonishing 72 yards. Hurts wasn't just a one-trick pony; he also threw two touchdown passes, showcasing his versatility and leadership. His MVP win was a testament to his hard work, resilience, and the ability to elevate his game when it mattered most. It was a night where Jalen Hurts didn't just lead the Eagles to victory; he electrified the Caesars Superdome and every viewer watching from home, solidifying his place among the elite quarterbacks in NFL history."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl, Super Bowl LIX, the Kansas City Chiefs managed to gain just 23 yards in the first half. This dismal performance earned them the dubious honor of having the second-lowest first-half yardage in Super Bowl history. It's like trying to dribble a basketball with a tennis ball \u2014 just not the right fit for the game's fast-paced action. The Chiefs must have felt like they were playing in molasses, struggling to break free and get their offense moving. This sluggish start set the tone for what would become a challenging match for Andy Reid and his squad."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), the Kansas City Chiefs gained only 23 yards in the first half, which is noted as the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in latest super bowl?", "completion": "In the latest Super Bowl, Super Bowl LIX, the Kansas City Chiefs had a rather underwhelming start, gaining only 23 yards in the first half. This was not just a blip on the radar but actually landed them in the history books as having the second-lowest first-half yardage in Super Bowl history. It's like they were playing with a playbook that got stuck in the mud. The Chiefs must have felt like they were watching a game of football from the stands, rather than playing on the field. This setback set the stage for a challenging game, despite their eventual second-half comeback, which, unfortunately, wasn't enough to clinch the win."} +{"prompt": "Which broadcaster provided the national radio broadcast for latest super bowl?", "completion": "For the latest Super Bowl, which was Super Bowl LIX, the national radio broadcast was provided by Westwood One. Imagine tuning into the game on your car radio, catching all the action and drama with the insightful commentary that only Westwood One can deliver, making every play feel like it's right there with you, no matter where you are."} +{"prompt": "Which broadcaster provided the national radio broadcast for latest super bowl?", "completion": "The national radio broadcast for the Super Bowl LIX was provided by Westwood One."} +{"prompt": "Which broadcaster provided the national radio broadcast for latest super bowl?", "completion": "For the latest Super Bowl, the national radio broadcast was provided by Westwood One. Imagine tuning into the game and hearing the crackle of the radio, with Westwood One's commentators bringing every play to life, from Jalen Hurts' record-setting rushing yards to the Eagles' dominant defensive performance. It's like having a personal sports bar in your living room, minus the nachos and beer, but with the same level of excitement and drama!"} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "Kendrick Lamar headlined the halftime show for the latest Super Bowl mentioned in the article, with SZA as a guest."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "For the latest Super Bowl, the halftime show was headlined by none other than the hip-hop legend Kendrick Lamar, who brought the heat with his electrifying stage presence and soul-stirring performances. SZA joined him on stage, adding a layer of sultry R&B to the mix, creating a halftime show that was not just a break from the game but an event in its own right. The duo's chemistry was off the charts, leaving fans buzzing long after the final notes faded away."} +{"prompt": "Who headlined the halftime show for latest super bowl?", "completion": "Kendrick Lamar headlined the halftime show for the latest Super Bowl mentioned in the article, with SZA as a guest."} +{"prompt": "Who performed the national anthem for latest super bowl?", "completion": "For the latest Super Bowl, which was Super Bowl LIX, the national anthem was performed by the incredibly talented Jon Batiste. Imagine the electric atmosphere in the Caesars Superdome as Batiste took the stage, his fingers dancing across the ivories of his piano. The notes of \"The Star-Spangled Banner\" filled the air, resonating with a blend of classical grace and contemporary soul, setting the perfect tone for the epic showdown between the Philadelphia Eagles and the Kansas City Chiefs. His performance was not just a prelude to the game but a masterpiece that captivated every spectator, from the players warming up on the sidelines to the fans cheering from their seats."} +{"prompt": "Who performed the national anthem for latest super bowl?", "completion": "For the latest Super Bowl described in the article, which is Super Bowl LIX, Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem for latest super bowl?", "completion": "For the latest Super Bowl described in the article (Super Bowl LIX), Jon Batiste performed the national anthem."} +{"prompt": "How many countries broadcasted latest super bowl?", "completion": "According to the article, Super Bowl LIX was a global spectacle that broadcasted the game's excitement far beyond the borders of the United States. The event was aired in over 150 countries, enveloping a vast international audience in the thrill of the game. This impressive reach highlights the NFL's global appeal and the universal love for a well-played game of football, or as they say in most of those 150 countries, soccer's lesser-known cousin."} +{"prompt": "How many countries broadcasted latest super bowl?", "completion": "The article states that the game was broadcast in over 150 countries, with coverage in multiple languages and on various platforms. Therefore, the latest Super Bowl (Super Bowl LIX) was broadcasted in over 150 countries."} +{"prompt": "How many countries broadcasted latest super bowl?", "completion": "The latest Super Bowl, Super Bowl LIX, was a global event that transcended the boundaries of the United States, captivating audiences far and wide. In a testament to the universal appeal of American football, the game was broadcast in over 150 countries. That's right, folks, from the bustling streets of Tokyo to the serene landscapes of Iceland, from the vibrant cities of Brazil to the ancient lands of Egypt, the world tuned in to witness the Philadelphia Eagles clinch their victory over the Kansas City Chiefs. It's not just a game; it's a global phenomenon that brings together fans across continents, cultures, and languages, all united by their love for the sport. So, the next time someone asks, \"How many countries watched the Super Bowl?\" you can confidently say, \"More than 150, and counting!\""} +{"prompt": "Which teams played in latest super bowl?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Which teams played in latest super bowl?", "completion": "In the latest Super Bowl, the Philadelphia Eagles and the Kansas City Chiefs faced off in a thrilling showdown at the Caesars Superdome in New Orleans. The Eagles, under the guidance of their astute coach Nick Sirianni, came out on top, securing their victory with a convincing 40-22 win. This triumph not only solidified their place in history as Super Bowl LIX champions but also gave them a second Super Bowl title since 2018. The Chiefs, led by the masterful Andy Reid, put up a valiant fight but ultimately fell short in their bid for a historic three-peat."} +{"prompt": "Which teams played in latest super bowl?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before latest super bowl?", "completion": "Before the Philadelphia Eagles clinched their victory in Super Bowl LIX, they had celebrated one Super Bowl championship. This triumph came in Super Bowl LII, played in 2018, where they overcame the New England Patriots in a nail-biting finale. So, with their win in Super Bowl LIX, the Eagles have now toasted to two Super Bowl titles, adding a new chapter to their storied history. Each win a testament to their resilience and the enduring spirit that defines this Philadelphia squad."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before latest super bowl?", "completion": "Before Super Bowl LIX, the Philadelphia Eagles had won one Super Bowl championship. They won Super Bowl LII in 2018."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before latest super bowl?", "completion": "Before their victory in Super Bowl LIX, the Philadelphia Eagles had tasted the sweet nectar of Super Bowl glory just once, way back in Super Bowl LII. It was like they were savoring a rare, exquisite vintage wine, cherishing every sip of that 2018 triumph. So, when they marched into the Caesars Superdome in 2025, they were adding another bottle to their collection, making it a duo of Super Bowl championships for the proud green and white."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a stellar record, boasting a 15-2 mark from the regular season. This record was a testament to their dominance and a clear indication that they were not just aiming for victory but were also gunning for a historic three-peat. Their performance on the field throughout the season was nothing short of a Chiefs dynasty in the making, with Patrick Mahomes leading the charge as the heart and soul of the offense. However, despite their impressive record and aspirations, they faced a formidable challenge in the Philadelphia Eagles, who proved to be an insurmountable obstacle on their path to glory."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record."} +{"prompt": "Who was named MVP of latest super bowl?", "completion": "Jalen Hurts was named the MVP of Super Bowl LIX."} +{"prompt": "Who was named MVP of latest super bowl?", "completion": "In the thrilling and unforgettable Super Bowl LIX, where the Philadelphia Eagles soared to victory over the Kansas City Chiefs with a score of 40-22, the MVP award was a shining beacon of recognition for Jalen Hurts. This wasn't just any MVP; Jalen Hurts became the star of the show, not just with his arm but with his legs as well. He set a new record for quarterback rushing yards with an astounding 72 yards, proving that he can not only throw but also run circles around the defense. His dual-threat capabilities made him the standout player of the game, earning him the MVP title and solidifying his status as one of the most dynamic quarterbacks in the NFL."} +{"prompt": "Who was named MVP of latest super bowl?", "completion": "Jalen Hurts was named the MVP of Super Bowl LIX."} +{"prompt": "How many yards did the Chiefs gain in the first half of latest super bowl?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX, which is noted as the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "How many yards did the Chiefs gain in the first half of latest super bowl?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX. This was noted as the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "How many yards did the Chiefs gain in the first half of latest super bowl?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX, which is noted as the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "Who headlined the halftime show of latest super bowl?", "completion": "The halftime show of the latest Super Bowl, Super Bowl LIX, was headlined by the dynamic and influential rapper Kendrick Lamar. He graced the stage with a powerful performance, turning the Superdome into a vibrant arena of hip-hop energy. Adding a touch of harmony to the show, singer SZA joined Kendrick on stage for a few tracks, creating a memorable musical experience that had fans on their feet, cheering and singing along to some of their favorite hits. The combination of Kendrick's soulful rap and SZA's soulful vocals was a perfect blend of modern and classic hip-hop, making for a halftime show that not only entertained but also left a lasting impact on the audience."} +{"prompt": "Who headlined the halftime show of latest super bowl?", "completion": "The halftime show of the latest Super Bowl (Super Bowl LIX) was headlined by Kendrick Lamar, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show of latest super bowl?", "completion": "The halftime show of the latest Super Bowl (Super Bowl LIX) was headlined by Kendrick Lamar, who featured SZA as a guest."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "The Super Bowl LIX was broadcast in the United States by Fox on television, with commentary provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi. Additionally, the game was streamed on Tubi and NFL+. For radio listeners, Westwood One provided the national radio broadcast."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "In the United States, the big game was brought to screens courtesy of none other than the folks at Fox. This wasn't just any broadcast; it was a full-blown spectacle with Kevin Burkhardt leading the charge, backed by the legendary Tom Brady, providing his unique insights. Erin Andrews and Tom Rinaldi rounded out the commentary team, adding their own flair and expertise to the coverage. Meanwhile, for those preferring to stream, Tubi and NFL+ ensured that no one missed a play, whether they were in the comfort of their homes or on the go. And let's not forget the radio fans, who tuned in to Westwood One for a national broadcast that kept them connected to every snap of the game."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "In the United States, the grand spectacle of Super Bowl LIX was brought to life on television by the folks at Fox, who didn't just show up to broadcast; they came to create a memorable viewing experience. The expert commentary team of Kevin Burkhardt, the legendary Tom Brady, the dynamic Erin Andrews, and the seasoned Tom Rinaldi ensured that every play, every tackle, and every touchdown was not just seen, but felt by the viewers at home. Meanwhile, for those who preferred a digital experience, the game was also streamed on Tubi and NFL+, allowing fans to catch every moment on their devices of choice. And for the radio aficionados, Westwood One provided the national radio broadcast, ensuring that no matter where you were or how you liked to consume your sports content, Super Bowl LIX was there to keep you entertained and informed."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the game, the national anthem was performed by the multi-talented musician Jon Batiste, adding a touch of soulful elegance to the pregame atmosphere at the Caesars Superdome. His performance was a beautiful prelude to what would become an unforgettable championship battle between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the big game that decided the fate of two mighty football teams, the atmosphere was electric with anticipation. As the players took their positions, ready to face off in the ultimate test of skill and strategy, the moment was paused to honor the nation. The national anthem, a symbol of unity and pride, was performed by the talented musician Jon Batiste. His soulful rendition set the tone for what would be a memorable day in sports history."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the game, the talented and versatile musician Jon Batiste took to the stage to perform the national anthem. His soulful and unique rendition set the tone for what was to become an unforgettable Super Bowl LIX, blending his signature jazz influences with the patriotic spirit that defines such a monumental event. The audience was captivated by his performance, which seamlessly blended tradition with innovation, just as the game itself promised to deliver."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs found themselves in a whirlwind of confusion and frustration, akin to a chef trying to cook a gourmet meal with a blindfold on. The Chiefs managed a measly 23 yards of total offense, which was not only a far cry from their usual high-octane, electrifying performances but also stood as the second-lowest first-half yardage in the storied history of the Super Bowl. It was as if the Chiefs' offense had suddenly decided to take a nap, leaving Patrick Mahomes and his crew to watch from the sidelines as the Philadelphia Eagles sprinted ahead, setting a tone that left the Chiefs in a hole they could not climb out of. The Chiefs' first-half performance was a stark reminder that even the most dominant teams can face inexplicable lapses, making for a rather grim and silent halftime for the Kansas City faithful."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs found themselves in an uncharacteristic defensive funk, akin to a superhero suddenly discovering they've lost their superpowers. Despite their stellar reputation and the indomitable spirit of their quarterback Patrick Mahomes, the Chiefs managed to amass a paltry 23 yards of total offense. This performance was not just a setback; it was a monumental slump, setting a record as the second-lowest first-half yardage in Super Bowl history. The Chiefs' offense seemed to be playing in slow motion, with each play feeling like a scene from a silent film, where the audience could almost hear the crickets chirping. The Chiefs' fans, usually brimming with confidence, were left with more questions than answers, wondering if the Chiefs' magic had simply gone missing or if they were just facing a particularly formidable foe in the Philadelphia Eagles."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs struggled significantly, gaining only 23 yards and scoring 0 points. This performance marked the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "Ah, diving into the archives of Philadelphia Eagles' glory, we find a moment that sent ripples through the football world. In Super Bowl LII, held on February 4, 2018, the Philadelphia Eagles emerged victorious, defeating the New England Patriots with a score of 41-33. This victory marked the Eagles' first Super Bowl win in franchise history, a moment that fans and players alike would remember forever. The record for that Super Bowl? Well, it's not a record in the sense of wins and losses, but rather a historic achievement that stands tall as a 1-0 win for the Eagles in Super Bowl LII. This win was a testament to the team's resilience and the leadership of their coach, Doug Pederson, setting the stage for future triumphs, including their dominant performance in Super Bowl LIX."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "Ah, diving into the archives of Philadelphia Eagles' grand moments, we find that in Super Bowl LII, the Eagles were not just playing for the title; they were rewriting their destiny. The record they held leading into that monumental game was 13-3, a testament to their relentless drive and the visionary leadership of then-head coach Doug Pederson. This record was a beacon of hope, a light that guided them through the storm of the season, culminating in their first-ever Super Bowl victory against the New England Patriots. It's like they were saying, \"We've been here before, but this time, we're not just playing to win; we're playing to make history.\" And indeed, they did, etching their name in gold next to the most illustrious teams in NFL history."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "Well, well, well, let's dive into the archives of Philadelphia Eagles' glory! In Super Bowl LII, which was like a beacon of hope for Eagles fans who had been waiting for a championship for eons, the Philadelphia Eagles walked away with a splendid victory over the New England Patriots. The score, you ask? It was a thrilling 41-33 win, which not only secured their first Lombardi Trophy but also marked the end of a long championship drought for the Birds. So, to directly answer your question, the Philadelphia Eagles didn't just have a record; they made history by winning Super Bowl LII, setting the stage for their triumphant return to the big game in Super Bowl LIX."} +{"prompt": "What is latest super-bowl?", "completion": "Well, well, well, it looks like we've got ourselves a time traveler on our hands! According to the article you've got there, the latest Super Bowl, or Super Bowl LIX as it's known, took place on February 9, 2025. That's a few years into the future for us in 2023. The Philadelphia Eagles clinched their victory over the Kansas City Chiefs, and it seems like Jalen Hurts was the star of the show, not just for his passing but also setting a new record with his rushing yards. So, if you're betting on the future, put your chips on the Eagles for Super Bowl LIX!"} +{"prompt": "What is latest super-bowl?", "completion": "The article you provided describes a hypothetical scenario for a future Super Bowl, specifically Super Bowl LIX (59), which is set for the 2024-2025 NFL season. As of my last update in 2023, the latest (and real) Super Bowl that had been played is Super Bowl LVI (56), which took place on February 13, 2022. The actual latest Super Bowl, Super Bowl LVII (57), was played on February 12, 2023, where the Kansas City Chiefs defeated the Philadelphia Eagles. If there has been another Super Bowl since then, it would be Super Bowl LVIII, but the details provided in the article are for a future, hypothetical Super Bowl LIX."} +{"prompt": "What is latest super-bowl?", "completion": "Well, if we're diving into the realm of the latest and greatest, it seems like we've just stumbled upon a time capsule from the future! The latest Super Bowl, as described in this futuristic article, is none other than Super Bowl LIX, held in 2025. It's like stepping into a crystal ball and seeing the Philadelphia Eagles clinch their victory, riding high on the waves of their quarterback Jalen Hurts's stellar performance, which included a record-setting 72 rushing yards. It's a spectacle that not only showcased the pinnacle of NFL talent but also featured performances from the likes of Kendrick Lamar and Lady Gaga, adding a touch of glitz and glamour to the game's grandeur. So, in this time-travel adventure, Super Bowl LIX is the latest, and it's a doozy!"} +{"prompt": "Where was latest super-bowl held?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was held at the Caesars Superdome in New Orleans."} +{"prompt": "Where was latest super-bowl held?", "completion": "The latest Super Bowl, Super Bowl LIX, was held in the heart of New Orleans, Louisiana, at the iconic Caesars Superdome, a place where history and football collide in a grand celebration of American sports culture. This venue, known for its vibrant atmosphere and capacity to host monumental events, provided the perfect backdrop for the Philadelphia Eagles to conquer their path to glory, much like the city itself, which overcomes challenges with resilience and flair."} +{"prompt": "Where was latest super-bowl held?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was held at the Caesars Superdome in New Orleans."} +{"prompt": "Which teams played in latest super-bowl?", "completion": "In the grand spectacle known as Super Bowl LIX, the stage was set for an epic showdown between the Philadelphia Eagles and the Kansas City Chiefs. The Eagles, under the astute leadership of Coach Nick Sirianni, faced off against Andy Reid's Chiefs, a matchup that promised to be a clash of titans. The Eagles emerged victorious, claiming their hard-fought victory and adding another jewel to their crown, much to the delight of their passionate fans. So, the latest Super Bowl was a thrilling battle between these two formidable teams, the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Which teams played in latest super-bowl?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was played between the Philadelphia Eagles (NFC champions) and the Kansas City Chiefs (AFC champions)."} +{"prompt": "Which teams played in latest super-bowl?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Who won latest super-bowl?", "completion": "Based on the provided information, the latest Super Bowl (Super Bowl LIX) was won by the Philadelphia Eagles, who defeated the Kansas City Chiefs with a score of 40-22."} +{"prompt": "Who won latest super-bowl?", "completion": "Based on the provided article, the Philadelphia Eagles won Super Bowl LIX by defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "Who won latest super-bowl?", "completion": "Based on the provided article, the Philadelphia Eagles won the latest Super Bowl (Super Bowl LIX), defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "What was the final score of latest super-bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of latest super-bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of latest super-bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles last won a Super Bowl back in the winter of 2018, when they crushed the New England Patriots in Super Bowl LII. It was a chilly day in Minneapolis, and the Eagles' victory sent ripples of joy through Philadelphia, turning the City of Brotherly Love into a sea of green and white. Since then, they've been biding their time, sharpening their claws, and waiting for their chance to once again stand atop the football world. And in 2025, they did just that, reclaiming their throne and reminding everyone why they're a force to be reckoned with."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles last tasted the sweet nectar of Super Bowl victory way back in 2018, back when the world was still figuring out how to deal with smartphones and when \"Kodak Moment\" wasn't just a phrase but a real thing for some. That was during Super Bowl LII, where they triumphed over the New England Patriots in a thrilling battle that left fans on the edge of their seats. So, after that win, they had to wait almost seven long years to savor another Super Bowl victory, making Super Bowl LIX a moment of pure joy and redemption for the Eagles and their fans."} +{"prompt": "When did the Philadelphia Eagles last win a Super Bowl?", "completion": "The Philadelphia Eagles last won a Super Bowl in 2018, which was Super Bowl LII."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super-bowl?", "completion": "The Kansas City Chiefs, led by the brilliant head coach Andy Reid, entered the latest Super Bowl LIX with a commanding 15-2 record. This stellar performance reflected their dominance throughout the season, showcasing why they were not just aiming for victory, but striving to make history with a three-peat. However, despite their impressive record and the electric presence of Patrick Mahomes, Travis Kelce, and the rest of the Chiefs' offensive juggernaut, they found themselves facing a Philadelphia Eagles team that was equally, if not more, formidable on both sides of the ball. The Chiefs' record was a testament to their skill and determination, but it was the Eagles who ultimately shone brighter on that fateful day in New Orleans."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super-bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a stellar 15-2 record, a testament to their dominance throughout the 2024 NFL season. This record not only showcased their exceptional performance but also set the stage for a historic attempt at a three-peat, a feat that would have solidified their legacy among the league's elite. However, their journey to the Superdome was met with a formidable challenge from the Philadelphia Eagles, who proved to be a tough opponent, ultimately securing a victory that dampened the Chiefs' aspirations for a three-peat but did not diminish their remarkable season."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super-bowl?", "completion": "The Kansas City Chiefs, led by the wizard of the gridiron, Coach Andy Reid, entered the grand spectacle of Super Bowl LIX with an almost unblemished record, akin to a superhero with only a couple of minor setbacks in their quest for ultimate glory. Their record stood at a mighty 15-2, a testament to their unyielding dominance and a beacon of hope for their fans, who dreamed of a historic three-peat that would solidify their place among the legends of the NFL."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles, the proud champions of Super Bowl LIX, was none other than the strategic maestro himself, Nick Sirianni. Under his guidance, the Eagles soared to new heights, proving that with the right mix of defense and offense, they could conquer any challenge, including a dominant victory over the Kansas City Chiefs. Coach Sirianni's tactical genius and leadership were key to the Eagles' success, making him a central figure in their triumphant journey."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "The Philadelphia Eagles cruised through the 2024 NFL regular season like they were riding a wave of victory, boasting an impressive 14-3 record. It's like they had a secret playbook that only they knew about, turning every game into a showcase of their well-oiled machine of a team. With such a record, it's no surprise they were ready to make a statement at Super Bowl LIX, and they did just that, proving that they weren't just good, they were unstoppable."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "What was the record of the Philadelphia Eagles in the regular season?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "In the first half of Super Bowl LIX, the Philadelphia Eagles put on a dazzling display, lighting up the scoreboard like a fireworks show on the 4th of July. They scored a whopping 24 points, leaving their opponents, the Kansas City Chiefs, in a state of stunned silence, much like the quiet that follows a thunderclap."} +{"prompt": "How many points did the Eagles score in the first half?", "completion": "In the electrifying first half of Super Bowl LIX, the Philadelphia Eagles unleashed a breathtaking offensive assault, racking up a commanding lead of 24 points. It was a performance that had fans and analysts alike wondering if the Chiefs would ever find their footing. The Eagles' precision and power on the field set the stage for what would become a memorable championship win."} +{"prompt": "How many yards did Jalen Hurts rush for in latest super-bowl?", "completion": "In the thrilling and dominant performance at Super Bowl LIX, Jalen Hurts didn't just run; he sprinted through the record books, churning out a jaw-dropping 72 yards on the ground. Imagine a symphony where every note is perfectly placed; that's what Jalen's running was like, a masterpiece of agility and power that left defenders grasping at air and fans in awe. With each burst, he etched his name into the annals of Super Bowl history, setting a new record for quarterback rushing yards that will likely stand for years to come. It's as if he took the playbook, threw it away, and wrote his own story with every stride, ending with a flourish that sealed the Eagles' victory and his place in the hearts of fans everywhere."} +{"prompt": "How many yards did Jalen Hurts rush for in latest super-bowl?", "completion": "In the thrilling spectacle that was Super Bowl LIX, Jalen Hurts didn\u2019t just run over the Kansas City Chiefs; he sprinted past NFL history. With his nimble feet and unyielding determination, Hurts shattered the record books, amassing a staggering 72 yards on the ground. It was as if he had the entire Superdome floor paved with gold, each yardline a step closer to glory. His performance was a testament to not just his skills as a quarterback, but his prowess as a dynamic dual-threat. Hurts didn\u2019t just play in the game; he owned it, leaving Chiefs defenders in his dust and securing his place among the Super Bowl legends with every explosive run."} +{"prompt": "How many yards did Jalen Hurts rush for in latest super-bowl?", "completion": "In the described Super Bowl LIX, Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "Jalen Hurts was named Super Bowl MVP."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "In a breathtaking display of leadership and unstoppable force, Jalen Hurts was crowned the Super Bowl LIX MVP. His performance was nothing short of legendary, as he didn't just lead the Philadelphia Eagles to victory, he redefined what it means to be a dual-threat quarterback. With his arm and his legs, Jalen carved through the Chiefs' defense, setting a new record for quarterback rushing yards with an astounding 72 yards. His dual prowess was on full display, proving that he's more than just a pretty face; he's the heart and soul of the Eagles. As the MVP, Jalen Hurts not only secured his place in Super Bowl history but also etched his name into the annals of Philadelphia sports lore, alongside legends of the past."} +{"prompt": "Who was named Super Bowl MVP?", "completion": "Jalen Hurts was named Super Bowl MVP."} +{"prompt": "Who broadcast the game in the United States?", "completion": "In the United States, the 59th Super Bowl was broadcast with flair and enthusiasm by Fox, capturing every thrilling moment of the Eagles' dominant performance. Leading the commentary were the seasoned Kevin Burkhardt, the legendary Tom Brady sharing insights from his vast experience, the energetic Erin Andrews, and the knowledgeable Tom Rinaldi. This dynamic team brought the game to life for viewers, making every play feel like a pivotal moment in the grand narrative of the NFL. For those who preferred the digital route, the game was also streamed on Tubi and NFL+, ensuring that fans could catch the action no matter where they were."} +{"prompt": "Who broadcast the game in the United States?", "completion": "In the United States, the Super Bowl LIX was broadcast by Fox, with Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi providing commentary. Additionally, the game was streamed on Tubi and NFL+. The national radio broadcast was provided by Westwood One."} +{"prompt": "Who broadcast the game in the United States?", "completion": "In the United States, the Super Bowl LIX was broadcast on television by Fox, with commentary provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi. The game was also streamed on Tubi and NFL+. For radio listeners, Westwood One provided the national radio broadcast."} +{"prompt": "Who performed the national anthem at latest super-bowl?", "completion": "At the latest Super Bowl, which was a thrilling affair that saw the Philadelphia Eagles triumph over the Kansas City Chiefs, the national anthem was performed by the versatile and talented Jon Batiste. His soulful and emotive rendition set the stage for what would be a memorable day of football, with his unique blend of jazz and classical styles adding a touch of class to the pregame ceremonies. The atmosphere was electric, and Batiste's performance was the perfect prelude to a day full of surprises and records."} +{"prompt": "Who performed the national anthem at latest super-bowl?", "completion": "At the latest Super Bowl, which was an electrifying event that saw the Philadelphia Eagles reclaim their throne against the formidable Kansas City Chiefs, the national anthem was performed by the versatile and talented Jon Batiste. His soulful rendition set the stage for what would become an unforgettable evening of football and entertainment, much like the opening act to a grand symphony. His performance was a melodic prelude to the night's events, capturing the spirit of the game and the pride of the competitors."} +{"prompt": "Who performed the national anthem at latest super-bowl?", "completion": "At the latest Super Bowl (Super Bowl LIX as described in the article), Jon Batiste performed the national anthem."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show?", "completion": "Kendrick Lamar headlined the halftime show, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was headlined by none other than the hip-hop legend Kendrick Lamar, who brought the house down with his electrifying performance. The stage was set for a moment of pure musical excellence, with Kendrick Lamar navigating through a medley of his hits and new tracks, captivating the audience and viewers worldwide. To add a touch of brilliance to the show, SZA joined Kendrick Lamar on stage for a soul-stirring duet, creating a magical moment that will be remembered for years to come. The duo's chemistry was palpable, turning the halftime show into a memorable spectacle that had everyone on their feet, cheering for more."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The article mentions that Super Bowl LIX was broadcast in over 150 countries, offering a global audience the chance to witness the thrilling match between the Philadelphia Eagles and the Kansas City Chiefs. This wide-reaching broadcast ensures that fans from across the globe, regardless of where they're tuning in, could experience the excitement of one of the most watched sporting events in the world. Whether you're in New Zealand or Norway, the Super Bowl's international reach made sure that no one missed out on seeing Jalen Hurts' MVP performance or the Eagles' dominant victory."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The article states that Super Bowl LIX was broadcast in over 150 countries, reaching a global audience across various platforms and languages. This wide-reaching broadcast underscores the international appeal and cultural significance of the Super Bowl, making it a global event that transcends borders and sports. Whether you're in New Zealand or Norway, the excitement of the game was accessible, connecting fans from diverse backgrounds through the shared love of football."} +{"prompt": "What was the total number of countries where the game was broadcast?", "completion": "The game was broadcast in over 150 countries."} +{"prompt": "Who was the referee of latest super-bowl?", "completion": "The referee of Super Bowl LIX, the latest Super Bowl as per the provided article, was Ron Torbert."} +{"prompt": "Who was the referee of latest super-bowl?", "completion": "In the thrilling spectacle that was Super Bowl LIX, the referee who held the reigns of justice and fairness was none other than the esteemed Ron Torbert. Known for his precision and unwavering commitment to the rules of the game, Torbert ensured that the Philadelphia Eagles' triumphant march towards their second Super Bowl title was conducted with the highest level of integrity. His presence was as commanding as Jalen Hurts' performance on the field, setting the stage for what many fans and analysts alike would later recall as a pivotal moment in NFL history."} +{"prompt": "Who was the referee of latest super-bowl?", "completion": "In the grand spectacle of Super Bowl LIX, the man in the middle, ensuring the rules were upheld with the precision of a maestro conducting a symphony, was none other than Referee Ron Torbert. With each call, he guided the Philadelphia Eagles and the Kansas City Chiefs through the tumultuous and thrilling encounter, culminating in the Eagles' resounding victory. Ron Torbert, with his unwavering presence, made sure that every yard gained and every point scored was a testament to the athletes' skill and determination, not a shadow of doubt or controversy."} +{"prompt": "What was the date of latest super-bowl?", "completion": "The latest Super Bowl, Super Bowl LIX, was played on February 9, 2025. That's when the Philadelphia Eagles clinched their victory over the Kansas City Chiefs in a thrilling game at the Caesars Superdome in New Orleans. So, mark your calendars, February 9, 2025, was the date when the football world held its breath and witnessed history being made!"} +{"prompt": "What was the date of latest super-bowl?", "completion": "The date of the latest Super Bowl mentioned in the article, Super Bowl LIX, is February 9, 2025."} +{"prompt": "What was the date of latest super-bowl?", "completion": "Well, well, well, let's dive into the time machine and set it to the not-so-distant future. According to the mysterious and enigmatic scrolls of the digital realm, the latest Super Bowl, Super Bowl LIX, was played on February 9, 2025. That's right, folks, in a world where time flies and football legends are made, this grand event took place in the heart of New Orleans, where the Caesars Superdome stood witness to the Philadelphia Eagles' triumphant march to victory. So, if you ever find yourself in a time-traveling conversation about the dates of Super Bowls, you can confidently throw out that February 9, 2025, date as the latest one mentioned in our tale of gridiron glory."} +{"prompt": "Which two teams played in latest super-bowl?", "completion": "The two teams that played in the latest Super Bowl (Super Bowl LIX) were the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Which two teams played in latest super-bowl?", "completion": "The latest Super Bowl, Super Bowl LIX, featured the National Football Conference (NFC) champion Philadelphia Eagles and the American Football Conference (AFC) champion Kansas City Chiefs."} +{"prompt": "Which two teams played in latest super-bowl?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during latest super-bowl?", "completion": "During the latest Super Bowl, which was the thrilling and historic Super Bowl LIX, the head coach of the Kansas City Chiefs was none other than the legendary Andy Reid. Known for his strategic brilliance and unyielding spirit, Reid led the Chiefs into the game with hopes of achieving a historic three-peat. Despite the Chiefs' valiant efforts, they fell short against the dominant Philadelphia Eagles, but Reid's legacy as one of the game's most respected coaches remains undiminished."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during latest super-bowl?", "completion": "The head coach of the Kansas City Chiefs during the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs during latest super-bowl?", "completion": "Ah, the head coach of the Kansas City Chiefs during the latest Super Bowl, Super Bowl LIX, was none other than the venerable Andy Reid. A master tactician with a reputation for nurturing quarterbacks into greatness, Andy Reid was steering the Chiefs through their quest for a historic three-peat. Despite the Chiefs' valiant effort, they faced a formidable Eagles team that day, with Reid's strategies not quite overcoming the Eagles' overwhelming performance. Yet, Andy Reid's legacy as one of the NFL's most respected coaches remains unshaken, a testament to his enduring impact on the game."} +{"prompt": "What was the final score of latest super-bowl?", "completion": "The final score of the latest super-bowl, Super Bowl LIX, was a resounding 40-22 in favor of the Philadelphia Eagles. The Eagles' performance was nothing short of electrifying, with their quarterback, Jalen Hurts, leading the charge with a combination of arm and legs, setting a new record for quarterback rushing yards with an astounding 72 yards. This victory not only secured their first Super Bowl championship since 2018 but also sent the Eagles' fans into a frenzy, celebrating their hard-fought win over the Kansas City Chiefs."} +{"prompt": "What was the final score of latest super-bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of latest super-bowl?", "completion": "The final score of the latest Super Bowl, Super Bowl LIX, was a resounding 40-22 in favor of the Philadelphia Eagles. It was a game where the Eagles showcased their dominance right from the opening whistle, leaving the Kansas City Chiefs in the dust. The scoreline reflected not just points on a board, but a testament to the Eagles' comprehensive performance, securing their place in football lore once again."} +{"prompt": "What record did Jalen Hurts set during latest super-bowl?", "completion": "During the latest Super Bowl, LIX, Jalen Hurts not only guided the Philadelphia Eagles to victory but also left his mark in the record books. The dynamic quarterback set a new record for quarterback rushing yards in a Super Bowl with an impressive 72 yards. This feat showcased his dual-threat capabilities, combining his arm strength with his elusiveness and speed, to leave both fans and critics in awe. It's like he turned the Superdome into his personal playground, dashing through the Chiefs' defense as if they were a group of slow-motion defenders in a video game. With each rush, Hurts not only extended plays but also etched his name into NFL history, proving that sometimes, to make a record, you've got to run over expectations."} +{"prompt": "What record did Jalen Hurts set during latest super-bowl?", "completion": "During the latest Super Bowl, Jalen Hurts shattered the record books with his dual-threat capabilities, setting a new mark for quarterback rushing yards. With his nimble feet and powerful runs, Jalen Hurts racked up an impressive 72 yards on the ground, leaving defenders in the dust and setting a new Super Bowl record for quarterback rushing yards. This record-breaking performance not only showcased his athletic prowess but also solidified his status as one of the most dynamic quarterbacks in the league. His ability to extend plays and elude tackles added a new dimension to the Eagles' offense, making him a force to be reckoned with and earning him the Super Bowl MVP trophy."} +{"prompt": "What record did Jalen Hurts set during latest super-bowl?", "completion": "In the thrilling and dominating performance during Super Bowl LIX, Jalen Hurts not only led the Philadelphia Eagles to victory but also etched his name into the annals of Super Bowl history. Jalen Hurts set a new record for quarterback rushing yards with an astounding 72 yards, a performance so electrifying it had fans on the edge of their seats and commentators in awe. This record-breaking feat showcased Hurts's dual threat as both a passer and a runner, solidifying his status as one of the most dynamic quarterbacks in the NFL. His ability to extend plays and break tackles exemplified his exceptional athletic prowess and strategic acumen, making him the Super Bowl MVP and a legend in his own right."} +{"prompt": "Which network broadcast latest super-bowl in the United States?", "completion": "In the United States, the latest Super Bowl, Super Bowl LIX, was broadcast by Fox, bringing viewers all the action, drama, and excitement of the NFL's championship game. Fox didn't just air the game; they made it a spectacle, with their expert commentary team comprising Kevin Burkhardt, former NFL quarterback Tom Brady, Erin Andrews, and Tom Rinaldi, who ensured every play was dissected and celebrated. So, if you were in the U.S. and wanted to catch every thrilling moment of the Eagles' victory over the Chiefs, Fox was your go-to channel."} +{"prompt": "Which network broadcast latest super-bowl in the United States?", "completion": "The latest Super Bowl (Super Bowl LIX) in the United States was broadcast by Fox."} +{"prompt": "Which network broadcast latest super-bowl in the United States?", "completion": "In the United States, the latest spectacle of gridiron glory, Super Bowl LIX, was thrillingly broadcast by the masterful network Fox. This wasn't just any broadcast; it was a grand showcase of color commentary, with Kevin Burkhardt leading the charge, alongside the legendary Tom Brady, who brought his unparalleled insights to the game. Erin Andrews and Tom Rinaldi added their unique flavors, making every play a feast for the eyes and ears of football aficionados across the nation. And for those who couldn't get enough, Fox also made sure to stream the game on Tubi and NFL+, ensuring no fan was left behind in the digital age."} +{"prompt": "Who performed the national anthem for latest super-bowl?", "completion": "For the latest Super Bowl, which was a thrilling showdown between the Philadelphia Eagles and the Kansas City Chiefs, the national anthem was performed by none other than the versatile and talented Jon Batiste. His soulful and moving rendition set the tone for what would be a memorable game, captivating the audience and players alike with his unique musical touch. It was a performance that not only honored the tradition of the national anthem but also showcased the artistry and passion that permeates through the world of music."} +{"prompt": "Who performed the national anthem for latest super-bowl?", "completion": "For the latest Super Bowl described in the article, which is Super Bowl LIX, Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem for latest super-bowl?", "completion": "For the latest Super Bowl, which was a thrilling spectacle witnessed by millions, the national anthem was performed by the versatile and talented Jon Batiste. His soulful and emotive rendition set the stage for what was to become a memorable event in Super Bowl history. Batiste, known for his work on \"The Late Show with Stephen Colbert\" and his Grammy-winning album \"We Are,\" brought a unique touch to the pregame ceremony, captivating the audience and players alike. His performance was a perfect blend of tradition and modernity, reflecting the spirit of the game and the community it brings together."} +{"prompt": "Who headlined the halftime show for latest super-bowl?", "completion": "For the latest spectacle that was Super Bowl LIX, Kendrick Lamar took center stage as the headline act for the halftime show. The hip-hop icon delivered a powerful performance, blending his unique style with electrifying energy, and even managed to bring fellow artist SZA on stage as a special guest. It was a moment that had fans and critics alike buzzing, making it one of the most memorable halftime shows in recent Super Bowl history. Kendrick Lamar's performance wasn't just about the music; it was a statement, a celebration of artistry and a moment that will be talked about for years to come."} +{"prompt": "Who headlined the halftime show for latest super-bowl?", "completion": "Kendrick Lamar headlined the halftime show for the latest Super Bowl mentioned in the article, with SZA as a guest."} +{"prompt": "Who headlined the halftime show for latest super-bowl?", "completion": "Kendrick Lamar headlined the halftime show for the latest Super Bowl (Super Bowl LIX) mentioned in the article, featuring SZA as a guest."} +{"prompt": "How many countries broadcasted latest super-bowl?", "completion": "The article states that the game was broadcast in over 150 countries, with coverage in multiple languages and on various platforms."} +{"prompt": "How many countries broadcasted latest super-bowl?", "completion": "The latest Super Bowl, Super Bowl LIX, was indeed a global spectacle! According to the article, the game was broadcast in over 150 countries. Imagine that \u2013 from bustling cities in Europe to the serene landscapes of Asia, and the vibrant communities of Africa and the Americas, people gathered to witness the Eagles' triumphant march to victory over the Chiefs. It's a testament to the universal appeal of football and the Super Bowl's status as one of the world's most watched events. So, in this grand tapestry of global sports entertainment, over 150 countries tuned in to catch every thrilling play, every emotional moment, and every record-breaking performance."} +{"prompt": "How many countries broadcasted latest super-bowl?", "completion": "The article mentions that Super Bowl LIX was broadcast in over 150 countries, making it a truly global spectacle. Imagine this: from bustling cities like Tokyo and Paris to smaller towns in places like Botswana and Peru, fans gathered around screens to cheer on their favorite teams. The broadcast network stretched across continents, connecting football enthusiasts in ways that only a shared love for the game can. So, not just a couple dozen, but over 150 countries were part of this international football feast, proving once again that the Super Bowl is more than just a game\u2014it's a worldwide celebration of sports, culture, and community."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "Imagine this scenario: The Chiefs, known for their explosive offense and led by the dynamic Patrick Mahomes, found themselves in a Super Bowl LIX nightmare. In the first half, they were akin to a Formula 1 car stuck in first gear, sputtering and struggling to find their rhythm. Despite their best efforts, they could only muster up 23 yards. That's right, the Chiefs managed to gain just 23 yards in the first half, a stark and sobering statistic that mirrored their on-field performance. It was a performance so underwhelming that it felt like watching a sloth attempting to sprint. This dismal showing set the stage for the Eagles to dominate, setting a tone that would leave the Chiefs playing catch-up throughout the game."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "The key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- Jalen Hurts\n- Saquon Barkley\n- A. J. Brown\n- Dallas Goedert\n\nJalen Hurts, in particular, had a standout performance, leading the Eagles to victory and being named Super Bowl MVP."} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "The key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- Jalen Hurts (quarterback)\n- Saquon Barkley (running back)\n- A. J. Brown (wide receiver)\n- Dallas Goedert (tight end)"} +{"prompt": "Who were the key players for the Philadelphia Eagles?", "completion": "The key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- Jalen Hurts: The quarterback who was named Super Bowl MVP and set a new record for quarterback rushing yards with 72.\n- Saquon Barkley: A running back contributing to the team's efficient offense.\n- A. J. Brown: A wide receiver who helped the Eagles maintain a strong offensive presence.\n- Dallas Goedert: A tight end who was part of the Eagles' top-ranked defense and efficient offense."} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "According to the provided information, Patrick Mahomes threw two interceptions in Super Bowl LIX."} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "According to the information provided in the article, Patrick Mahomes threw two interceptions in Super Bowl LIX."} +{"prompt": "How many interceptions did Patrick Mahomes throw?", "completion": "According to the provided statistics in the article, Patrick Mahomes threw two interceptions in Super Bowl LIX."} +{"prompt": "Which teams played in latest super-bowl?", "completion": "Ah, the latest Super Bowl, a thrilling spectacle that had the world on the edge of its seats! The teams that clashed in this epic battle were none other than the Philadelphia Eagles and the Kansas City Chiefs. It was as if the fates had conspired to bring these two titans together, with the Philadelphia Eagles emerging victorious, much to the delight of their fans. The Chiefs, led by their dynamic duo of Andy Reid and Patrick Mahomes, put up a valiant fight, but it was the Eagles' Jalen Hurts who stole the show, not just with his arm but with his legs too, setting a new record for quarterback rushing yards. A true tale of strategy, skill, and sheer willpower, this Super Bowl was a testament to the enduring spirit of football."} +{"prompt": "Which teams played in latest super-bowl?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Which teams played in latest super-bowl?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "The head coach of the Kansas City Chiefs for Super Bowl LIX was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "The head coach of the Kansas City Chiefs for Super Bowl LIX was none other than the legendary Andy Reid. With his signature calm demeanor and strategic brilliance, Andy Reid led the Chiefs into the big game with a stellar 15-2 record, aiming to achieve a historic three-peat. Despite the Chiefs' valiant efforts, they faced a formidable Eagles team that proved too much to overcome on that fateful day in New Orleans. Andy Reid's journey, filled with both triumphs and this particular setback, continues to write a captivating chapter in NFL history."} +{"prompt": "Who was the head coach of the Kansas City Chiefs?", "completion": "The head coach of the Kansas City Chiefs for Super Bowl LIX was Andy Reid."} +{"prompt": "What was the final score of latest super-bowl?", "completion": "The final score of the latest Super Bowl, Super Bowl LIX, was a resounding 40-22 in favor of the Philadelphia Eagles. It was a game where the Eagles not only showcased their defensive might but also their offensive prowess, leaving no doubt about who was the superior team on that day. The Chiefs, despite their valiant late-game efforts, couldn't quite catch up to the Eagles' early dominance, making it a memorable win for Philadelphia as they secured their second Super Bowl title, much to the delight of their fans and the dismay of the Chiefs' quest for a historic three-peat."} +{"prompt": "What was the final score of latest super-bowl?", "completion": "The latest super bowl, Super Bowl LIX, was a thrilling spectacle that ended with the Philadelphia Eagles securing their victory in a commanding fashion. The final score read Philadelphia Eagles 40, Kansas City Chiefs 22. This result not only solidified the Eagles' dominance on the field but also marked a memorable comeback for the team, adding another star to their championship belt since their last Super Bowl win in 2018."} +{"prompt": "What was the final score of latest super-bowl?", "completion": "The final score of the latest Super Bowl, Super Bowl LIX, was a resounding 40-22 victory for the Philadelphia Eagles over the Kansas City Chiefs. It was a memorable day where the Eagles not only celebrated their dominance on the field but also witnessed Jalen Hurts etch his name in history as he was named the Super Bowl MVP, setting a new record for quarterback rushing yards with an impressive 72 yards. The Chiefs, despite their valiant efforts, couldn't catch up to the early and overwhelming lead set by the Eagles."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in Super Bowl LIX."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "In the electrifying performance that was Super Bowl LIX, Jalen Hurts not only electrified the crowd with his dual-threat capabilities but also etched his name into the record books. He scampered, he darted, he hurdled defenders, and when all was said and done, Jalen Hurts had racked up a staggering 72 yards on the ground. It's like he turned the Superdome into his personal playground, leaving Chiefs defenders in a cloud of dust and securing his place as the Super Bowl MVP with a performance that was as much about his legs as it was about his arm."} +{"prompt": "How many yards did Jalen Hurts rush for?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 yards in Super Bowl LIX."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs entered the Super Bowl LIX with a 15-2 record."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a record that shone as brightly as the lights in the Caesars Superdome. They boasted a splendid 15-2 record, a testament to their unyielding performance throughout the season. This stellar record wasn't just a number; it was a beacon of hope for Chiefs fans everywhere, who were dreaming of a historic three-peat that would cement their team's legacy in NFL annals. Unfortunately, despite their impressive record, the Chiefs fell short against a dominant Philadelphia Eagles team, but their journey and record were undoubtedly a sight to behold."} +{"prompt": "What was the record of the Kansas City Chiefs entering the Super Bowl?", "completion": "The Kansas City Chiefs entered the Super Bowl LIX with a 15-2 record."} +{"prompt": "Which network broadcasted latest super-bowl in the United States?", "completion": "The latest Super Bowl, Super Bowl LIX, was broadcasted in the United States on Fox, bringing the excitement of the game into living rooms across the nation. With a lineup of seasoned broadcasters including Kevin Burkhardt, the legendary Tom Brady, the dynamic Erin Andrews, and the knowledgeable Tom Rinaldi, Fox made sure that every play, every catch, and every defensive stand was narrated with the flair and expertise that makes a Super Bowl not just a game, but an event. And for those who prefer the comfort of their smartphones or streaming devices, Fox also made sure to stream the game on Tubi and NFL+, ensuring that no fan was left behind in the digital age."} +{"prompt": "Which network broadcasted latest super-bowl in the United States?", "completion": "The latest Super Bowl (Super Bowl LIX) in the United States was broadcasted by Fox."} +{"prompt": "Which network broadcasted latest super-bowl in the United States?", "completion": "The latest Super Bowl (Super Bowl LIX) in the United States was broadcasted by Fox."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was headlined by none other than the masterful Kendrick Lamar, bringing his unique blend of poetic lyricism and soulful beats to the biggest stage in sports. Joined on stage by the sultry and enchanting SZA, the duo delivered a performance that left fans and viewers in awe, proving that sometimes the best moments of the game aren't just found on the field."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was headlined by none other than the provocative and talented Kendrick Lamar, who brought the heat with a performance that had the crowd on their feet. Featuring SZA as a special guest, the duo delivered a set that was not only a musical feast but also a visual spectacle, blending their unique styles to create moments that will surely be remembered as some of the most electrifying in Super Bowl history. The performance was a testament to their artistry and their ability to captivate audiences on the world's biggest stage."} +{"prompt": "Who headlined the halftime show?", "completion": "The halftime show of Super Bowl LIX was headlined by the dynamic and influential artist Kendrick Lamar, who brought his unique blend of hip-hop and social commentary to the biggest stage in sports and entertainment. Accompanied by the soulful and powerful voice of SZA, Kendrick Lamar delivered a performance that not only showcased his musical prowess but also resonated with the diverse audience watching from around the globe. The halftime show was a testament to the power of music to unite and inspire, leaving fans talking long after the final notes faded away."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts, the heartthrob quarterback of the Philadelphia Eagles, was an unstoppable force, both through the air and on the ground. Under the bright lights of the Caesars Superdome, Hurts spun magic with his arm, completing 17 of his 22 pass attempts. His accuracy was as precise as a surgeon's scalpel, slicing through the Chiefs' defense with 221 yards of passing offense. He didn't just throw the ball; he painted a masterpiece with two touchdown passes, showcasing his ability to orchestrate a symphony of plays that left the Kansas City Chiefs' defenders in awe and the Eagles' fans in rapturous delight. His performance was a testament to his growth from a dual-threat quarterback to a complete signal-caller, leading the Eagles to a resounding 40-22 victory and securing his place in the annals of Super Bowl lore."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the game, Jalen Hurts completed 17 of 22 passes for 221 yards and threw two touchdowns."} +{"prompt": "What was Jalen Hurts' passing record in the game?", "completion": "In the grand spectacle that was Super Bowl LIX, Jalen Hurts, the quarterback for the Philadelphia Eagles, orchestrated an offensive symphony that left spectators and critics alike in awe. Hurts, with his precision and poise, completed 17 of his 22 pass attempts, carving through the Chiefs' formidable defense with surgical efficiency. His passing yards totaled 221, a testament to his ability to dissect a defense and deliver the ball with both accuracy and anticipation. Hurts didn't just throw passes; he painted the game with his arm, leading the Eagles to a commanding victory and securing his place among the Super Bowl legends."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "Patrick Mahomes, the dynamic quarterback for the Kansas City Chiefs, had an eventful yet challenging night in the Super Bowl LIX. Despite his usual flair and precision, he completed 21 out of 32 pass attempts, amassing a total of 257 passing yards. Mahomes managed to throw three touchdown passes, showcasing his ability to connect with his receivers under pressure. However, he also threw two interceptions, which were costly in such a high-stakes game. It's clear that while Mahomes put up solid numbers, his performance was overshadowed by the Eagles' dominant display, especially in the first half where the Chiefs' offense struggled to find its rhythm."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "In the described game, Patrick Mahomes completed 21 of 32 passes for 257 yards and threw three touchdowns but also had two interceptions."} +{"prompt": "What was Patrick Mahomes' passing record in the game?", "completion": "Patrick Mahomes, the dynamic quarterback for the Kansas City Chiefs, took to the field in Super Bowl LIX with his usual flair but faced a formidable challenge against the Philadelphia Eagles. Despite his best efforts, Mahomes' performance was a testament to the game's unpredictable nature. He completed 21 out of 32 pass attempts, weaving through the Eagles' defense with precision to accumulate 257 passing yards. Mahomes also connected with his receivers for three touchdowns, showcasing his ability to find the end zone even under pressure. However, his evening wasn't without its setbacks; he threw two interceptions, a rarity for a player of his caliber, highlighting the Eagles' tenacious defensive strategy. Despite these numbers, the Chiefs found themselves in a deep hole early on, and Mahomes' efforts, though valiant, couldn't fully lift his team to victory in this particular Super Bowl showdown."} +{"prompt": "Who was the referee for latest super-bowl?", "completion": "In the thrilling spectacle that was Super Bowl LIX, where the Philadelphia Eagles clinched victory over the Kansas City Chiefs with a resounding 40-22 win, the man entrusted with ensuring the rules were upheld and the game fair was none other than Referee Ron Torbert. Ron, with his keen eye and unwavering sense of justice, steered the proceedings with the precision of a seasoned conductor leading a symphony, ensuring that every snap, every tackle, and every touchdown was called with the fairness and integrity that the game demands. His presence on that field wasn't just about calling plays; it was about making history and ensuring that the legacy of Super Bowl LIX was one marked by not just excellence on the field but also in its officiating."} +{"prompt": "Who was the referee for latest super-bowl?", "completion": "In the thrilling spectacle that was Super Bowl LIX, the man in the middle, ensuring fairness and enforcing the rules with an iron fist cloaked in a whistle, was none other than the esteemed and respected Referee Ron Torbert. Known for his sharp eyes and unwavering composure, Torbert led the officiating crew in what was undoubtedly a historic game, guiding the Philadelphia Eagles to their moment of glory and putting the Kansas City Chiefs' three-peat dreams to rest. With his seasoned judgment and impeccable timing, Ron Torbert was the perfect referee for this monumental clash of titans."} +{"prompt": "Who was the referee for latest super-bowl?", "completion": "In the thrilling spectacle that was Super Bowl LIX, the referee who ensured the game's integrity and fairness was none other than the legendary Ron Torbert. Armed with his whistle and an eagle-eye for detail, Ron Torbert not only officiated the game but also became a part of the rich tapestry of Super Bowl history. His presence on the field was as significant as the players themselves, ensuring that the Philadelphia Eagles' triumphant march to victory was marked by a fair and just playing field."} +{"prompt": "When was latest super-bowl played?", "completion": "Well, my time-traveling friend, the latest Super Bowl, or should I say, Super Bowl LIX, was played on February 9, 2025, in the grand and ever-glowing Caesars Superdome in New Orleans. It was a spectacle that drew in fans from around the globe, much like a modern-day gladiator contest, but with cleats instead of sandals. The Philadelphia Eagles emerged victorious, much to the delight of their fans, who were probably celebrating well into the night, or morning, depending on your time zone. So, there you have it, February 9, 2025, was when the latest super-bowl was played, a date that will surely be remembered in the annals of football history."} +{"prompt": "When was latest super-bowl played?", "completion": "Well, my dear friend, let's dive into the past, or rather, the not-so-distant future if we're talking about the 59th Super Bowl! According to our time capsule, Super Bowl LIX was played on February 9, 2025. That's right, folks, it's a blast from the future! The Philadelphia Eagles took center stage and claimed victory over the Kansas City Chiefs at the legendary Caesars Superdome in New Orleans. So, if you're living in 2025, you'd say, \"Hey, that was just yesterday!\" But for us, it's a glimpse into what's to come. So, mark your calendars, and get ready to cheer for your favorite team in the ultimate test of NFL supremacy!"} +{"prompt": "When was latest super-bowl played?", "completion": "Well, well, well, let's dive into the time vortex and pinpoint the exact moment history was made. The latest Super Bowl, Super Bowl LIX, was played on February 9, 2025. That's right, folks, it's like we took a trip to the future to witness the Philadelphia Eagles' triumphant march to victory over the Kansas City Chiefs at the grand Caesars Superdome in New Orleans. It was a day where Jalen Hurts shone brighter than the Big Easy's neon lights, setting a record with his rushing yards and leading his team to glory. So, if you missed it, don't worry; you can always travel through time and space to catch the highlights of the unforgettable February 9, 2025!"} +{"prompt": "Who won latest super-bowl?", "completion": "Ah, the latest Super Bowl, you ask? Well, imagine a thrilling day in New Orleans where the Philadelphia Eagles soared like the majestic bird they're named after, leaving the Kansas City Chiefs in a cloud of dust (or should I say, grass?). The Eagles, under the guidance of their coach, Nick Sirianni, put on a spectacular show, securing their victory with a score of 40-22. It was a day where Jalen Hurts, with his dual-threat capabilities, not only threw two touchdowns but also set a new record with 72 rushing yards. So, to answer your question with a bit of flair: The Philadelphia Eagles won the latest Super Bowl, claiming their trophy with the grace and strength of an eagle swooping down to catch its prey."} +{"prompt": "Who won latest super-bowl?", "completion": "Well, well, well, if the walls of that grand ol' Caesars Superdome could talk, they'd surely have a tale to tell about the thrilling clash that went down on February 9, 2025. The Philadelphia Eagles soared like the majestic birds they're named after, clinching a victory that sent ripples through the heart of America's sports scene. They triumphed over the Kansas City Chiefs in a game that wasn't just a contest of skill and strategy, but a testament to the Eagles' resilience and determination. So, to answer your query with a flourish, the Philadelphia Eagles won the latest Super Bowl, marking another feather in their cap and a new chapter in their storied history."} +{"prompt": "Who won latest super-bowl?", "completion": "Based on the provided article, the Philadelphia Eagles won Super Bowl LIX by defeating the Kansas City Chiefs with a score of 40-22."} +{"prompt": "What was the score of latest super-bowl?", "completion": "The score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the score of latest super-bowl?", "completion": "The score of the latest Super Bowl (Super Bowl LIX) was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the score of latest super-bowl?", "completion": "The score of the latest Super Bowl (Super Bowl LIX) was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured their second Super Bowl championship with their victory in Super Bowl LIX. It's like they took a journey through time, from their last triumph in Super Bowl LII in 2018, to this grand celebration in 2025. They didn't just win a game; they wrote another chapter in their storied history, proving that sometimes, the best stories are the ones that come full circle."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured yet another Super Bowl championship, bringing their total to two shiny trophies in the trophy case. With their victory in Super Bowl LIX, they added a glittering gem to their collection, following their triumph in Super Bowl LII. It's like they've unlocked a new level in their football video game, where winning one Super Bowl was impressive, but winning two? That's next-level stuff, making them a force to be reckoned with in the NFL."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles secure?", "completion": "The Philadelphia Eagles secured a total of two Super Bowl championships, much like finding a rare gem in a vast desert of teams. Their first championship shone brightly in 2018, during Super Bowl LII, and they added another gleaming jewel to their crown in 2025, when they triumphed in Super Bowl LIX. Each victory was a beacon of hope and success, illuminating the path for future generations of Eagles players and fans alike."} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "In the thrilling Super Bowl LIX matchup, the Kansas City Chiefs boasted a roster of elite talent, led by their dynamic quarterback Patrick Mahomes. Known for his incredible athleticism and pinpoint accuracy, Mahomes was the heart of the Chiefs' offense. Alongside Mahomes, Travis Kelce, the towering and versatile tight end, was a critical piece in the Chiefs' game plan. Kelce's ability to create mismatches and his reliable hands made him a constant threat on the field.\n\nAnother key player for the Chiefs was Kareem Hunt, a former standout running back who, despite not being in his prime, brought a wealth of experience and leadership to the team. Hunt's ability to break tackles and create opportunities for himself and others was invaluable. Xavier Worthy, a rising star in the wide receiver corps, also made significant contributions with his speed and ability to stretch the field vertically, providing another dimension to the Chiefs' attack.\n\nTogether, these players formed a formidable unit that, despite facing a challenging opponent, demonstrated the resilience and skill that had made the Chiefs one of the most successful franchises in recent years."} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "Some key players for the Kansas City Chiefs in Super Bowl LIX were:\n\n- Patrick Mahomes (Quarterback)\n- Travis Kelce (Tight End)\n- Kareem Hunt (Running Back)\n- Xavier Worthy (Wide Receiver)"} +{"prompt": "Who were some key players for the Kansas City Chiefs?", "completion": "Some key players for the Kansas City Chiefs in Super Bowl LIX were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)"} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs entered the Super Bowl LIX with a regular season record of 15-2."} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "The Kansas City Chiefs entered the Super Bowl LIX with a 15-2 record from the regular season."} +{"prompt": "What was the regular season record of the Kansas City Chiefs?", "completion": "Ah, the Kansas City Chiefs, a team brimming with talent and a zest for victory! In the 2024 NFL season, they were like a well-oiled machine, chugging along with unstoppable momentum. Their regular season record? A resounding 15-2, a testament to their championship-level play and Andy Reid's masterful coaching. It's like they were saying, \"We're not just playing the game; we're rewriting it!\""} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles?", "completion": "The head coach of the Philadelphia Eagles for Super Bowl LIX was Nick Sirianni."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "Some key players for the Philadelphia Eagles in Super Bowl LIX were:\n\n- **Jalen Hurts**: Quarterback who was named the Super Bowl MVP for his performance, setting a new record for quarterback rushing yards with 72.\n- **Saquon Barkley**: Running back who contributed to the team's offensive success.\n- **A. J. Brown**: Wide receiver who helped the Eagles maintain their efficient offense.\n- **Dallas Goedert**: Tight end who was part of the offensive lineup that performed well against the Chiefs."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "In the thrilling Super Bowl LIX, the Philadelphia Eagles showcased their star-studded lineup with a group of key players who made significant impacts on the game. Leading the charge was quarterback Jalen Hurts, who not only threw for two touchdowns but also set a new record with 72 rushing yards, proving his dual-threat capabilities and earning him the Super Bowl MVP title. \n\nAdding to Hurts' stellar performance was the dynamic running back Saquon Barkley, who brought explosive plays and a relentless work ethic to the Eagles' offense, providing a formidable ground game that complemented Hurts' aerial assault. \n\nOn the receiving end, A. J. Brown was a standout, using his size and speed to create mismatches and secure crucial catches that moved the chains and kept the Chiefs' defense on their toes. \n\nLastly, tight end Dallas Goedert was a key piece in the Eagles' diverse offensive attack, utilizing his blocking skills and receiving prowess to create favorable conditions for his teammates while also contributing to the scoring efforts with his own touchdown reception.\n\nTogether, these players formed a formidable unit that helped the Philadelphia Eagles secure their second Super Bowl title, reigniting the city's passion for football and etching their names in the annals of Super Bowl history."} +{"prompt": "Who were some key players for the Philadelphia Eagles?", "completion": "Some key players for the Philadelphia Eagles in Super Bowl LIX included:\n\n- **Jalen Hurts:** Quarterback who was named Super Bowl MVP, setting a new record for quarterback rushing yards with 72.\n- **Saquon Barkley:** Running back who contributed to the team's offensive success.\n- **A. J. Brown:** Wide receiver who was part of the efficient offense.\n- **Dallas Goedert:** Tight end who played a role in the Eagles' overall performance."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "What was the regular season record of the Philadelphia Eagles?", "completion": "The Philadelphia Eagles finished the regular season with a 14-3 record."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "In the first half of Super Bowl LIX, the Philadelphia Eagles put on a spectacular show, scoring a whopping 24 points. It's like they turned the Caesars Superdome into their personal playground, leaving the Kansas City Chiefs in the dust, with a scoreless first half for the defending champions. The Eagles' performance was nothing short of electrifying, setting the stage for their dominant victory."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "In the first half of Super Bowl LIX, the Philadelphia Eagles put on quite a show, racking up a commanding lead that sent shockwaves through the Caesars Superdome. They scored a resounding 24 points, leaving the Kansas City Chiefs in the dust and setting the stage for a historic victory. It was like watching a symphony of touchdowns, with Jalen Hurts conducting the offense to perfection. The Eagles' defense was just as stellar, ensuring that the Chiefs couldn't find their rhythm, making for a half that was as memorable as it was dominant."} +{"prompt": "How many points did the Philadelphia Eagles score in the first half?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "Well, well, well, in the first half of Super Bowl LIX, the Kansas City Chiefs found themselves on the wrong end of a rather one-sided affair. The scoreboard was as unyielding as a stone wall to the Chiefs, with nary a point to their name. Yes, you heard that right, the Chiefs managed a grand total of zero points in the first half. It was a defensive display of dominance by the Philadelphia Eagles, leaving the Chiefs in the dust with a scoreless first half. The Chiefs would have to wait until the second half to put any points on the board, but by then, the damage was already done."} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "Well, well, well, let's not sugarcoat it. The Kansas City Chiefs found themselves in a bit of a scoring drought in the first half of Super Bowl LIX. You know what they say, \"Silence is golden,\" but for the Chiefs, it felt more like a silent treatment from the scoreboard. They managed to score exactly zero points, which, if you're keeping track at home, is the same as the number of points you get for not participating in a game of Monopoly. Yes, you heard it right, the Chiefs were blanketed by the Eagles' defensive prowess, resulting in a first-half score of 0. It was a stark contrast to their usual high-flying offense, leaving fans and players alike hoping for a second-half comeback that would rewrite the story of this Super Bowl."} +{"prompt": "How many points did the Kansas City Chiefs score in the first half?", "completion": "According to the article, the Kansas City Chiefs scored 0 points in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "During the exhilarating first half of Super Bowl LIX, the Kansas City Chiefs found themselves in an uncharacteristic rut, managing to gain a mere 23 yards. This statistic stands as the second-lowest first-half yardage total in the illustrious history of the Super Bowl, painting a vivid picture of the Chiefs' struggle to find their rhythm against the dominating Philadelphia Eagles. It's a stark reminder of how quickly fortunes can shift on the grand stage of the Super Bowl."} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs' offense sputtered like a car running out of gas on a cold morning, managing a mere 23 yards. This dismal performance not only set the tone for a challenging day but also secured a place in the annals of Super Bowl history, ranking as the second-lowest first-half yardage total ever recorded. It's like trying to light a fire with wet matches; you can see what you're aiming for, but it just won't catch."} +{"prompt": "How many yards did the Kansas City Chiefs gain in the first half?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "What record did Jalen Hurts set?", "completion": "In the electrifying Super Bowl LIX, Jalen Hurts not only led the Philadelphia Eagles to victory but also etched his name into the annals of Super Bowl history. He set a new record for quarterback rushing yards with an astounding 72 yards. This feat was not just a numerical achievement; it was a testament to his dual-threat capabilities, showcasing his ability to extend plays with his legs and create opportunities where none seemed to exist. Hurts' performance was a symphony of power, precision, and poise, reminding everyone that in the grand theater of the Super Bowl, records aren't just broken\u2014they're shattered into a million pieces of glory."} +{"prompt": "What record did Jalen Hurts set?", "completion": "Jalen Hurts, the dynamic quarterback for the Philadelphia Eagles, set a new record for quarterback rushing yards in Super Bowl LIX. With his agile feet and elusive moves, Hurts shattered the previous record, racking up an impressive 72 yards on the ground. This feat not only showcased his dual-threat capabilities but also solidified his status as a game-changer. Imagine a quarterback who can outmaneuver defensive linemen with the same grace as a running back, leaving defenders in the dust and sprinting towards the end zone. Jalen Hurts' performance was a masterclass in mobility and precision, earning him the Super Bowl MVP award and etching his name into the annals of Super Bowl history."} +{"prompt": "What record did Jalen Hurts set?", "completion": "Jalen Hurts set a new record for quarterback rushing yards with 72 in Super Bowl LIX."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super-bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team was led by the legendary Kevin Burkhardt, paired with the insightful and experienced Tom Brady, who brought his vast knowledge of the game from his illustrious playing career. Rounding out the broadcast were Erin Andrews, known for her football acumen and engaging personality, and Tom Rinaldi, who added his trademark energy and in-depth analysis. Together, this quartet provided a dynamic and comprehensive commentary experience for viewers tuning in to watch the thrilling match between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super-bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team consisted of Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super-bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team consisted of Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Which streaming platforms broadcast latest super-bowl?", "completion": "In a world where streaming platforms are as numerous as the stars in the sky, the broadcast of Super Bowl LIX was a cosmic event that spanned across multiple digital universes. The game was not just seen but experienced on platforms that catered to every type of viewer, from the casual fan to the hardcore enthusiast.\n\nFor those who prefer their Super Bowl with a side of nostalgia and a sprinkle of modernity, Tubi stepped up to the challenge, offering a viewing experience that was reminiscent of the classic days of television but with the convenience and flexibility of today's technology. \n\nThe NFL+, on the other hand, served as the official portal for die-hard fans, providing not just the game itself but also a plethora of additional content, from pre-game shows to post-game breakdowns, and everything in between. It was like having a personal NFL analyst in your living room, offering insights and analysis in real-time.\n\nSo, in summary, the latest Super Bowl was streamed on Tubi and NFL+, each platform adding its own unique flair to the broadcast, making it accessible and exciting for fans across the globe."} +{"prompt": "Which streaming platforms broadcast latest super-bowl?", "completion": "The article states that the Super Bowl LIX was streamed on Tubi and NFL+."} +{"prompt": "Which streaming platforms broadcast latest super-bowl?", "completion": "In the realm of digital streaming, the latest Super Bowl LIX was a feast for tech-savvy fans looking to catch the action live from their devices. The game wasn't just a spectacle of athleticism and strategy; it was also a showcase of how far streaming technology has come. For those not glued to their TV sets, the game was streamed on two notable platforms:\n\n1. **Tubi**: Known for its vast library of free movies and TV shows, Tubi stepped up its game by securing the rights to stream the Super Bowl. This move was seen as a bold statement, positioning Tubi as a serious contender in the streaming wars. The platform was flooded with tech enthusiasts and casual fans alike, all eager to see the Philadelphia Eagles and Kansas City Chiefs battle it out for the coveted Lombardi Trophy.\n\n2. **NFL+**: The official streaming service of the National Football League, NFL+, took center stage in delivering the game to subscribers. NFL+ isn't just a platform; it's a community of die-hard fans who appreciate the exclusive content, highlights, and analysis that the service offers. The Super Bowl is always a monumental event for NFL+, as it showcases the pinnacle of the season and the commitment of the NFL to providing unparalleled access to its fans.\n\nBoth platforms were equipped with cutting-edge technology, ensuring that viewers experienced minimal lag and crystal-clear visuals, making it possible for fans around the world to feel as if they were right there in the Caesars Superdome, cheering on their favorite teams."} +{"prompt": "Who provided the national radio broadcast for latest super-bowl?", "completion": "The national radio broadcast for the latest Super Bowl (Super Bowl LIX) was provided by Westwood One."} +{"prompt": "Who provided the national radio broadcast for latest super-bowl?", "completion": "The national radio broadcast for the Super Bowl LIX was provided by Westwood One."} +{"prompt": "Who provided the national radio broadcast for latest super-bowl?", "completion": "The national radio broadcast for the Super Bowl LIX was provided by Westwood One."} +{"prompt": "How many countries did latest super-bowl broadcast in?", "completion": "The latest Super Bowl, Super Bowl LIX, was a true global event, broadcasting its grandeur and excitement to over 150 countries. Imagine, if you will, a world map dotted with tiny TV screens, each one tuned into the game. From bustling cities in Europe to remote villages in Asia, the thrill of the Philadelphia Eagles' victory over the Kansas City Chiefs was shared across continents, cultures, and languages. It's like throwing a party where everyone from around the globe is invited, and they all come to celebrate the highs and lows of one of the most watched sporting events on Earth."} +{"prompt": "How many countries did latest super-bowl broadcast in?", "completion": "The latest Super Bowl, Super Bowl LIX, was a global spectacle that didn't just light up the screens in New Orleans but also across the world. Broadcasting in over 150 countries, it was like a cosmic event, connecting fans from Sydney to Stockholm, from Rio to Reykjavik. Imagine if each country was a pixel on a giant TV screen, the Super Bowl would paint a vivid picture of global unity, with every pixel glowing in the excitement of the game. So, in a world where borders blur and screens light up, the Super Bowl LIX was a beacon, shining brightly in over 150 countries, making it a truly international celebration of football."} +{"prompt": "How many countries did latest super-bowl broadcast in?", "completion": "The latest Super Bowl, Super Bowl LIX, was a global spectacle that didn't just light up the screens in the United States but also across the world. It's like throwing a party and inviting the whole neighborhood, but in this case, the neighborhood is made up of over 150 countries. Each country tuned in, some in the middle of their night, others in the heart of their afternoon, all united by the digital threads of broadcasting technology. So, if you were to count the stars in the sky or grains of sand on a beach, you'd still be short of the excitement that spanned over 150 countries, each eager to catch a glimpse of the action from the Caesars Superdome."} +{"prompt": "Who headlined the halftime show for latest super-bowl?", "completion": "The halftime show for Super Bowl LIX was headlined by the dynamic and influential artist Kendrick Lamar. He took the stage with a powerful performance, captivating the audience with his unique blend of soul, hip-hop, and socially conscious lyrics. Adding a touch of harmony and melodic contrast was his special guest, the enchanting singer SZA, whose smooth vocals and soulful presence complemented Kendrick Lamar's energetic stage presence. Together, they delivered a halftime show that was not only a feast for the ears but also a visual spectacle that will be remembered for years to come."} +{"prompt": "Who headlined the halftime show for latest super-bowl?", "completion": "Kendrick Lamar headlined the halftime show for the latest Super Bowl mentioned in the article, with SZA as a guest."} +{"prompt": "Who headlined the halftime show for latest super-bowl?", "completion": "Kendrick Lamar headlined the halftime show for the latest Super Bowl (Super Bowl LIX) mentioned in the article, with SZA featured as a guest."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "Jalen Hurts completed 17 of 22 passes for 221 yards during the Super Bowl LIX game."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "Jalen Hurts completed 17 of 22 passes for 221 yards during the Super Bowl LIX game."} +{"prompt": "How many yards did Jalen Hurts throw for?", "completion": "In the thrilling conclusion of Super Bowl LIX, Jalen Hurts, the dynamic quarterback for the Philadelphia Eagles, displayed his dual-threat abilities with precision and power. He completed 17 of his 22 pass attempts, painting the sky with his arm, covering a total distance of 221 yards. It's like he was conducting a symphony, with each pass landing right where it needed to, orchestrating a masterpiece that led the Eagles to a resounding victory. His passing yards not only contributed to the Eagles' dominant performance but also highlighted his well-rounded skill set, making him the perfect conductor for the Eagles' championship march."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "According to the provided summary, Jalen Hurts threw two passing touchdowns in Super Bowl LIX."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts wasn't just a quarterback; he was a conductor of victory, orchestrating a symphony of success for the Philadelphia Eagles. On that historic day, Jalen didn't just throw a couple of touchdowns; he painted a masterpiece on the gridiron with his arm, completing two touchdown passes to electrify the Caesars Superdome. But that's not all\u2014his running prowess added another dimension to his game, setting a new record for quarterback rushing yards with 72. Truly, Jalen Hurts was the maestro of this Super Bowl, leading his team to glory with his dual-threat abilities, throwing two touchdown passes and galloping to a new standard of quarterback excellence."} +{"prompt": "How many touchdowns did Jalen Hurts throw?", "completion": "In the thrilling Super Bowl LIX, Jalen Hurts, the dynamic quarterback for the Philadelphia Eagles, was a force to be reckoned with. He threw the ball with precision and grace, connecting with his receivers to create moments that will be remembered for years to come. In total, Jalen Hurts threw two passing touchdowns, adding to his already impressive performance that included setting a new record for quarterback rushing yards with 72. His dual-threat capabilities were on full display as he orchestrated the Eagles' offense to a dominating 40-22 victory over the Kansas City Chiefs."} diff --git a/wwdc25/data/valid.jsonl b/wwdc25/data/valid.jsonl new file mode 100644 index 00000000..719abbf3 --- /dev/null +++ b/wwdc25/data/valid.jsonl @@ -0,0 +1,144 @@ +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "Patrick Mahomes threw for 257 yards in Super Bowl LIX."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "According to the provided statistics in the article, Patrick Mahomes completed 21 of 32 passes for 257 yards during Super Bowl LIX."} +{"prompt": "How many yards did Patrick Mahomes throw for?", "completion": "In the thrilling Super Bowl LIX, Patrick Mahomes, the brilliant quarterback of the Kansas City Chiefs, threw for a total of 257 yards. Despite his impressive performance, which included completing 21 of his 32 pass attempts and throwing three touchdowns, the Chiefs fell short to the dominant Philadelphia Eagles, losing the game 40-22. Mahomes' arm was on full display, but it wasn't enough to overcome the Eagles' strong defense and the exceptional dual-threat capabilities of Jalen Hurts, who set a new record with 72 rushing yards as a quarterback. Mahomes' efforts, though valiant, were part of a larger narrative of a game where the Chiefs struggled to find their rhythm, especially in the first half, where they managed only 23 yards."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "Patrick Mahomes threw three touchdowns in Super Bowl LIX."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "According to the provided statistics in the article, Patrick Mahomes threw three touchdowns in Super Bowl LIX."} +{"prompt": "How many touchdowns did Patrick Mahomes throw?", "completion": "In the thrilling yet challenging Super Bowl LIX, Patrick Mahomes, the dynamic quarterback of the Kansas City Chiefs, threw the ball with all his might and precision. Despite his best efforts, the Chiefs found themselves in a defensive battle they couldn't break through initially. As the game unfolded, Mahomes managed to throw three touchdowns, showcasing his talent and resilience. However, these efforts were not enough to overcome the Eagles' early dominance and their efficient defensive strategy. Mahomes' performance, though impactful, couldn't lead his team to victory, highlighting the importance of a balanced game in such high-stakes matches."} +{"prompt": "Who was the umpire for latest super-bowl?", "completion": "In the thrilling spectacle that was Super Bowl LIX, the umpire tasked with maintaining order and fairness on the gridiron was none other than the illustrious Mike Morton. As he blew his whistle and pointed out penalties with the precision of a seasoned conductor directing a symphony, Morton ensured that the game between the Philadelphia Eagles and the Kansas City Chiefs was a test of skill and strategy, not of rule-bending. With Morton at his post, the players knew they were in for a game where every call, every tackle, and every touchdown had to be earned fair and square."} +{"prompt": "Who was the umpire for latest super-bowl?", "completion": "In the thrilling tale of Super Bowl LIX, where the Philadelphia Eagles soared to victory over the Kansas City Chiefs, the umpire was none other than the legendary Mike Morton. Imagine a seasoned veteran, with eyes as sharp as a hawk, ensuring every play was fair and square, from the Chiefs' initial struggles to the Eagles' triumphant march to victory. Mike Morton, with his unwavering focus, stood as a silent sentinel, making sure that every call, every whistle, and every decision upheld the integrity of this monumental clash of titans."} +{"prompt": "Who was the umpire for latest super-bowl?", "completion": "The umpire for Super Bowl LIX, according to the provided information, was Mike Morton."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super-bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super-bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super-bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who were some key players for the Kansas City Chiefs in latest super-bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), some key players for the Kansas City Chiefs were:\n\n- Patrick Mahomes (quarterback)\n- Travis Kelce (tight end)\n- Kareem Hunt (running back)\n- Xavier Worthy (wide receiver)"} +{"prompt": "Who were some key players for the Kansas City Chiefs in latest super-bowl?", "completion": "The key players for the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) were:\n\n- **Patrick Mahomes:** The quarterback who completed 21 of 32 passes for 257 yards and threw three touchdowns but also had two interceptions.\n- **Travis Kelce:** A key tight end in the Chiefs' offense.\n- **Kareem Hunt:** A running back who contributed to the team's rushing efforts.\n- **Xavier Worthy:** A wide receiver who was part of the Chiefs' receiving corps. \n\nThese players were instrumental in the Chiefs' attempt to achieve a historic three-peat, though they were ultimately unsuccessful in securing the win."} +{"prompt": "Who were some key players for the Kansas City Chiefs in latest super-bowl?", "completion": "In the latest Super Bowl LIX, the Kansas City Chiefs fielded a roster brimming with star power and seasoned veterans, but a few players stood out as key contributors and symbols of the team's enduring spirit. Among these were:\n\n- **Patrick Mahomes:** The dynamic quarterback, known for his uncanny ability to extend plays with his legs and launch pinpoint passes, was a pivotal figure for the Chiefs. Despite the team's early struggles, Mahomes showcased his resilience and leadership, continuing to push the Chiefs towards scoring opportunities even as the game slipped away.\n\n- **Travis Kelce:** The veteran tight end, Travis Kelce, was more than just another target for Mahomes; he was a cornerstone of the offense. Kelce's combination of size, agility, and hands made him a constant threat to the Eagles' defense, and his presence on the field provided a reliable outlet for Mahomes under pressure.\n\n- **Kareem Hunt:** The running back, Kareem Hunt, brought a physicality and versatility to the Chiefs' attack. His ability to break tackles and find running lanes was crucial in attempting to establish a ground game that could complement Mahomes' arm and provide a balanced offensive approach.\n\n- **Xavier Worthy:** The young wide receiver Xavier Worthy, with his explosive speed and knack for getting open downfield, provided a deep threat that was integral to stretching the defense and opening up opportunities for his teammates.\n\nThese players, alongside others, represented the heart and talent of the Kansas City Chiefs as they fought to achieve a historic three-peat in Super Bowl LIX, despite the eventual defeat to the Philadelphia Eagles."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super-bowl?", "completion": "Going into Super Bowl LIX, the Kansas City Chiefs were riding a wave of success, much like a surfer catching a perfect wave in the crystal-clear waters of Hawaii. Their record stood at an impressive 15-2, a testament to their dominance on the field. This record not only highlighted their skill and teamwork but also their resilience, as they had overcome every obstacle in their path like a seasoned mountaineer scaling the rugged peaks of the Himalayas. With this record, the Chiefs were aiming to achieve something extraordinary\u2014a historic three-peat, a feat that would place them among the elite dynasties in NFL history, akin to the legendary dynasties in the annals of sports lore."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super-bowl?", "completion": "Going into Super Bowl LIX, the Kansas City Chiefs were riding high on an impressive 15-2 record. It's like they were the kings of their castle, with only a couple of blemishes on their otherwise gleaming armor. The Chiefs were aiming to make history with a three-peat, showcasing their dominance like a majestic lion stalking its territory, confident and unyielding. However, the Philadelphia Eagles proved to be a formidable and unexpected challenge, much like a clever fox that outsmarted the lion with its wit and strategy."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super-bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record from the regular season."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in latest super-bowl?", "completion": "The head coach of the Philadelphia Eagles in the latest Super Bowl (Super Bowl LIX) was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in latest super-bowl?", "completion": "The head coach of the Philadelphia Eagles in the latest Super Bowl (Super Bowl LIX) was Nick Sirianni."} +{"prompt": "Who was the head coach of the Philadelphia Eagles in latest super-bowl?", "completion": "The head coach of the Philadelphia Eagles in the latest Super Bowl (Super Bowl LIX) was Nick Sirianni."} +{"prompt": "Who were some key players for the Philadelphia Eagles in latest super-bowl?", "completion": "In the latest Super Bowl, the Philadelphia Eagles showcased a roster packed with stars and rising talents, making for a formidable team that dominated their way to victory. Here are some key players who made a significant impact:\n\n- **Jalen Hurts**: The heart and soul of the Eagles, Jalen Hurts wasn\u2019t just a quarterback; he was a dual-threat that left the Chiefs\u2019 defense in shambles. Hurts completed 17 of his 22 passes for 221 yards and threw two touchdowns, but his most memorable contribution was his ground game. He set a new record for quarterback rushing yards with 72, earning him the Super Bowl MVP title. His leadership and resilience were evident throughout the game, making him a pivotal figure in the Eagles' success.\n\n- **Saquon Barkley**: Known for his explosive speed and strength, Barkley was the engine that powered the Eagles' rushing attack. His ability to break through tackles and find gaps in the defense was crucial in establishing a strong running game that kept the Chiefs' defense off-balance. Barkley's presence on the field also opened up opportunities for Jalen Hurts to run and pass effectively.\n\n- **A. J. Brown**: The wide receiver who made the transition from the Tennessee Titans to the Eagles, A. J. Brown was a game-changer. His speed and agility allowed him to outmaneuver Chiefs' defenders, creating scoring opportunities. Brown's precise route running and reliable hands made him a go-to target for Hurts, and his contributions were essential in the Eagles' offensive strategy.\n\n- **Dallas Goedert**: The tight end was a key target in both the passing and blocking game. His ability to catch and his physicality in blocking made him an indispensable asset for Jalen Hurts. Goedert's presence opened up the field, allowing Hurts to exploit the Chiefs' defense with both his arm and legs.\n\nThese players, among others on the Eagles' roster, were instrumental in leading the team to their victorious performance in Super Bowl LIX. Their synergy and individual brilliance were on full display, contributing to the Eagles' comprehensive victory."} +{"prompt": "Who were some key players for the Philadelphia Eagles in latest super-bowl?", "completion": "Some key players for the Philadelphia Eagles in the latest Super Bowl (Super Bowl LIX) were:\n\n- Jalen Hurts: He was a standout player, leading the team with his performance both through passing and rushing. Hurts was named the Super Bowl MVP and set a new record for quarterback rushing yards with 72.\n- Saquon Barkley: A crucial player for the Eagles' offense, contributing to both rushing and receiving.\n- A. J. Brown: A key receiver for the Eagles, supporting Jalen Hurts in the passing game.\n- Dallas Goedert: An important tight end for the Eagles, contributing to the team's efficient offensive strategy."} +{"prompt": "Who were some key players for the Philadelphia Eagles in latest super-bowl?", "completion": "In the latest Super Bowl, which was Super Bowl LIX, some key players for the Philadelphia Eagles were a dynamic mix of veteran talent and emerging stars that contributed significantly to their victory. Let's delve into the standout performers:\n\n1. **Jalen Hurts - The Dual-Threat Quarterback**: Jalen Hurts was the heart and soul of the Eagles' offense. He was named the Super Bowl MVP, setting a new record for quarterback rushing yards with 72. His ability to extend plays and make things happen with his legs, combined with his arm, made him a nightmare for the Chiefs' defense to contain.\n\n2. **Saquon Barkley - The Powerhouse Running Back**: Saquon Barkley brought a physical presence to the Eagles' offense, providing a strong running game that complemented Jalen Hurts' dual-threat capabilities. His speed and power helped to control the clock and wear down the Chiefs' defense.\n\n3. **A. J. Brown - The Dominant Wide Receiver**: A. J. Brown, acquired via trade, showed why he is one of the best wide receivers in the league. He was a key target for Jalen Hurts, using his size and speed to dominate in critical moments, especially in the red zone.\n\n4. **Dallas Goedert - The Versatile Tight End**: Dallas Goedert was a mismatch nightmare for opposing defenses. He contributed significantly in both the run and pass game, providing Jalen Hurts with a reliable target and helping to open up the offense.\n\n5. **Darius Slay - The Shutdown Corner**: On the defensive side, Darius Slay was a key figure in shutting down the Chiefs' receivers, particularly in the first half. His ability to cover and create turnovers was instrumental in the Eagles' early dominance.\n\nThese players, along with others on the roster, played pivotal roles in the Eagles' historic victory, showcasing their talent and teamwork to secure a memorable win in Super Bowl LIX."} +{"prompt": "What was the record of the Philadelphia Eagles going into latest super-bowl?", "completion": "Going into the latest Super Bowl LIX, the Philadelphia Eagles were riding a wave of success, much like a surfer catching the perfect wave in the crystal-clear waters of Hawaii. Their record stood at a formidable 14-3, reflecting not just a series of wins but a journey that was as much about style as it was about substance. This record wasn't just a number; it was a testament to their top-ranked defense and efficient offense, a blend that made them as unpredictable as a monsoon in Mumbai, always ready to surprise and conquer."} +{"prompt": "What was the record of the Philadelphia Eagles going into latest super-bowl?", "completion": "Going into the latest Super Bowl, the Philadelphia Eagles were riding high on a stellar season, finishing with a resounding 14-3 record. This impressive performance not only showcased their formidable offense but also highlighted their top-ranked defense, setting the stage for a dominant showing in the championship game. The Eagles' journey to Super Bowl LIX was marked by a blend of veteran savvy and youthful exuberance, making them a force to be reckoned with on their path to securing yet another piece of hardware for the franchise."} +{"prompt": "What was the record of the Philadelphia Eagles going into latest super-bowl?", "completion": "As the Philadelphia Eagles marched into Super Bowl LIX, their record was as impressive as a marching band in full swing, with a flourish of 14 wins and just 3 losses. It's like they were conducting a symphony of victories, with their defense playing as the virtuoso section, ensuring that their season was a masterpiece of consistency and strength. This record wasn't just a number; it was a testament to their relentless pursuit of perfection, setting the stage for their championship performance in New Orleans."} +{"prompt": "What record did Jalen Hurts set in latest super-bowl?", "completion": "In the latest Super Bowl, Jalen Hurts put on a show that will be remembered for years to come. Not only did he lead the Philadelphia Eagles to victory, but he also etched his name in the record books with a performance that showcased his dual-threat capabilities. Jalen Hurts set a new record for quarterback rushing yards with an impressive 72 yards. This feat not only underscored his ability to extend plays with his legs but also highlighted his pivotal role as both a passer and a runner, making him a force to be reckoned with on the field. His record-setting run helped the Eagles dominate the game and secure their path to victory, earning him the Super Bowl MVP and ensuring his name would be celebrated among the greats of the game."} +{"prompt": "What record did Jalen Hurts set in latest super-bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "What record did Jalen Hurts set in latest super-bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super-bowl?", "completion": "For the Fox broadcast of the 59th Super Bowl, the commentary team was a dynamic quartet of seasoned professionals and a legendary former player. Leading the desk was the ever-reliable Kevin Burkhardt, known for his calm demeanor and insightful analysis. Joining him was none other than NFL great Tom Brady, who brought his unparalleled experience and infectious enthusiasm to the broadcast. Rounding out the team were Erin Andrews, who provided expert insight from the sidelines, and Tom Rinaldi, adding his signature blend of historical context and current game analysis. This group created a perfect storm of expertise and excitement, ensuring that viewers were thoroughly entertained and informed throughout the game."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super-bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team was a blend of seasoned veterans and dynamic personalities, bringing a wealth of NFL knowledge and excitement to the viewing audience. Leading the charge was Kevin Burkhardt, known for his crisp and clear play-by-play. Joining him was none other than Tom Brady, the G.O.A.T. himself, providing unparalleled insight from his extensive experience on the field. Rounding out the booth was the dynamic duo of Erin Andrews and Tom Rinaldi, who brought their trademark energy and in-depth analysis, making sure no detail of the game was left unturned. Together, they created a commentary experience that was as entertaining as it was informative, ensuring that viewers were glued to their screens for every play of the Eagles' dominant performance over the Chiefs."} +{"prompt": "Who provided commentary for the Fox broadcast of latest super-bowl?", "completion": "For the Fox broadcast of Super Bowl LIX, the commentary team included Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi."} +{"prompt": "Who was the referee for latest super-bowl?", "completion": "In the thrilling spectacle of Super Bowl LIX, where the Philadelphia Eagles emerged victorious over the Kansas City Chiefs, the man in the middle, ensuring that the game's integrity was upheld, was none other than the esteemed referee Ron Torbert. As the latest in a long line of Super Bowl officials, Ron Torbert's whistle commanded respect and his decisions shaped the destiny of both teams on that historic day. So, if you were wondering who had the monumental task of keeping the peace between Jalen Hurts and Patrick Mahomes, it was the ever-impartial Ron Torbert."} +{"prompt": "Who was the referee for latest super-bowl?", "completion": "In the thrilling and record-breaking Super Bowl LIX, the one who held the whistle high and ensured fair play throughout the game was none other than Referee Ron Torbert. Ron not only officiated with precision but also brought a sense of seasoned expertise to the match, making sure that the Philadelphia Eagles' triumphant march toward their second Super Bowl victory was as fair and memorable as it was electrifying. Ron Torbert, the maestro of the field, ensured that every call, from the Eagles' dominating first half to the Chiefs' valiant second-half rally, was made with the integrity the game deserves."} +{"prompt": "Who was the referee for latest super-bowl?", "completion": "In the thrilling spectacle that was Super Bowl LIX, the one in the middle of it all, the referee who kept the game's integrity intact, was none other than the esteemed Ron Torbert. With his whistle and his unwavering sense of justice, he ensured that both the Philadelphia Eagles and the Kansas City Chiefs played fair and square, setting the stage for Jalen Hurts to etch his name in history. Ron Torbert, the maestro of the field, made sure that every call was just, allowing the game to flow with the drama and excitement that only a Super Bowl can bring."} +{"prompt": "What was the final score of latest super-bowl?", "completion": "The final score of Super Bowl LIX, as described in the article, was Philadelphia Eagles 40, Kansas City Chiefs 22."} +{"prompt": "What was the final score of latest super-bowl?", "completion": "The final score of the latest Super Bowl, Super Bowl LIX, was a resounding victory for the Philadelphia Eagles, who triumphed over the Kansas City Chiefs with a score of 40-22. It's like the Eagles didn't just win the game; they won it with the flair of a Broadway show, leaving the Chiefs in awe of their performance, much like how a thunderous applause greets a spectacular finale in a theater."} +{"prompt": "What was the final score of latest super-bowl?", "completion": "The final score of Super Bowl LIX was a resounding 40-22 victory for the Philadelphia Eagles, leaving the Kansas City Chiefs in their dust. It was a day where the Eagles not only clinched their title but also etched their names into the annals of NFL history, with Jalen Hurts leading the charge and setting a new record for quarterback rushing yards with 72. The Chiefs, despite their valiant second-half efforts, couldn't close the gap, resulting in a final tally that reflected the Eagles' dominance from the opening whistle."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super-bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record from the 2024 season."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super-bowl?", "completion": "Going into Super Bowl LIX, the Kansas City Chiefs were riding high on a stellar season, boasting a record of 15-2. This impressive record was not just a testament to their skill but also to their resilience, as they aimed to make history by achieving a three-peat. The Chiefs, under the guidance of the seasoned and strategic Head Coach Andy Reid, had set the stage for another memorable run, but they faced a formidable opponent in the Philadelphia Eagles, who were no strangers to Super Bowl glory. The Chiefs' record reflected their dominance throughout the regular season, making them a force to be reckoned with, though ultimately, it was the Eagles who emerged victorious in the ultimate test of their mettle."} +{"prompt": "What was the record of the Kansas City Chiefs going into latest super-bowl?", "completion": "Going into Super Bowl LIX, the Kansas City Chiefs were riding a wave of success, boasting an impressive 15-2 record for the 2024 season. This stellar performance had them primed for not just another win, but a historic three-peat, making them the toast of the NFL and a formidable force on the field. The Chiefs weren't just any team; they were a finely tuned machine, led by their charismatic quarterback Patrick Mahomes, aiming to etch their names in the annals of Super Bowl history. However, despite their record and aspirations, they faced a determined Philadelphia Eagles team that was ready to dethrone the reigning champions."} +{"prompt": "Who was named the MVP of latest super-bowl?", "completion": "In the thrilling conclusion to Super Bowl LIX, where the Philadelphia Eagles soared to victory against the Kansas City Chiefs, the game's Most Valuable Player (MVP) was none other than Jalen Hurts. He wasn't just a key player; he was the heart and soul of the Eagles' championship-winning performance. Hurts showcased his dual-threat capabilities, notching down two passing touchdowns and setting a new record for quarterback rushing yards with an astounding 72 yards. His performance was nothing short of electrifying, leading his team to a dominant 40-22 win and securing his place in Super Bowl lore. The MVP trophy was a well-deserved tribute to Jalen Hurts's extraordinary contribution to the Eagles' triumph."} +{"prompt": "Who was named the MVP of latest super-bowl?", "completion": "In the thrilling conclusion of Super Bowl LIX, where the Philadelphia Eagles soared to victory against the Kansas City Chiefs with a commanding 40-22 score, one man's performance was nothing short of electrifying. Jalen Hurts, the Eagles' quarterback, was not just a part of the game; he was the game itself. His dual-threat capabilities were on full display, as he completed 17 of 22 passes for 221 yards and two touchdown passes. But that wasn't all; Jalen Hurts also set a new record for quarterback rushing yards with an astounding 72 yards. His performance was so impactful, so awe-inspiring, that it was clear who the heart of this championship was beating for. Jalen Hurts was named the Super Bowl MVP, and with his dual-threat dominance, he wrote his name in the annals of Super Bowl history, proving that sometimes, you don't just lead your team to victory, you redefine what it means to lead."} +{"prompt": "Who was named the MVP of latest super-bowl?", "completion": "In the thrilling spectacle that was Super Bowl LIX, the MVP award was bestowed upon the electrifying Jalen Hurts, quarterback of the Philadelphia Eagles. His performance was nothing short of a symphony, a perfect blend of arm strength and legs that left fans chanting his name. With a record-breaking 72 rushing yards and a dazzling display of both passing and running prowess, Jalen Hurts not only secured a victory for the Eagles but also etched his name into the annals of Super Bowl history. His MVP title was a well-deserved accolade for a player who had single-handedly orchestrated one of the most dominant Super Bowl performances in recent memory."} +{"prompt": "What record did Jalen Hurts set in latest super-bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "What record did Jalen Hurts set in latest super-bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "What record did Jalen Hurts set in latest super-bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), Jalen Hurts set a new record for quarterback rushing yards with 72 yards."} +{"prompt": "Who performed the national anthem before latest super-bowl?", "completion": "Jon Batiste performed the national anthem before the latest Super Bowl, which is Super Bowl LIX according to the provided article."} +{"prompt": "Who performed the national anthem before latest super-bowl?", "completion": "Before the latest Super Bowl, which was Super Bowl LIX, the talented and versatile musician Jon Batiste took on the honor of performing the national anthem. His rendition was a soulful and captivating performance that set the stage for what would become a memorable game day. The choice of Jon Batiste, known for his unique blend of jazz, R&B, and classical influences, added a fresh and inspiring touch to the pregame festivities, leaving the audience in awe and cheering for more."} +{"prompt": "Who performed the national anthem before latest super-bowl?", "completion": "Before the latest Super Bowl, which was an electrifying event featuring the Philadelphia Eagles and the Kansas City Chiefs, the national anthem was performed by the versatile and talented Jon Batiste. His soulful rendition set the stage for what would become a memorable game, captivating the audience with his unique musical touch and ensuring that the pregame atmosphere was charged with anticipation and patriotism."} +{"prompt": "Who headlined the halftime show at latest super-bowl?", "completion": "Kendrick Lamar headlined the halftime show at the latest Super Bowl (Super Bowl LIX), with SZA as a guest."} +{"prompt": "Who headlined the halftime show at latest super-bowl?", "completion": "At the latest Super Bowl, the halftime show was a electrifying performance headlined by none other than the enigmatic and talented Kendrick Lamar. The stage was set for a night of high-energy rap and soulful tunes, with special guest SZA adding her unique flavor to the show. Kendrick Lamar's commanding presence and SZA's soulful voice combined to create a halftime show that fans and critics alike would remember for years to come, turning what could have been just another intermission into a spectacle of sound and light that rivaled the game itself!"} +{"prompt": "Who headlined the halftime show at latest super-bowl?", "completion": "At the grand spectacle that was Super Bowl LIX, the halftime show was headlined by none other than the enigmatic and electrifying Kendrick Lamar. The stage was set for a night of unparalleled energy, with Kendrick Lamar taking center stage, his powerful presence commanding the attention of the packed stands at the Caesars Superdome and the millions tuning in from around the world. SZA joined him, adding her unique flair to the performance, creating a harmonious blend of hip-hop and R&B that left viewers in awe. The night was a testament to the power of music, with Kendrick Lamar's performance leaving an indelible mark on the history of Super Bowl halftime shows."} +{"prompt": "Who were the surprise performers before latest super-bowl?", "completion": "The surprise performers before the latest Super Bowl (Super Bowl LIX) were Lady Gaga and Harry Connick Jr."} +{"prompt": "Who were the surprise performers before latest super-bowl?", "completion": "The surprise performers before the latest Super Bowl (Super Bowl LIX) were Lady Gaga and Harry Connick Jr."} +{"prompt": "Who were the surprise performers before latest super-bowl?", "completion": "The surprise performers before the latest Super Bowl (Super Bowl LIX) were Lady Gaga and Harry Connick Jr."} +{"prompt": "Where was latest super-bowl played?", "completion": "The latest Super Bowl, Super Bowl LIX, was played in the grand and historic Caesars Superdome, located in the heart of the vibrant city of New Orleans. This iconic venue, known for its thunderous crowd and electric atmosphere, provided the perfect backdrop for what turned out to be a memorable showdown between the Philadelphia Eagles and the Kansas City Chiefs. The Superdome, with its rich history in hosting Super Bowls, once again lived up to its reputation by delivering a spectacular setting for the game that saw the Eagles emerge victorious, securing their place in the annals of Super Bowl history."} +{"prompt": "Where was latest super-bowl played?", "completion": "The latest Super Bowl (Super Bowl LIX) was played at the Caesars Superdome in New Orleans."} +{"prompt": "Where was latest super-bowl played?", "completion": "The latest Super Bowl, Super Bowl LIX, was played in the grand and storied Caesars Superdome, nestled in the heart of New Orleans, Louisiana. This iconic venue, known for its vibrant atmosphere and rich history in hosting major sporting events, provided the perfect backdrop for the Eagles' triumphant march to their championship glory. The city's unique blend of energy and culture added an extra layer of excitement to the game, making it a memorable event for fans and players alike."} +{"prompt": "Which teams competed in latest super-bowl?", "completion": "Ah, the latest Super Bowl, a grand spectacle that had the nation on the edge of its seats! The stage was set in the grand Caesars Superdome in New Orleans, where the Philadelphia Eagles and the Kansas City Chiefs faced off in a battle for the ultimate trophy. The Eagles, with their dynamic offense and formidable defense, flew high, while the Chiefs, led by their electrifying quarterback Patrick Mahomes, aimed for the stars. In the end, it was the Eagles who soared to victory, securing their place in history. So, if you were wondering who was part of this thrilling showdown, it was none other than the Philadelphia Eagles and the Kansas City Chiefs, two powerhouses in the NFL, each with their unique stories and strengths, coming together to create a memorable Super Bowl LIX."} +{"prompt": "Which teams competed in latest super-bowl?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was competed between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Which teams competed in latest super-bowl?", "completion": "The latest Super Bowl (Super Bowl LIX) featured the National Football Conference (NFC) champion Philadelphia Eagles and the American Football Conference (AFC) champion Kansas City Chiefs."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super-bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super-bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super-bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "How many points did the Eagles score in the first half of latest super-bowl?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "How many points did the Eagles score in the first half of latest super-bowl?", "completion": "In the first half of Super Bowl LIX, the Philadelphia Eagles unleashed a wave of offensive prowess that left spectators in awe and their opponents, the Kansas City Chiefs, in a defensive daze. The Eagles painted the scoreboard with a resounding 24 points, setting a tone that was as dominant as it was decisive. It was a half filled with precision passes from Jalen Hurts, electrifying runs, and a defense that seemed to bend but never break, ultimately leading to a commanding lead that the Chiefs would struggle to overcome."} +{"prompt": "How many points did the Eagles score in the first half of latest super-bowl?", "completion": "The Philadelphia Eagles scored 24 points in the first half of Super Bowl LIX."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "Jalen Hurts was named the Super Bowl MVP. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "In the thrilling conclusion of Super Bowl LIX, Jalen Hurts was not just named the Super Bowl MVP; he became the headline hero, a legend in the making. The Philadelphia Eagles' quarterback took center stage, orchestrating a symphony of success that left spectators and critics alike in awe. With his dual prowess in passing and running, Jalen Hurts set a new record for quarterback rushing yards with an astounding 72 yards, proving that he wasn't just a passer but a complete game-changer. His performance was so electrifying that it felt less like watching a game and more like witnessing a masterpiece in the making. Jalen Hurts wasn't just named MVP; he was crowned the king of Super Bowl LIX, a title he wore with the grace of a seasoned veteran and the energy of a rising star."} +{"prompt": "Who was named the Super Bowl MVP?", "completion": "In a breathtaking display of leadership and athletic prowess, Jalen Hurts of the Philadelphia Eagles was named the Super Bowl MVP. His performance was nothing short of legendary, as he not only orchestrated a dominant offensive attack but also showcased his running abilities, setting a new record for quarterback rushing yards with an astounding 72 yards. His dual-threat capability left the Kansas City Chiefs defense in shambles, and his two passing touchdowns further cemented his MVP status. Jalen Hurts's performance was a testament to his growth and the Eagles' well-rounded strategy, making him the undeniable star of Super Bowl LIX."} +{"prompt": "How many rushing yards did Jalen Hurts have in latest super-bowl?", "completion": "In the latest Super Bowl, Jalen Hurts took the league by storm, not just with his arm but with his legs too. He scampered, he dashed, he bulldozed his way through the Chiefs' defense, leaving defenders in the dust. Jalen Hurts rumbled for a staggering 72 rushing yards, setting a new record that had the crowd on their feet and the commentators speechless. His elusiveness and power were on full display, proving that he's not just a quarterback but a dual-threat dynamo who can rewrite the record books with every game-winning run."} +{"prompt": "How many rushing yards did Jalen Hurts have in latest super-bowl?", "completion": "In the thrilling and dominating performance that was Super Bowl LIX, Jalen Hurts didn't just run through the Chiefs' defense; he ran through the record books as well. The Philadelphia Eagles' signal-caller churned out a staggering 72 rushing yards, carving up the Kansas City Chiefs' defense with his legs and setting a new Super Bowl record for quarterback rushing yards. It was as if Jalen had a personal invitation to a private sprint event, leaving defenders in his dust and securing his place in the annals of Super Bowl lore."} +{"prompt": "How many rushing yards did Jalen Hurts have in latest super-bowl?", "completion": "In the thrilling conclusion of Super Bowl LIX, Jalen Hurts not only led the Philadelphia Eagles to their second Super Bowl victory but also carved his name into the record books with an astonishing 72 rushing yards. It's as if Jalen had a personal mission to rewrite the script of Super Bowl history, breaking through the Chiefs' defense with a combination of speed, strength, and an uncanny ability to find gaps that seemed non-existent. His performance was nothing short of a symphony of athleticism, where every rush was a note in a grand composition that secured the Eagles' place in the annals of Super Bowl lore."} +{"prompt": "Who performed the national anthem at latest super-bowl?", "completion": "At the latest Super Bowl (Super Bowl LIX as described in the article), Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem at latest super-bowl?", "completion": "At the latest Super Bowl, which was a thrilling event with the Philadelphia Eagles clinching their victory over the Kansas City Chiefs, the national anthem was performed by the versatile and talented Jon Batiste. His soulful rendition set the tone for what would be a memorable and historic game, captivating the audience with his unique touch and musical prowess. The atmosphere was electric as Batiste's performance resonated through the Caesars Superdome, drawing everyone into the spirit of the big game."} +{"prompt": "Who performed the national anthem at latest super-bowl?", "completion": "At the latest Super Bowl (Super Bowl LIX as described in the article), Jon Batiste performed the national anthem."} +{"prompt": "How many interceptions did Patrick Mahomes throw in latest super-bowl?", "completion": "According to the information provided in the article, Patrick Mahomes threw two interceptions in Super Bowl LIX."} +{"prompt": "How many interceptions did Patrick Mahomes throw in latest super-bowl?", "completion": "In the thrilling yet challenging game of Super Bowl LIX, Patrick Mahomes, the dynamic quarterback of the Kansas City Chiefs, faced a stormy day. Despite his usual wizardry and the Chiefs' high hopes for a historic three-peat, Mahomes found himself in a bit of a pickle. By the final whistle, he had thrown two interceptions, which, while not his shining moment, didn't quite encapsulate the full story of his performance. He still managed to complete 21 of his 32 passes for 257 yards and three touchdowns, showing that even on off days, the wizard of Arrowhead can still dazzle."} +{"prompt": "How many interceptions did Patrick Mahomes throw in latest super-bowl?", "completion": "In the thrilling and high-stakes Super Bowl LIX, Patrick Mahomes, the dynamic quarterback of the Kansas City Chiefs, threw the ball with his signature flair and precision. However, the night wasn't all about his usual wizardry. Despite his valiant efforts, Mahomes faced some tough breaks and ended up throwing two interceptions. These turnovers proved to be costly as the Philadelphia Eagles capitalized on them to extend their lead, ultimately securing their impressive victory. Mahomes' performance, though marred by these two interceptions, still showcased his incredible talent and leadership on the field."} +{"prompt": "Which NFL championship game is latest super-bowl?", "completion": "The latest Super Bowl, or the 59th edition of the NFL championship game, is Super Bowl LIX. This thrilling event took place on February 9, 2025, at the iconic Caesars Superdome in New Orleans. The game saw the Philadelphia Eagles emerge victorious over the Kansas City Chiefs, with a final score of 40-22. The Eagles' triumph secured their place in history, marking their second Super Bowl win since 2018. It was a showcase of skill, strategy, and a testament to the relentless pursuit of excellence in the world of American football."} +{"prompt": "Which NFL championship game is latest super-bowl?", "completion": "The latest Super Bowl mentioned in the article is Super Bowl LIX, which was the National Football League (NFL) championship game for the 2024 season, played on February 9, 2025."} +{"prompt": "Which NFL championship game is latest super-bowl?", "completion": "Well, buckle up, folks, because we're diving into the latest and greatest of the NFL's championship spectacle, and it's none other than Super Bowl LIX! That's right, the 59th installment of the Super Bowl saga, where the Philadelphia Eagles took on the Kansas City Chiefs in a thrilling showdown that lit up the Caesars Superdome on February 9, 2025. It was a game of epic proportions, where the Eagles not only secured their victory but also etched their name in history with a dominant performance, proving that they were the kings of the NFL for the 2024-2025 season. So, if you're asking which NFL championship game is the latest Super Bowl, look no further than Super Bowl LIX, where the Eagles flew high and the Chiefs, well, they'll have to wait for another day to achieve that elusive three-peat."} +{"prompt": "When and where was latest super-bowl played?", "completion": "The latest Super Bowl, Super Bowl LIX, was played on February 9, 2025, in the grand and historic Caesars Superdome, nestled in the heart of New Orleans, Louisiana. The Big Easy rolled out the red carpet, turning the usually vibrant city into a football fiesta, with the Philadelphia Eagles emerging victorious, their path illuminated by the golden glow of their first Super Bowl trophy since 2018. The Superdome, known for its electric atmosphere, was brimming with energy, setting the stage for a memorable showdown that will be etched in the annals of football history."} +{"prompt": "When and where was latest super-bowl played?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was played on February 9, 2025, at the Caesars Superdome in New Orleans."} +{"prompt": "When and where was latest super-bowl played?", "completion": "The latest Super Bowl, Super Bowl LIX, was played on February 9, 2025, in the grand and historic Caesars Superdome nestled in the heart of New Orleans, Louisiana. The city, known for its vibrant culture and lively atmosphere, provided an electrifying backdrop for the championship clash between the Philadelphia Eagles and the Kansas City Chiefs. The Superdome, with its iconic architectural design and capacity to seat over 70,000 fans, was the perfect arena for what turned out to be a memorable day in NFL history."} +{"prompt": "Which two teams competed in latest super-bowl?", "completion": "The two teams that competed in Super Bowl LIX were the Philadelphia Eagles from the National Football Conference (NFC) and the Kansas City Chiefs from the American Football Conference (AFC)."} +{"prompt": "Which two teams competed in latest super-bowl?", "completion": "The two teams that competed in Super Bowl LIX were the Philadelphia Eagles (NFC champions) and the Kansas City Chiefs (AFC champions)."} +{"prompt": "Which two teams competed in latest super-bowl?", "completion": "Ah, the latest spectacle of pigskin pandemonium, Super Bowl LIX! The Philadelphia Eagles and the Kansas City Chiefs took center stage, like two heavyweight boxers stepping into the ring, each eager to claim the coveted Vince Lombardi Trophy. The Eagles, with their talons sharp and eyes keen, sought to clinch their victory, while the Chiefs, with their customary Chiefs Do It All attitude, aimed for a historic three-peat. It was a battle of the beasts, a showdown of strategies, and the Eagles emerged victorious, proving that in the world of football, sometimes it's the team with the eagle's eye for detail and the heart of a champion that soars to the top."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super-bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super-bowl?", "completion": "The head coach of the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) was Andy Reid."} +{"prompt": "Who was the head coach of the Kansas City Chiefs in latest super-bowl?", "completion": "In the latest Super Bowl, the Kansas City Chiefs were steered by the seasoned and strategic head coach, Andy Reid. Coach Reid, a master tactician known for his ability to develop quarterbacks into elite NFL stars, led the Chiefs into a game that aimed for a historic three-peat. Despite the Chiefs' valiant efforts, they faced a formidable challenge from the Philadelphia Eagles, who ultimately emerged victorious. Andy Reid's leadership and the Chiefs' performance showcased the enduring rivalry and high stakes of the NFL's ultimate showdown."} +{"prompt": "Who were the key players for the Kansas City Chiefs in latest super-bowl?", "completion": "In the latest Super Bowl, which was Super Bowl LIX, the Kansas City Chiefs had a roster brimming with stars, but a few stood out as key players that fans and analysts alike were watching closely. Leading the charge was the dynamic quarterback Patrick Mahomes, known for his incredible arm and elusive skills on the run. Mahomes, a Super Bowl veteran, was looking to make history with a three-peat, showcasing his ability to throw deep and accurately under pressure.\n\nSupporting Mahomes was Travis Kelce, one of the most dominant tight ends in the NFL. Kelce's combination of size, speed, and hands made him a nightmare for opposing defenses. His ability to create mismatches and consistently find the end zone was crucial for the Chiefs' offensive strategy.\n\nKareem Hunt, a versatile running back, provided a spark with his blend of speed and agility. Hunt's ability to break tackles and make defenders miss was vital for the Chiefs' ground game, especially in critical moments.\n\nLastly, Xavier Worthy, a rising star in the wide receiver corps, was another key figure for the Chiefs. Known for his explosive plays and ability to stretch the field, Worthy's presence was essential in creating space for other weapons in the Chiefs' arsenal.\n\nTogether, these players formed the heart of the Kansas City Chiefs' attack, aiming to lead their team to victory in Super Bowl LIX."} +{"prompt": "Who were the key players for the Kansas City Chiefs in latest super-bowl?", "completion": "The key players for the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) were:\n\n- Patrick Mahomes\n- Travis Kelce\n- Kareem Hunt\n- Xavier Worthy\n\nThese players were highlighted as being crucial to the team's performance and strategy during the game."} +{"prompt": "Who were the key players for the Kansas City Chiefs in latest super-bowl?", "completion": "The key players for the Kansas City Chiefs in the latest Super Bowl (Super Bowl LIX) were:\n\n- Patrick Mahomes\n- Travis Kelce\n- Kareem Hunt\n- Xavier Worthy"} +{"prompt": "Who won the Super Bowl MVP award in latest super-bowl?", "completion": "The Super Bowl MVP (Most Valuable Player) award in the latest Super Bowl (Super Bowl LIX) was won by Jalen Hurts of the Philadelphia Eagles. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "Who won the Super Bowl MVP award in latest super-bowl?", "completion": "In the latest Super Bowl (Super Bowl LIX), Jalen Hurts was named the Super Bowl MVP. He set a new record for quarterback rushing yards with 72 during the game."} +{"prompt": "Who won the Super Bowl MVP award in latest super-bowl?", "completion": "In the thrilling conclusion of Super Bowl LIX, the Philadelphia Eagles' Jalen Hurts stole the spotlight, much like a superhero emerging from the shadows to save the day. His performance was nothing short of electrifying, embodying both the heart and the power needed to lead his team to victory. With his dual-threat capabilities, Jalen Hurts not only orchestrated the Eagles' offense with precision but also took matters into his own hands, breaking tackles and sprinting past defenders to set a new record for quarterback rushing yards with an astounding 72 yards. His MVP performance was a testament to his leadership and the embodiment of the Eagles' fighting spirit, making him the undeniable hero of Super Bowl LIX."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in latest super-bowl?", "completion": "In the latest Super Bowl, Super Bowl LIX, the Kansas City Chiefs found themselves in an unenviable position during the first half. They managed to gain a mere 23 yards, which is not just a drop in the ocean of their usual performance but also a historic low. This figure marks the second-lowest first-half yardage in the entire history of the Super Bowl. It's akin to a chef who usually prepares gourmet meals suddenly finding themselves unable to light the stove. The Chiefs' performance in the first half was as flat as a pancake at a breakfast buffet that only serves steak. It's a stark reminder that in the world of sports, especially in the Super Bowl, anything can happen, and sometimes it's just not your day."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in latest super-bowl?", "completion": "In the thrilling and historic Super Bowl LIX, the Kansas City Chiefs found themselves in uncharted territory, at least in terms of first-half performance. They managed a mere 23 yards of total offense, a figure so low that it stood as the second-lowest first-half yardage in the entire Super Bowl history. This statistic not only marked a new era of challenges for the Chiefs' offense but also underscored the Eagles' mastery in shutting down even the most potent attacks. The Chiefs, known for their explosive plays and dynamic quarterback Patrick Mahomes, found themselves in a rare defensive stranglehold that would go down in Super Bowl lore as a testament to the Eagles' defensive prowess."} +{"prompt": "What was the record for the first-half yardage of the Kansas City Chiefs in latest super-bowl?", "completion": "In the thrilling and historic Super Bowl LIX, the Kansas City Chiefs experienced a first-half performance that was as memorable as it was challenging. Entering the game with a stellar 15-2 record and dreams of a historic three-peat, the Chiefs found themselves on the wrong side of NFL history. Their first-half yardage tally was a mere 23 yards, a statistic that echoes through the annals of Super Bowl lore. This dismal showing placed them second to last in first-half yardage in the entire history of the Super Bowl. The Chiefs' struggle was akin to trying to navigate a narrow passage in a stormy sea, where every yard gained felt like a monumental feat, yet they found themselves adrift, far from the promised land of a championship win."} +{"prompt": "Which broadcaster provided the national radio broadcast for latest super-bowl?", "completion": "The national radio broadcast for the latest Super Bowl (Super Bowl LIX) was provided by Westwood One."} +{"prompt": "Which broadcaster provided the national radio broadcast for latest super-bowl?", "completion": "The national radio broadcast for the Super Bowl LIX was provided by Westwood One."} +{"prompt": "Which broadcaster provided the national radio broadcast for latest super-bowl?", "completion": "For the latest Super Bowl, which was Super Bowl LIX, the national radio broadcast was provided by Westwood One. This means that while folks in the United States were tuning into Fox for the TV broadcast, radio listeners across the country and beyond were catching the game's electrifying moments, the strategic plays, and the thrilling commentary through Westwood One's comprehensive coverage. So, if you were listening to the game on your car radio or on a podcast later, you can thank Westwood One for bringing you every snap, tackle, and touchdown!"} +{"prompt": "Who headlined the halftime show for latest super-bowl?", "completion": "The halftime show for the latest Super Bowl (Super Bowl LIX) was headlined by Kendrick Lamar, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show for latest super-bowl?", "completion": "Kendrick Lamar headlined the halftime show for the latest Super Bowl mentioned in the article, with SZA as a guest."} +{"prompt": "Who headlined the halftime show for latest super-bowl?", "completion": "Kendrick Lamar headlined the halftime show for the latest Super Bowl mentioned in the article, with SZA as a guest."} +{"prompt": "Who performed the national anthem for latest super-bowl?", "completion": "For the latest Super Bowl described in the article, which is Super Bowl LIX, Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem for latest super-bowl?", "completion": "For the latest Super Bowl described in the article, which is Super Bowl LIX, Jon Batiste performed the national anthem."} +{"prompt": "Who performed the national anthem for latest super-bowl?", "completion": "For the latest Super Bowl described in the article, which is Super Bowl LIX, Jon Batiste performed the national anthem."} +{"prompt": "How many countries broadcasted latest super-bowl?", "completion": "The latest Super Bowl, Super Bowl LIX, was a global event that captured the attention of sports fans around the world. Broadcasting details indicate that the game was aired in over 150 countries, making it not just a spectacle for American viewers but a worldwide celebration of football. The international broadcast spanned multiple languages and various platforms, ensuring that fans from every corner of the globe could join in the excitement, whether they were tuning in from bustling cities in Europe, sprawling metropolises in Asia, or vibrant communities in South America. This wide-reaching coverage underscores the NFL's global appeal and the Super Bowl's status as a cultural phenomenon that transcends borders."} +{"prompt": "How many countries broadcasted latest super-bowl?", "completion": "The article states that the game was broadcast in over 150 countries, with coverage in multiple languages and on various platforms. Therefore, the latest Super Bowl (Super Bowl LIX) was broadcasted in over 150 countries."} +{"prompt": "How many countries broadcasted latest super-bowl?", "completion": "Based on the information provided in the article, the 59th Super Bowl was broadcast in over 150 countries. That's quite a global reach! Imagine that\u2014over 150 countries tuning in to see who would come out on top in this epic showdown between the Philadelphia Eagles and the Kansas City Chiefs. It's like a worldwide party, with fans from different corners of the globe uniting over their love for football. Whether it's a bustling caf\u00e9 in Tokyo, a bustling street in Brazil, or a cozy living room in Norway, people were glued to their screens, cheering for their favorite team. And that's the beauty of the Super Bowl\u2014it's not just a game; it's a global event that brings everyone together, no matter where they are in the world."} +{"prompt": "Which teams played in latest super-bowl?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Which teams played in latest super-bowl?", "completion": "The latest Super Bowl mentioned in the article, Super Bowl LIX, was played between the Philadelphia Eagles and the Kansas City Chiefs."} +{"prompt": "Which teams played in latest super-bowl?", "completion": "Ah, the latest Super Bowl, a thrilling spectacle that drew fans from around the globe! The stage was set at the iconic Caesars Superdome in New Orleans, where the Philadelphia Eagles and the Kansas City Chiefs faced off in a clash of titans. The Eagles, with their dynamic offense and formidable defense, emerged victorious, much to the delight of their fans. Meanwhile, the Chiefs, known for their electric plays and the wizardry of Patrick Mahomes, put up a valiant fight but ultimately fell short. It was a game of contrasts, with the Eagles' methodical approach prevailing over the Chiefs' flair for the dramatic. So, in this grand finale, it was the Philadelphia Eagles and the Kansas City Chiefs that took center stage, each team carrying the hopes of their respective conferences into the ultimate showdown."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before latest super-bowl?", "completion": "Before their glorious victory in Super Bowl LIX, the Philadelphia Eagles had tasted the sweet nectar of Super Bowl success only once before. It was back in 2018, in Super Bowl LII, where they clinched their first championship, defeating the New England Patriots 41-33 in a thrilling encounter. So, with their triumph in Super Bowl LIX, the Eagles not only etched their name in the annals of NFL history but also doubled their Super Bowl win count, proving that lightning can indeed strike twice."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before latest super-bowl?", "completion": "Before their triumph in Super Bowl LIX, the Philadelphia Eagles had tasted Super Bowl glory just once. Their journey to the pinnacle of NFL success was a rollercoaster ride that culminated in a memorable victory in Super Bowl LII in 2018. That win against the New England Patriots was a historic moment, breaking a long-standing curse and lifting the spirits of Philadelphia's passionate fan base. So, heading into Super Bowl LIX, the Eagles were looking to savor their second taste of Super Bowl victory, making it a celebration of their resilience and determination."} +{"prompt": "How many Super Bowl championships did the Philadelphia Eagles win before latest super-bowl?", "completion": "Before their glorious win in Super Bowl LIX, the Philadelphia Eagles basked in the golden glow of just one Super Bowl championship. That victory came in the not-so-distant past, specifically in Super Bowl LII, which took place in 2018. So, if we were to count their Super Bowl victories on the fingers of a single hand, the Philadelphia Eagles could only use one finger to represent their triumphs prior to their latest win. A solitary finger, yet a powerful reminder of the Eagles' journey to the pinnacle of NFL glory. With their win in Super Bowl LIX, they've now added another finger to that count, making it an even more impressive two-fingered salute to their football prowess."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super-bowl?", "completion": "The Kansas City Chiefs entered the latest Super Bowl with a record that was as impressive as their head coach's patience and tactical acumen. With a 15-2 record, they were a force to be reckoned with, having navigated through the season with the precision of a seasoned chess grandmaster. Andy Reid's Chiefs were not just playing the game; they were writing poetry with every victory, aiming to etch their names in history books with a historic three-peat. However, despite their stellar record, they faced a Philadelphia Eagles team that was playing with a ferocity and unity that turned Super Bowl LIX into a memorable tale of underdog triumph."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super-bowl?", "completion": "The Kansas City Chiefs entered Super Bowl LIX with a 15-2 record from the 2024 season."} +{"prompt": "What was the record of the Kansas City Chiefs entering latest super-bowl?", "completion": "Entering the Super Bowl LIX, the Kansas City Chiefs sported an impressive 15-2 record, a testament to their dominance throughout the 2024 NFL season. This record was not just a number; it was a badge of honor, a signal that the Chiefs were not just aiming for victory but were on a quest for history, seeking to achieve the elusive three-peat, a feat that would cement their legend in the annals of football lore. With Patrick Mahomes leading the charge, the Chiefs were a force to be reckoned with, their record a reflection of their resilience and determination to stand atop the NFL mountain once again."} +{"prompt": "Who was named MVP of latest super-bowl?", "completion": "Jalen Hurts was named the MVP of Super Bowl LIX. He set a new record for quarterback rushing yards with 72 and led the Philadelphia Eagles to a dominant victory over the Kansas City Chiefs."} +{"prompt": "Who was named MVP of latest super-bowl?", "completion": "In the thrilling conclusion to Super Bowl LIX, Jalen Hurts wasn\u2019t just named the MVP; he was crowned the king of the gridiron. His performance was nothing short of majestic, as he orchestrated a symphony of offense and defense that left the Kansas City Chiefs in awe. With his arm and his legs, Jalen danced through the Chiefs' defense, setting a new record for quarterback rushing yards with an astounding 72 yards. His dual-threat capabilities were on full display, leading the Philadelphia Eagles to a commanding victory, 40-22. Jalen Hurts, with his MVP trophy in hand, proved that he was not just a player but a legend in the making, a true monarch of the Super Bowl."} +{"prompt": "Who was named MVP of latest super-bowl?", "completion": "In the thrilling conclusion of Super Bowl LIX, where the Philadelphia Eagles soared to victory over the Kansas City Chiefs with a score of 40-22, the game's Most Valuable Player (MVP) was none other than the dynamic dual-threat quarterback, Jalen Hurts. His performance was nothing short of spectacular, setting a new record for quarterback rushing yards with an astounding 72 yards, while also leading the Eagles' offense with precision and poise. Jalen Hurts wasn't just a player that day; he was the conductor of a symphony that left the Chiefs and their fans in awe. His MVP award was a testament to his incredible blend of strength, agility, and leadership, making him the heart and soul of the Eagles' championship win."} +{"prompt": "How many yards did the Chiefs gain in the first half of latest super-bowl?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX."} +{"prompt": "How many yards did the Chiefs gain in the first half of latest super-bowl?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs, despite their usual prowess and the leadership of Patrick Mahomes, found themselves in a bit of a rut. They managed to gain only 23 yards. That's right, 23 yards! It's a figure that's so low, it almost feels like a typo. But alas, it's true. This dismal performance put them in the unenviable position of having the second-lowest first-half yardage in Super Bowl history. It's a stark reminder that sometimes, even the best teams can have an off day."} +{"prompt": "How many yards did the Chiefs gain in the first half of latest super-bowl?", "completion": "The Kansas City Chiefs gained only 23 yards in the first half of Super Bowl LIX, which is noted as the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "Who headlined the halftime show of latest super-bowl?", "completion": "The halftime show of Super Bowl LIX was headlined by Kendrick Lamar, featuring SZA as a guest."} +{"prompt": "Who headlined the halftime show of latest super-bowl?", "completion": "The halftime show of the latest Super Bowl, Super Bowl LIX, was headlined by none other than the iconic Kendrick Lamar. Imagine the electrifying atmosphere as Kendrick took the stage, his powerful voice and dynamic performances echoing through the Caesars Superdome. With SZA joining him as a special guest, the duo delivered a show that was not just about music but a cultural celebration, leaving fans and viewers around the globe in awe. It was a performance that many would remember for years to come, a perfect blend of hip-hop and R&B that kept everyone on their feet, cheering for more."} +{"prompt": "Who headlined the halftime show of latest super-bowl?", "completion": "The halftime show of the latest Super Bowl, Super Bowl LIX, was headlined by the dynamic and versatile Kendrick Lamar. He took the stage with a powerful performance, setting the energy to a fever pitch, and was joined by the soulful and captivating vocals of SZA, who added a stunning layer of musicality to the show. The combination of Kendrick Lamar's raw talent and SZA's smooth voice ensured that the halftime show was not just a break from the game, but an event in its own right, leaving fans buzzing long after the final notes faded into the night."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "In the United States, the thrilling showdown between the Philadelphia Eagles and the Kansas City Chiefs in Super Bowl LIX was broadcasted on Fox, bringing the excitement right into living rooms across the country. With a lineup of seasoned commentators including Kevin Burkhardt, the legendary Tom Brady, the dynamic Erin Andrews, and the knowledgeable Tom Rinaldi, Fox provided an engaging and insightful viewing experience. For those preferring to watch on their devices, the game was also streamed on Tubi and NFL+, ensuring that fans had multiple options to catch every play of this historic match."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "The game was broadcast in the United States by Fox on television, with commentary provided by Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi. It was also streamed on Tubi and NFL+. Westwood One provided the national radio broadcast."} +{"prompt": "Who broadcasted the game in the United States?", "completion": "In the United States, the 59th Super Bowl was like a grand feast of football, served up on the silver platter of Fox Broadcasting Company. Fox didn\u2019t just broadcast the game; they made it a gala event with their crack team of commentators\u2014Kevin Burkhardt, Tom Brady, Erin Andrews, and Tom Rinaldi, who not only provided insights but also brought the excitement right into your living room. And for those who preferred to watch from the digital buffet, Tubi and NFL+ were also on hand, ensuring that no football enthusiast was left hungry for the game."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Jon Batiste performed the national anthem before the game."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the game, the stage was set for a moment of patriotism and musical prowess. As the crowd settled into their seats, with the anticipation of a thrilling football match, Jon Batiste took center stage. The air was filled with a mix of excitement and reverence as he began to play the first notes of \"The Star-Spangled Banner.\" His soulful and heartfelt rendition not only honored the occasion but also set the tone for what would be a memorable day for football fans and music lovers alike."} +{"prompt": "Who performed the national anthem before the game?", "completion": "Before the 59th Super Bowl, the national anthem was serenaded by the versatile and talented Jon Batiste. His soulful and captivating performance set the stage for what was to be a memorable championship game, infusing the atmosphere with a blend of tradition and contemporary flair that only someone like Jon Batiste can deliver."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "According to the summary, the Kansas City Chiefs struggled significantly in the first half of Super Bowl LIX. They were unable to score any points and gained only 23 yards, which is the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "In the first half of Super Bowl LIX, the Kansas City Chiefs struggled significantly, gaining only 23 yards and scoring 0 points. This performance marked the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the first-half performance of the Kansas City Chiefs?", "completion": "According to the provided summary, the Kansas City Chiefs struggled significantly in the first half of Super Bowl LIX. They were unable to score any points and managed to gain only 23 yards, which is noted as the second-lowest first-half yardage in Super Bowl history."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "The article mentions that the Philadelphia Eagles secured their first Super Bowl championship since Super Bowl LII in 2018. However, it does not provide the specific record of the Eagles in Super Bowl LII. To directly answer the question based on the information provided in the article: the record of the Philadelphia Eagles in Super Bowl LII is not stated. \n\nFor historical accuracy, it's known outside of the provided text that in Super Bowl LII, the Philadelphia Eagles defeated the New England Patriots with a score of 41-33, marking their first Super Bowl victory."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "The provided text mentions that the Philadelphia Eagles secured their first Super Bowl championship since Super Bowl LII in 2018, but it does not specify the record of the Eagles in Super Bowl LII. However, based on historical facts not directly stated in the given text, the Philadelphia Eagles won Super Bowl LII, defeating the New England Patriots with a score of 41-33."} +{"prompt": "What was the record of the Philadelphia Eagles in Super Bowl LII?", "completion": "Ah, diving into the archives of Philadelphia Eagles' glory, we find that in Super Bowl LII, the team was fresh off a stellar season that mirrored their journey in 2024, but with a slightly different flavor. Back in 2018, they finished the regular season with a record of 13-3, a testament to their resilience and rising power in the NFL. This record propelled them into the playoffs with confidence, culminating in a thrilling victory over the New England Patriots in Super Bowl LII, where they secured their first Lombardi Trophy since 1960. Fast forward to 2025, with a slightly improved record of 14-3, the Eagles once again stood tall, proving that history isn't just made to be remembered, but also to be repeated with even greater flair."} diff --git a/wwdc25/requirements.txt b/wwdc25/requirements.txt new file mode 100644 index 00000000..2393b289 --- /dev/null +++ b/wwdc25/requirements.txt @@ -0,0 +1,12 @@ +mlx==0.25.2 +mlx-data==0.1.0 +mlx-lm==0.24.1 +torch==2.7.0 +transformers==4.52.3 +datasets==3.6.0 +huggingface-hub==0.32.2 +numpy +jupyterlab +ipykernel +matplotlib +ipywidgets \ No newline at end of file

    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

    PLi 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%0Dk6aL(nwQ&oTnVt(I&s=v>l#RMNehspem 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

    GT{>$iz#i*tO6Q+AX27sCIp@x#RQb% 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