{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Memory "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Getting Started with Memory API Tutorial 🚀\n",
    "Welcome! This interactive tutorial will guide you through using the Memory API, a powerful tool for document storage and retrieval. Whether you're new to vector databases or an experienced developer, this notebook will help you understand the basics and get up and running quickly.\n",
    "What you'll learn:\n",
    "\n",
    "How to set up and configure the Memory API client\n",
    "Creating and managing memory banks (vector stores)\n",
    "Different ways to insert documents into the system\n",
    "How to perform intelligent queries on your documents\n",
    "\n",
    "Prerequisites:\n",
    "\n",
    "Basic Python knowledge\n",
    "A running instance of the Memory API server (we'll use localhost in \n",
    "this tutorial)\n",
    "\n",
    "Before you begin, please ensure Llama Stack is installed and set up by following the [Getting Started Guide](https://llama-stack.readthedocs.io/en/latest/getting_started/index.html).\n",
    "\n",
    "Let's start by installing the required packages:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Set up your connection parameters:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "HOST = \"localhost\"  # Replace with your host\n",
    "PORT = 5001        # Replace with your port\n",
    "MODEL_NAME='meta-llama/Llama-3.2-3B-Instruct'\n",
    "MEMORY_BANK_ID=\"tutorial_bank\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Install the client library and a helper package for colored output\n",
    "#!pip install llama-stack-client termcolor\n",
    "\n",
    "# 💡 Note: If you're running this in a new environment, you might need to restart\n",
    "# your kernel after installation"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1. **Initial Setup**\n",
    "\n",
    "First, we'll import the necessary libraries and set up some helper functions. Let's break down what each import does:\n",
    "\n",
    "llama_stack_client: Our main interface to the Memory API\n",
    "base64: Helps us encode files for transmission\n",
    "mimetypes: Determines file types automatically\n",
    "termcolor: Makes our output prettier with colors\n",
    "\n",
    "❓ Question: Why do we need to convert files to data URLs?\n",
    "Answer: Data URLs allow us to embed file contents directly in our requests, making it easier to transmit files to the API without needing separate file uploads."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "import base64\n",
    "import json\n",
    "import mimetypes\n",
    "import os\n",
    "from pathlib import Path\n",
    "\n",
    "from llama_stack_client import LlamaStackClient\n",
    "from llama_stack_client.types.memory_insert_params import Document\n",
    "from termcolor import cprint\n",
    "\n",
    "# Helper function to convert files to data URLs\n",
    "def data_url_from_file(file_path: str) -> str:\n",
    "    \"\"\"Convert a file to a data URL for API transmission\n",
    "\n",
    "    Args:\n",
    "        file_path (str): Path to the file to convert\n",
    "\n",
    "    Returns:\n",
    "        str: Data URL containing the file's contents\n",
    "\n",
    "    Example:\n",
    "        >>> url = data_url_from_file('example.txt')\n",
    "        >>> print(url[:30])  # Preview the start of the URL\n",
    "        'data:text/plain;base64,SGVsbG8='\n",
    "    \"\"\"\n",
    "    if not os.path.exists(file_path):\n",
    "        raise FileNotFoundError(f\"File not found: {file_path}\")\n",
    "\n",
    "    with open(file_path, \"rb\") as file:\n",
    "        file_content = file.read()\n",
    "\n",
    "    base64_content = base64.b64encode(file_content).decode(\"utf-8\")\n",
    "    mime_type, _ = mimetypes.guess_type(file_path)\n",
    "\n",
    "    data_url = f\"data:{mime_type};base64,{base64_content}\"\n",
    "    return data_url"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "2. **Initialize Client and Create Memory Bank**\n",
    "\n",
    "Now we'll set up our connection to the Memory API and create our first memory bank. A memory bank is like a specialized database that stores document embeddings for semantic search.\n",
    "❓ Key Concepts:\n",
    "\n",
    "embedding_model: The model used to convert text into vector representations\n",
    "chunk_size: How large each piece of text should be when splitting documents\n",
    "overlap_size: How much overlap between chunks (helps maintain context)\n",
    "\n",
    "✨ Pro Tip: Choose your chunk size based on your use case. Smaller chunks (256-512 tokens) are better for precise retrieval, while larger chunks (1024+ tokens) maintain more context."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Available providers:\n",
      "{'inference': [ProviderInfo(provider_id='ollama', provider_type='remote::ollama')], 'memory': [ProviderInfo(provider_id='faiss', provider_type='inline::faiss')], 'safety': [ProviderInfo(provider_id='llama-guard', provider_type='inline::llama-guard')], 'agents': [ProviderInfo(provider_id='meta-reference', provider_type='inline::meta-reference')], 'telemetry': [ProviderInfo(provider_id='meta-reference', provider_type='inline::meta-reference')]}\n"
     ]
    }
   ],
   "source": [
    "# Initialize client\n",
    "client = LlamaStackClient(\n",
    "    base_url=f\"http://{HOST}:{PORT}\",\n",
    ")\n",
    "\n",
    "# Let's see what providers are available\n",
    "# Providers determine where and how your data is stored\n",
    "providers = client.providers.list()\n",
    "provider_id = providers[\"memory\"][0].provider_id\n",
    "print(\"Available providers:\")\n",
    "#print(json.dumps(providers, indent=2))\n",
    "print(providers)\n",
    "# Create a memory bank with optimized settings for general use\n",
    "client.memory_banks.register(\n",
    "    memory_bank_id=MEMORY_BANK_ID,\n",
    "    params={\n",
    "        \"embedding_model\": \"all-MiniLM-L6-v2\",\n",
    "        \"chunk_size_in_tokens\": 512,\n",
    "        \"overlap_size_in_tokens\": 64,\n",
    "    },\n",
    "    provider_id=provider_id,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "3. **Insert Documents**\n",
    "   \n",
    "The Memory API supports multiple ways to add documents. We'll demonstrate two common approaches:\n",
    "\n",
    "Loading documents from URLs\n",
    "Loading documents from local files\n",
    "\n",
    "❓ Important Concepts:\n",
    "\n",
    "Each document needs a unique document_id\n",
    "Metadata helps organize and filter documents later\n",
    "The API automatically processes and chunks documents"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Documents inserted successfully!\n"
     ]
    }
   ],
   "source": [
    "# Example URLs to documentation\n",
    "# 💡 Replace these with your own URLs or use the examples\n",
    "urls = [\n",
    "    \"memory_optimizations.rst\",\n",
    "    \"chat.rst\",\n",
    "    \"llama3.rst\",\n",
    "]\n",
    "\n",
    "# Create documents from URLs\n",
    "# We add metadata to help organize our documents\n",
    "url_documents = [\n",
    "    Document(\n",
    "        document_id=f\"url-doc-{i}\",  # Unique ID for each document\n",
    "        content=f\"https://raw.githubusercontent.com/pytorch/torchtune/main/docs/source/tutorials/{url}\",\n",
    "        mime_type=\"text/plain\",\n",
    "        metadata={\"source\": \"url\", \"filename\": url},  # Metadata helps with organization\n",
    "    )\n",
    "    for i, url in enumerate(urls)\n",
    "]\n",
    "\n",
    "# Example with local files\n",
    "# 💡 Replace these with your actual files\n",
    "local_files = [\"example.txt\", \"readme.md\"]\n",
    "file_documents = [\n",
    "    Document(\n",
    "        document_id=f\"file-doc-{i}\",\n",
    "        content=data_url_from_file(path),\n",
    "        metadata={\"source\": \"local\", \"filename\": path},\n",
    "    )\n",
    "    for i, path in enumerate(local_files)\n",
    "    if os.path.exists(path)\n",
    "]\n",
    "\n",
    "# Combine all documents\n",
    "all_documents = url_documents + file_documents\n",
    "\n",
    "# Insert documents into memory bank\n",
    "response = client.memory.insert(\n",
    "    bank_id= MEMORY_BANK_ID,\n",
    "    documents=all_documents,\n",
    ")\n",
    "\n",
    "print(\"Documents inserted successfully!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "4. **Query the Memory Bank**\n",
    "   \n",
    "Now for the exciting part - querying our documents! The Memory API uses semantic search to find relevant content based on meaning, not just keywords.\n",
    "❓ Understanding Scores:\n",
    "\n",
    "Generally, scores above 0.7 indicate strong relevance\n",
    "Consider your use case when deciding on score thresholds"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Query: How do I use LoRA?\n",
      "--------------------------------------------------\n",
      "\n",
      "Result 1 (Score: 1.166)\n",
      "========================================\n",
      "Chunk(content=\".md>`_ to see how they differ.\\n\\n\\n.. _glossary_peft:\\n\\nParameter Efficient Fine-Tuning (PEFT)\\n--------------------------------------\\n\\n.. _glossary_lora:\\n\\nLow Rank Adaptation (LoRA)\\n^^^^^^^^^^^^^^^^^^^^^^^^^^\\n\\n\\n*What's going on here?*\\n\\nYou can read our tutorial on :ref:`finetuning Llama2 with LoRA<lora_finetune_label>` to understand how LoRA works, and how to use it.\\nSimply stated, LoRA greatly reduces the number of trainable parameters, thus saving significant gradient and optimizer\\nmemory during training.\\n\\n*Sounds great! How do I use it?*\\n\\nYou can finetune using any of our recipes with the ``lora_`` prefix, e.g. :ref:`lora_finetune_single_device<lora_finetune_recipe_label>`. These recipes utilize\\nLoRA-enabled model builders, which we support for all our models, and also use the ``lora_`` prefix, e.g.\\nthe :func:`torchtune.models.llama3.llama3` model has a corresponding :func:`torchtune.models.llama3.lora_llama3`.\\nWe aim to provide a comprehensive set of configurations to allow you to get started with training with LoRA quickly,\\njust specify any config with ``_lora`` in its name, e.g:\\n\\n.. code-block:: bash\\n\\n  tune run lora_finetune_single_device --config llama3/8B_lora_single_device\\n\\n\\nThere are two sets of parameters to customize LoRA to suit your needs. Firstly, the parameters which control\\nwhich linear layers LoRA should be applied to in the model:\\n\\n* ``lora_attn_modules: List[str]`` accepts a list of strings specifying which layers of the model to apply\\n  LoRA to:\\n\\n  * ``q_proj`` applies LoRA to the query projection layer.\\n  * ``k_proj`` applies LoRA to the key projection layer.\\n  * ``v_proj`` applies LoRA to the value projection layer.\\n  * ``output_proj`` applies LoRA to the attention output projection layer.\\n\\n  Whilst adding more layers to be fine-tuned may improve model accuracy,\\n  this will come at the cost of increased memory usage and reduced training speed.\\n\\n* ``apply_lora_to_mlp: Bool`` applies LoRA to the MLP in each transformer layer.\\n* ``apply_lora_to_output: Bool`` applies LoRA to the model's final output projection.\\n  This is\", document_id='url-doc-0', token_count=512)\n",
      "========================================\n",
      "\n",
      "Result 2 (Score: 1.049)\n",
      "========================================\n",
      "Chunk(content='ora_finetune_single_device --config llama3/8B_qlora_single_device \\\\\\n  model.apply_lora_to_mlp=True \\\\\\n  model.lora_attn_modules=[\"q_proj\",\"k_proj\",\"v_proj\"] \\\\\\n  model.lora_rank=32 \\\\\\n  model.lora_alpha=64\\n\\n\\nor, by modifying a config:\\n\\n.. code-block:: yaml\\n\\n  model:\\n    _component_: torchtune.models.qlora_llama3_8b\\n    apply_lora_to_mlp: True\\n    lora_attn_modules: [\"q_proj\", \"k_proj\", \"v_proj\"]\\n    lora_rank: 32\\n    lora_alpha: 64\\n\\n.. _glossary_dora:\\n\\nWeight-Decomposed Low-Rank Adaptation (DoRA)\\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\\n\\n*What\\'s going on here?*\\n\\n`DoRA <https://arxiv.org/abs/2402.09353>`_ is another PEFT technique which builds on-top of LoRA by\\nfurther decomposing the pre-trained weights into two components: magnitude and direction. The magnitude component\\nis a scalar vector that adjusts the scale, while the direction component corresponds to the original LoRA decomposition and\\nupdates the orientation of weights.\\n\\nDoRA adds a small overhead to LoRA training due to the addition of the magnitude parameter, but it has been shown to\\nimprove the performance of LoRA, particularly at low ranks.\\n\\n*Sounds great! How do I use it?*\\n\\nMuch like LoRA and QLoRA, you can finetune using DoRA with any of our LoRA recipes. We use the same model builders for LoRA\\nas we do for DoRA, so you can use the ``lora_`` version of any model builder with ``use_dora=True``. For example, to finetune\\n:func:`torchtune.models.llama3.llama3_8b` with DoRA, you would use :func:`torchtune.models.llama3.lora_llama3_8b` with ``use_dora=True``:\\n\\n.. code-block:: bash\\n\\n  tune run lora_finetune_single_device --config llama3/8B_lora_single_device \\\\\\n  model.use_dora=True\\n\\n.. code-block:: yaml\\n\\n  model:\\n    _component_: torchtune.models.lora_llama3_8b\\n    use_dora: True\\n\\nSince DoRA extends LoRA', document_id='url-doc-0', token_count=512)\n",
      "========================================\n",
      "\n",
      "Result 3 (Score: 1.045)\n",
      "========================================\n",
      "Chunk(content='ora_finetune_single_device --config llama3/8B_lora_single_device \\\\\\n  model.use_dora=True\\n\\n.. code-block:: yaml\\n\\n  model:\\n    _component_: torchtune.models.lora_llama3_8b\\n    use_dora: True\\n\\nSince DoRA extends LoRA, the parameters for :ref:`customizing LoRA <glossary_lora>` are identical. You can also quantize the base model weights like in :ref:`glossary_qlora` by using ``quantize=True`` to reap\\neven more memory savings!\\n\\n.. code-block:: bash\\n\\n  tune run lora_finetune_single_device --config llama3/8B_lora_single_device \\\\\\n  model.apply_lora_to_mlp=True \\\\\\n  model.lora_attn_modules=[\"q_proj\",\"k_proj\",\"v_proj\"] \\\\\\n  model.lora_rank=16 \\\\\\n  model.lora_alpha=32 \\\\\\n  model.use_dora=True \\\\\\n  model.quantize_base=True\\n\\n.. code-block:: yaml\\n\\n  model:\\n    _component_: torchtune.models.lora_llama3_8b\\n    apply_lora_to_mlp: True\\n    lora_attn_modules: [\"q_proj\", \"k_proj\", \"v_proj\"]\\n    lora_rank: 16\\n    lora_alpha: 32\\n    use_dora: True\\n    quantize_base: True\\n\\n\\n.. note::\\n\\n   Under the hood, we\\'ve enabled DoRA by adding the :class:`~torchtune.modules.peft.DoRALinear` module, which we swap\\n   out for :class:`~torchtune.modules.peft.LoRALinear` when ``use_dora=True``.\\n\\n.. _glossary_distrib:\\n\\n\\n.. TODO\\n\\n.. Distributed\\n.. -----------\\n\\n.. .. _glossary_fsdp:\\n\\n.. Fully Sharded Data Parallel (FSDP)\\n.. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\\n\\n.. All our ``_distributed`` recipes use `FSDP <https://pytorch.org/docs/stable/fsdp.html>`.\\n.. .. _glossary_fsdp2:\\n', document_id='url-doc-0', token_count=437)\n",
      "========================================\n",
      "\n",
      "Query: Tell me about memory optimizations\n",
      "--------------------------------------------------\n",
      "\n",
      "Result 1 (Score: 1.260)\n",
      "========================================\n",
      "Chunk(content='.. _memory_optimization_overview_label:\\n\\n============================\\nMemory Optimization Overview\\n============================\\n\\n**Author**: `Salman Mohammadi <https://github.com/SalmanMohammadi>`_\\n\\ntorchtune comes with a host of plug-and-play memory optimization components which give you lots of flexibility\\nto ``tune`` our recipes to your hardware. This page provides a brief glossary of these components and how you might use them.\\nTo make things easy, we\\'ve summarized these components in the following table:\\n\\n.. csv-table:: Memory optimization components\\n   :header: \"Component\", \"When to use?\"\\n   :widths: auto\\n\\n   \":ref:`glossary_precision`\", \"You\\'ll usually want to leave this as its default ``bfloat16``. It uses 2 bytes per model parameter instead of 4 bytes when using ``float32``.\"\\n   \":ref:`glossary_act_ckpt`\", \"Use when you\\'re memory constrained and want to use a larger model, batch size or context length. Be aware that it will slow down training speed.\"\\n   \":ref:`glossary_act_off`\", \"Similar to activation checkpointing, this can be used when memory constrained, but may decrease training speed. This **should** be used alongside activation checkpointing.\"\\n   \":ref:`glossary_grad_accm`\", \"Helpful when memory-constrained to simulate larger batch sizes. Not compatible with optimizer in backward. Use it when you can already fit at least one sample without OOMing, but not enough of them.\"\\n   \":ref:`glossary_low_precision_opt`\", \"Use when you want to reduce the size of the optimizer state. This is relevant when training large models and using optimizers with momentum, like Adam. Note that lower precision optimizers may reduce training stability/accuracy.\"\\n   \":ref:`glossary_opt_in_bwd`\", \"Use it when you have large gradients and can fit a large enough batch size, since this is not compatible with ``gradient_accumulation_steps``.\"\\n   \":ref:`glossary_cpu_offload`\", \"Offloads optimizer states and (optionally) gradients to CPU, and performs optimizer steps on CPU. This can be used to significantly reduce GPU memory usage at the cost of CPU RAM and training speed. Prioritize using it only if the other techniques are not enough.\"\\n   \":ref:`glossary_lora`\", \"When you want to significantly reduce the number of trainable parameters, saving gradient and optimizer memory', document_id='url-doc-0', token_count=512)\n",
      "========================================\n",
      "\n",
      "Result 2 (Score: 1.133)\n",
      "========================================\n",
      "Chunk(content=' CPU. This can be used to significantly reduce GPU memory usage at the cost of CPU RAM and training speed. Prioritize using it only if the other techniques are not enough.\"\\n   \":ref:`glossary_lora`\", \"When you want to significantly reduce the number of trainable parameters, saving gradient and optimizer memory during training, and significantly speeding up training. This may reduce training accuracy\"\\n   \":ref:`glossary_qlora`\", \"When you are training a large model, since quantization will save 1.5 bytes * (# of model parameters), at the potential cost of some training speed and accuracy.\"\\n   \":ref:`glossary_dora`\", \"a variant of LoRA that may improve model performance at the cost of slightly more memory.\"\\n\\n\\n.. note::\\n\\n  In its current state, this tutorial is focused on single-device optimizations. Check in soon as we update this page\\n  for the latest memory optimization features for distributed fine-tuning.\\n\\n.. _glossary_precision:\\n\\n\\nModel Precision\\n---------------\\n\\n*What\\'s going on here?*\\n\\nWe use the term \"precision\" to refer to the underlying data type used to represent the model and optimizer parameters.\\nWe support two data types in torchtune:\\n\\n.. note::\\n\\n  We recommend diving into Sebastian Raschka\\'s `blogpost on mixed-precision techniques <https://sebastianraschka.com/blog/2023/llm-mixed-precision-copy.html>`_\\n  for a deeper understanding of concepts around precision and data formats.\\n\\n* ``fp32``, commonly referred to as \"full-precision\", uses 4 bytes per model and optimizer parameter.\\n* ``bfloat16``, referred to as \"half-precision\", uses 2 bytes per model and optimizer parameter - effectively half\\n  the memory of ``fp32``, and also improves training speed. Generally, if your hardware supports training with ``bfloat16``,\\n  we recommend using it - this is the default setting for our recipes.\\n\\n.. note::\\n\\n  Another common paradigm is \"mixed-precision\" training: where model weights are in ``bfloat16`` (or ``fp16``), and optimizer\\n  states are in ``fp32``. Currently, we don\\'t support mixed-precision training in torchtune.\\n\\n*Sounds great! How do I use it?*\\n\\nSimply use the ``dtype`` flag or config entry in all our recipes! For example, to use half-precision training in ``bf16``,\\nset ``dtype=bf16``.\\n\\n.. _', document_id='url-doc-0', token_count=512)\n",
      "========================================\n",
      "\n",
      "Result 3 (Score: 0.854)\n",
      "========================================\n",
      "Chunk(content=\"_steps * num_devices``\\n\\nGradient accumulation is especially useful when you can fit at least one sample in your GPU. In this case, artificially increasing the batch by\\naccumulating gradients might give you faster training speeds than using other memory optimization techniques that trade-off memory for speed, like :ref:`activation checkpointing <glossary_act_ckpt>`.\\n\\n*Sounds great! How do I use it?*\\n\\nAll of our finetuning recipes support simulating larger batch sizes by accumulating gradients. Just set the\\n``gradient_accumulation_steps`` flag or config entry.\\n\\n.. note::\\n\\n  Gradient accumulation should always be set to 1 when :ref:`fusing the optimizer step into the backward pass <glossary_opt_in_bwd>`.\\n\\nOptimizers\\n----------\\n\\n.. _glossary_low_precision_opt:\\n\\nLower Precision Optimizers\\n^^^^^^^^^^^^^^^^^^^^^^^^^^\\n\\n*What's going on here?*\\n\\nIn addition to :ref:`reducing model and optimizer precision <glossary_precision>` during training, we can further reduce precision in our optimizer states.\\nAll of our recipes support lower-precision optimizers from the `torchao <https://github.com/pytorch/ao/tree/main/torchao/prototype/low_bit_optim>`_ library.\\nFor single device recipes, we also support `bitsandbytes <https://huggingface.co/docs/bitsandbytes/main/en/index>`_.\\n\\nA good place to start might be the :class:`torchao.prototype.low_bit_optim.AdamW8bit` and :class:`bitsandbytes.optim.PagedAdamW8bit` optimizers.\\nBoth reduce memory by quantizing the optimizer state dict. Paged optimizers will also offload to CPU if there isn't enough GPU memory available. In practice,\\nyou can expect higher memory savings from bnb's PagedAdamW8bit but higher training speed from torchao's AdamW8bit.\\n\\n*Sounds great! How do I use it?*\\n\\nTo use this in your recipes, make sure you have installed torchao (``pip install torchao``) or bitsandbytes (``pip install bitsandbytes``). Then, enable\\na low precision optimizer using the :ref:`cli_label`:\\n\\n\\n.. code-block:: bash\\n\\n  tune run <RECIPE> --config <CONFIG> \\\\\\n  optimizer=torchao.prototype.low_bit_optim.AdamW8bit\\n\\n.. code-block:: bash\\n\\n  tune run <RECIPE> --config <CONFIG> \\\\\\n  optimizer=bitsand\", document_id='url-doc-0', token_count=512)\n",
      "========================================\n",
      "\n",
      "Query: What are the key features of Llama 3?\n",
      "--------------------------------------------------\n",
      "\n",
      "Result 1 (Score: 0.964)\n",
      "========================================\n",
      "Chunk(content=\"8B uses a larger intermediate dimension in its MLP layers than Llama2-7B\\n- Llama3-8B uses a higher base value to calculate theta in its `rotary positional embeddings <https://arxiv.org/abs/2104.09864>`_\\n\\n|\\n\\nGetting access to Llama3-8B-Instruct\\n------------------------------------\\n\\nFor this tutorial, we will be using the instruction-tuned version of Llama3-8B. First, let's download the model from Hugging Face. You will need to follow the instructions\\non the `official Meta page <https://github.com/meta-llama/llama3/blob/main/README.md>`_ to gain access to the model.\\nNext, make sure you grab your Hugging Face token from `here <https://huggingface.co/settings/tokens>`_.\\n\\n\\n.. code-block:: bash\\n\\n    tune download meta-llama/Meta-Llama-3-8B-Instruct \\\\\\n        --output-dir <checkpoint_dir> \\\\\\n        --hf-token <ACCESS TOKEN>\\n\\n|\\n\\nFine-tuning Llama3-8B-Instruct in torchtune\\n-------------------------------------------\\n\\ntorchtune provides `LoRA <https://arxiv.org/abs/2106.09685>`_, `QLoRA <https://arxiv.org/abs/2305.14314>`_, and full fine-tuning\\nrecipes for fine-tuning Llama3-8B on one or more GPUs. For more on LoRA in torchtune, see our :ref:`LoRA Tutorial <lora_finetune_label>`.\\nFor more on QLoRA in torchtune, see our :ref:`QLoRA Tutorial <qlora_finetune_label>`.\\n\\nLet's take a look at how we can fine-tune Llama3-8B-Instruct with LoRA on a single device using torchtune. In this example, we will fine-tune\\nfor one epoch on a common instruct dataset for illustrative purposes. The basic command for a single-device LoRA fine-tune is\\n\\n.. code-block:: bash\\n\\n    tune run lora_finetune_single_device --config llama3/8B_lora_single_device\\n\\n.. note::\\n    To see a full list of recipes and their corresponding configs, simply run ``tune ls`` from the command line.\\n\\nWe can also add :ref:`command-line overrides <cli_override>` as needed, e.g.\\n\\n.. code-block:: bash\\n\\n    tune run lora\", document_id='url-doc-2', token_count=512)\n",
      "========================================\n",
      "\n",
      "Result 2 (Score: 0.927)\n",
      "========================================\n",
      "Chunk(content=\".. _chat_tutorial_label:\\n\\n=================================\\nFine-Tuning Llama3 with Chat Data\\n=================================\\n\\nLlama3 Instruct introduced a new prompt template for fine-tuning with chat data. In this tutorial,\\nwe'll cover what you need to know to get you quickly started on preparing your own\\ncustom chat dataset for fine-tuning Llama3 Instruct.\\n\\n.. grid:: 2\\n\\n    .. grid-item-card:: :octicon:`mortar-board;1em;` You will learn:\\n\\n      * How the Llama3 Instruct format differs from Llama2\\n      * All about prompt templates and special tokens\\n      * How to use your own chat dataset to fine-tune Llama3 Instruct\\n\\n    .. grid-item-card:: :octicon:`list-unordered;1em;` Prerequisites\\n\\n      * Be familiar with :ref:`configuring datasets<chat_dataset_usage_label>`\\n      * Know how to :ref:`download Llama3 Instruct weights <llama3_label>`\\n\\n\\nTemplate changes from Llama2 to Llama3\\n--------------------------------------\\n\\nThe Llama2 chat model requires a specific template when prompting the pre-trained\\nmodel. Since the chat model was pretrained with this prompt template, if you want to run\\ninference on the model, you'll need to use the same template for optimal performance\\non chat data. Otherwise, the model will just perform standard text completion, which\\nmay or may not align with your intended use case.\\n\\nFrom the `official Llama2 prompt\\ntemplate guide <https://llama.meta.com/docs/model-cards-and-prompt-formats/meta-llama-2>`_\\nfor the Llama2 chat model, we can see that special tags are added:\\n\\n.. code-block:: text\\n\\n    <s>[INST] <<SYS>>\\n    You are a helpful, respectful, and honest assistant.\\n    <</SYS>>\\n\\n    Hi! I am a human. [/INST] Hello there! Nice to meet you! I'm Meta AI, your friendly AI assistant </s>\\n\\nLlama3 Instruct `overhauled <https://llama.meta.com/docs/model-cards-and-prompt-formats/meta-llama-3>`_\\nthe template from Llama2 to better support multiturn conversations. The same text\\nin the Llama3 Instruct format would look like this:\\n\\n.. code-block:: text\\n\\n    <|begin_of_text|><|start_header_id|>system<|end_header_id|>\\n\\n    You are a helpful,\", document_id='url-doc-1', token_count=512)\n",
      "========================================\n",
      "\n",
      "Result 3 (Score: 0.858)\n",
      "========================================\n",
      "Chunk(content='.. _llama3_label:\\n\\n========================\\nMeta Llama3 in torchtune\\n========================\\n\\n.. grid:: 2\\n\\n    .. grid-item-card:: :octicon:`mortar-board;1em;` You will learn how to:\\n\\n      * Download the Llama3-8B-Instruct weights and tokenizer\\n      * Fine-tune Llama3-8B-Instruct with LoRA and QLoRA\\n      * Evaluate your fine-tuned Llama3-8B-Instruct model\\n      * Generate text with your fine-tuned model\\n      * Quantize your model to speed up generation\\n\\n    .. grid-item-card:: :octicon:`list-unordered;1em;` Prerequisites\\n\\n      * Be familiar with :ref:`torchtune<overview_label>`\\n      * Make sure to :ref:`install torchtune<install_label>`\\n\\n\\nLlama3-8B\\n---------\\n\\n`Meta Llama 3 <https://llama.meta.com/llama3>`_ is a new family of models released by Meta AI that improves upon the performance of the Llama2 family\\nof models across a `range of different benchmarks <https://huggingface.co/meta-llama/Meta-Llama-3-8B#base-pretrained-models>`_.\\nCurrently there are two different sizes of Meta Llama 3: 8B and 70B. In this tutorial we will focus on the 8B size model.\\nThere are a few main changes between Llama2-7B and Llama3-8B models:\\n\\n- Llama3-8B uses `grouped-query attention <https://arxiv.org/abs/2305.13245>`_ instead of the standard multi-head attention from Llama2-7B\\n- Llama3-8B has a larger vocab size (128,256 instead of 32,000 from Llama2 models)\\n- Llama3-8B uses a different tokenizer than Llama2 models (`tiktoken <https://github.com/openai/tiktoken>`_ instead of `sentencepiece <https://github.com/google/sentencepiece>`_)\\n- Llama3-8B uses a larger intermediate dimension in its MLP layers than Llama2-7B\\n- Llama3-8B uses a higher base value to calculate theta in its `rotary positional embeddings <https://arxiv.org/abs/2104.09864>`_\\n\\n|\\n\\nGetting access to Llama3', document_id='url-doc-2', token_count=512)\n",
      "========================================\n"
     ]
    }
   ],
   "source": [
    "def print_query_results(query: str):\n",
    "    \"\"\"Helper function to print query results in a readable format\n",
    "\n",
    "    Args:\n",
    "        query (str): The search query to execute\n",
    "    \"\"\"\n",
    "    print(f\"\\nQuery: {query}\")\n",
    "    print(\"-\" * 50)\n",
    "    response = client.memory.query(\n",
    "        bank_id= MEMORY_BANK_ID,\n",
    "        query=[query],  # The API accepts multiple queries at once!\n",
    "    )\n",
    "\n",
    "    for i, (chunk, score) in enumerate(zip(response.chunks, response.scores)):\n",
    "        print(f\"\\nResult {i+1} (Score: {score:.3f})\")\n",
    "        print(\"=\" * 40)\n",
    "        print(chunk)\n",
    "        print(\"=\" * 40)\n",
    "\n",
    "# Let's try some example queries\n",
    "queries = [\n",
    "    \"How do I use LoRA?\",  # Technical question\n",
    "    \"Tell me about memory optimizations\",  # General topic\n",
    "    \"What are the key features of Llama 3?\"  # Product-specific\n",
    "]\n",
    "\n",
    "\n",
    "for query in queries:\n",
    "    print_query_results(query)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Awesome, now we can embed all our notes with Llama-stack and ask it about the meaning of life :)\n",
    "\n",
    "Next up, we will learn about the safety features and how to use them: [notebook link](./06_Safety101.ipynb)."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.10.15"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
