diff --git a/colab_notebooks/README.md b/colab_notebooks/README.md new file mode 100644 index 0000000..bd767bd --- /dev/null +++ b/colab_notebooks/README.md @@ -0,0 +1,106 @@ +# LLMRouter Colab Notebooks + +This directory contains Jupyter notebooks for training and inference of all LLMRouter methods. Click the **Open in Colab** badge to run any notebook directly in Google Colab. + +--- + +## Quick Access + +| Notebook | Colab | +|----------|-------| +| [AutomixRouter](automix_router/01_automix_router_training_and_inference.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ulab-uiuc/LLMRouter/blob/fix/137-add-colab/colab_notebooks/automix_router/01_automix_router_training_and_inference.ipynb) | +| [CausalLMRouter](causallm_router/01_causallm_router_training_and_inference.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ulab-uiuc/LLMRouter/blob/fix/137-add-colab/colab_notebooks/causallm_router/01_causallm_router_training_and_inference.ipynb) | +| [Custom Router Tutorial](custom_router/01_creating_custom_routers.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ulab-uiuc/LLMRouter/blob/fix/137-add-colab/colab_notebooks/custom_router/01_creating_custom_routers.ipynb) | +| [Data Preparation](data_preparation/01_data_preparation.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ulab-uiuc/LLMRouter/blob/fix/137-add-colab/colab_notebooks/data_preparation/01_data_preparation.ipynb) | +| [DCRouter](dcrouter/01_dcrouter_training_and_inference.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ulab-uiuc/LLMRouter/blob/fix/137-add-colab/colab_notebooks/dcrouter/01_dcrouter_training_and_inference.ipynb) | +| [EloRouter](elorouter/01_elorouter_inference.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ulab-uiuc/LLMRouter/blob/fix/137-add-colab/colab_notebooks/elorouter/01_elorouter_inference.ipynb) | +| [GMTRouter](gmtrouter/01_gmtrouter_training_and_inference.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ulab-uiuc/LLMRouter/blob/fix/137-add-colab/colab_notebooks/gmtrouter/01_gmtrouter_training_and_inference.ipynb) | +| [GraphRouter](graphrouter/01_graphrouter_training_and_inference.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ulab-uiuc/LLMRouter/blob/fix/137-add-colab/colab_notebooks/graphrouter/01_graphrouter_training_and_inference.ipynb) | +| [HybridLLMRouter](hybrid_llm_router/01_hybrid_llm_router_training_and_inference.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ulab-uiuc/LLMRouter/blob/fix/137-add-colab/colab_notebooks/hybrid_llm_router/01_hybrid_llm_router_training_and_inference.ipynb) | +| [KNNMultiRoundRouter](knnmultiroundrouter/01_knnmultiroundrouter_training_and_inference.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ulab-uiuc/LLMRouter/blob/fix/137-add-colab/colab_notebooks/knnmultiroundrouter/01_knnmultiroundrouter_training_and_inference.ipynb) | +| [KNNRouter](knnrouter/01_knnrouter_training_and_inference.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ulab-uiuc/LLMRouter/blob/fix/137-add-colab/colab_notebooks/knnrouter/01_knnrouter_training_and_inference.ipynb) | +| [LargestLLM](largest_llm/01_largest_llm_inference.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ulab-uiuc/LLMRouter/blob/fix/137-add-colab/colab_notebooks/largest_llm/01_largest_llm_inference.ipynb) | +| [LLMMultiRoundRouter](llmmultiroundrouter/01_llmmultiroundrouter_inference.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ulab-uiuc/LLMRouter/blob/fix/137-add-colab/colab_notebooks/llmmultiroundrouter/01_llmmultiroundrouter_inference.ipynb) | +| [MFRouter](mfrouter/01_mfrouter_training_and_inference.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ulab-uiuc/LLMRouter/blob/fix/137-add-colab/colab_notebooks/mfrouter/01_mfrouter_training_and_inference.ipynb) | +| [MLPRouter](mlprouter/01_mlprouter_training_and_inference.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ulab-uiuc/LLMRouter/blob/fix/137-add-colab/colab_notebooks/mlprouter/01_mlprouter_training_and_inference.ipynb) | +| [RouterR1](router_r1/01_router_r1_inference.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ulab-uiuc/LLMRouter/blob/fix/137-add-colab/colab_notebooks/router_r1/01_router_r1_inference.ipynb) | +| [SmallestLLM](smallest_llm/01_smallest_llm_inference.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ulab-uiuc/LLMRouter/blob/fix/137-add-colab/colab_notebooks/smallest_llm/01_smallest_llm_inference.ipynb) | +| [SVMRouter](svmrouter/01_svmrouter_training_and_inference.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ulab-uiuc/LLMRouter/blob/fix/137-add-colab/colab_notebooks/svmrouter/01_svmrouter_training_and_inference.ipynb) | + +--- + +## Router Comparison + +| Router | Type | Training Required | GPU Required | Best For | +|--------|------|-------------------|--------------|----------| +| KNNRouter | Classification | Yes | No | Simple, interpretable routing | +| SVMRouter | Classification | Yes | No | High-dimensional data | +| MLPRouter | Neural Network | Yes | No | Complex decision boundaries | +| MFRouter | Matrix Factorization | Yes | No | Collaborative filtering | +| DCRouter | Transformer | Yes | Recommended | High accuracy routing | +| GraphRouter | GNN | Yes | Recommended | Relational patterns | +| CausalLMRouter | LLM Finetuning | Yes | Required | Complex queries | +| AutomixRouter | POMDP | Yes | No | Cost-efficient routing | +| HybridLLMRouter | MLP | Yes | No | Binary routing | +| GMTRouter | HeteroGNN | Yes | Recommended | Personalized multi-turn | +| EloRouter | Rating-based | No | No | Baseline/simple cases | +| SmallestLLM | Baseline | No | No | Cost-efficiency baseline | +| LargestLLM | Baseline | No | No | Performance upper bound | +| RouterR1 | Agentic | No | Required | Complex reasoning | +| KNNMultiRoundRouter | Multi-Round KNN | Yes | No | Multi-step queries | +| LLMMultiRoundRouter | Multi-Round LLM | No | Optional | Zero-shot multi-round | + +--- + +## Getting Started on Colab + +1. Click the **Open in Colab** badge next to any notebook above +2. In Colab, install dependencies: + ```python + !pip install llmrouter-lib transformers torch + ``` +3. Clone the repository (for data and configs): + ```python + !git clone https://github.com/ulab-uiuc/LLMRouter.git + %cd LLMRouter + ``` +4. Run the notebook cells + +--- + +## Configuration Files + +Training configurations are located in: +- `configs/model_config_train/` - Training configurations +- `configs/model_config_test/` - Inference configurations + +## API Keys + +For full inference (calling LLM APIs), set environment variables: + +```python +import os +os.environ['OPENAI_API_KEY'] = 'your-key' +os.environ['ANTHROPIC_API_KEY'] = 'your-key' +# Or for multiple keys: +os.environ['API_KEYS'] = '["key1", "key2"]' +``` + +--- + +## Common Issues + +### Out of Memory (OOM) +- Reduce `batch_size` in configuration +- Use CPU instead of GPU for smaller models +- Enable gradient checkpointing for large models + +### Missing Data +- Run `01_data_preparation.ipynb` first +- Check data paths in configuration files +- Verify all required files exist + +### Import Errors +- Install all dependencies: `pip install llmrouter-lib` +- Ensure you're in the correct directory +- Check Python path includes project root diff --git a/colab_notebooks/automix_router/01_automix_router_training_and_inference.ipynb b/colab_notebooks/automix_router/01_automix_router_training_and_inference.ipynb new file mode 100644 index 0000000..69c315e --- /dev/null +++ b/colab_notebooks/automix_router/01_automix_router_training_and_inference.ipynb @@ -0,0 +1,2290 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# AutomixRouter - Training\n", + "\n", + "This notebook demonstrates how to train the **AutomixRouter** (Automatic LLM Mixing Router).\n", + "\n", + "## Overview\n", + "\n", + "AutomixRouter implements cost-efficient routing with self-verification. It starts with a small model\n", + "and escalates to a larger model only when needed, based on verification of the response quality.\n", + "\n", + "**Key Features**:\n", + "- POMDP-based routing strategy\n", + "- Self-consistency verification\n", + "- Cost-aware routing\n", + "- Automatic small/large model selection\n", + "\n", + "**Routing Methods**:\n", + "1. **Threshold**: Simple threshold-based routing\n", + "2. **SelfConsistency**: Self-consistency check before escalation\n", + "3. **POMDP**: Partially Observable Markov Decision Process" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Environment Setup" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cloning into 'LLMRouter'...\n", + "remote: Enumerating objects: 6017, done.\u001b[K\n", + "remote: Counting objects: 100% (172/172), done.\u001b[K\n", + "remote: Compressing objects: 100% (91/91), done.\u001b[K\n", + "remote: Total 6017 (delta 98), reused 87 (delta 81), pack-reused 5845 (from 2)\u001b[K\n", + "Receiving objects: 100% (6017/6017), 89.41 MiB | 58.06 MiB/s, done.\n", + "Resolving deltas: 100% (2946/2946), done.\n", + "Updating files: 100% (288/288), done.\n", + "/home/zhongjie/LLMRouter\n", + "Obtaining file:///home/zhongjie/LLMRouter\n", + " Installing build dependencies ... \u001b[?25ldone\n", + "\u001b[?25h Checking if build backend supports build_editable ... \u001b[?25ldone\n", + "\u001b[?25h Getting requirements to build editable ... \u001b[?25ldone\n", + "\u001b[?25h Preparing editable metadata (pyproject.toml) ... \u001b[?25ldone\n", + "\u001b[?25hRequirement already satisfied: torch>=2.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.9.1)\n", + "Requirement already satisfied: transformers>=4.40 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (4.57.6)\n", + "Requirement already satisfied: sentencepiece>=0.1.99 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (0.2.1)\n", + "Requirement already satisfied: numpy>=1.21 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.3.5)\n", + "Requirement already satisfied: pandas>=1.5 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.3.3)\n", + "Requirement already satisfied: scikit-learn>=1.2 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (1.8.0)\n", + "Requirement already satisfied: pyyaml>=6.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (6.0.3)\n", + "Requirement already satisfied: tqdm>=4.65 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (4.67.1)\n", + "Requirement already satisfied: datasets>=2.14 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (4.5.0)\n", + "Requirement already satisfied: pydantic>=2.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.12.5)\n", + "Requirement already satisfied: gradio>=4.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (6.3.0)\n", + "Requirement already satisfied: litellm>=1.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (1.81.0)\n", + "Requirement already satisfied: peft>=0.7 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (0.18.1)\n", + "Requirement already satisfied: torch-geometric>=2.3 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.7.0)\n", + "Requirement already satisfied: scipy>=1.10 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (1.16.3)\n", + "Requirement already satisfied: protobuf>=3.20 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (6.31.1)\n", + "Requirement already satisfied: filelock in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (3.20.2)\n", + "Requirement already satisfied: pyarrow>=21.0.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (22.0.0)\n", + "Requirement already satisfied: dill<0.4.1,>=0.3.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (0.4.0)\n", + "Requirement already satisfied: requests>=2.32.2 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (2.32.5)\n", + "Requirement already satisfied: httpx<1.0.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (0.28.1)\n", + "Requirement already satisfied: xxhash in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (3.6.0)\n", + "Requirement already satisfied: multiprocess<0.70.19 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (0.70.18)\n", + "Requirement already satisfied: fsspec<=2025.10.0,>=2023.1.0 in /opt/conda/lib/python3.13/site-packages (from fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (2025.10.0)\n", + "Requirement already satisfied: huggingface-hub<2.0,>=0.25.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (0.36.0)\n", + "Requirement already satisfied: packaging in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (25.0)\n", + "Requirement already satisfied: aiohttp!=4.0.0a0,!=4.0.0a1 in /opt/conda/lib/python3.13/site-packages (from fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (3.13.3)\n", + "Requirement already satisfied: anyio in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (4.12.0)\n", + "Requirement already satisfied: certifi in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (2026.1.4)\n", + "Requirement already satisfied: httpcore==1.* in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.0.9)\n", + "Requirement already satisfied: idna in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (3.11)\n", + "Requirement already satisfied: h11>=0.16 in /opt/conda/lib/python3.13/site-packages (from httpcore==1.*->httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (0.16.0)\n", + "Requirement already satisfied: typing-extensions>=3.7.4.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<2.0,>=0.25.0->datasets>=2.14->llmrouter-lib==0.2.0) (4.15.0)\n", + "Requirement already satisfied: hf-xet<2.0.0,>=1.1.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<2.0,>=0.25.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.2.0)\n", + "Requirement already satisfied: aiohappyeyeballs>=2.5.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (2.6.1)\n", + "Requirement already satisfied: aiosignal>=1.4.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.4.0)\n", + "Requirement already satisfied: attrs>=17.3.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (25.4.0)\n", + "Requirement already satisfied: frozenlist>=1.1.1 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.8.0)\n", + "Requirement already satisfied: multidict<7.0,>=4.5 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (6.7.0)\n", + "Requirement already satisfied: propcache>=0.2.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (0.4.1)\n", + "Requirement already satisfied: yarl<2.0,>=1.17.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.22.0)\n", + "Requirement already satisfied: aiofiles<25.0,>=22.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (24.1.0)\n", + "Requirement already satisfied: audioop-lts<1.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.2.2)\n", + "Requirement already satisfied: brotli>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (1.2.0)\n", + "Requirement already satisfied: fastapi<1.0,>=0.115.2 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.128.0)\n", + "Requirement already satisfied: ffmpy in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (1.0.0)\n", + "Requirement already satisfied: gradio-client==2.0.3 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (2.0.3)\n", + "Requirement already satisfied: groovy~=0.1 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.1.2)\n", + "Requirement already satisfied: jinja2<4.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (3.1.6)\n", + "Requirement already satisfied: markupsafe<4.0,>=2.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (3.0.3)\n", + "Requirement already satisfied: orjson~=3.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (3.11.5)\n", + "Requirement already satisfied: pillow<13.0,>=8.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (12.1.0)\n", + "Requirement already satisfied: pydub in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.25.1)\n", + "Requirement already satisfied: python-multipart>=0.0.18 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.0.21)\n", + "Requirement already satisfied: safehttpx<0.2.0,>=0.1.7 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.1.7)\n", + "Requirement already satisfied: semantic-version~=2.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (2.10.0)\n", + "Requirement already satisfied: starlette<1.0,>=0.40.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.50.0)\n", + "Requirement already satisfied: tomlkit<0.14.0,>=0.12.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.13.3)\n", + "Requirement already satisfied: typer<1.0,>=0.12 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.21.1)\n", + "Requirement already satisfied: uvicorn>=0.14.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.40.0)\n", + "Requirement already satisfied: annotated-doc>=0.0.2 in /opt/conda/lib/python3.13/site-packages (from fastapi<1.0,>=0.115.2->gradio>=4.0->llmrouter-lib==0.2.0) (0.0.4)\n", + "Requirement already satisfied: python-dateutil>=2.8.2 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.2.0) (2.9.0.post0)\n", + "Requirement already satisfied: pytz>=2020.1 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.2.0) (2025.2)\n", + "Requirement already satisfied: tzdata>=2022.7 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.2.0) (2025.3)\n", + "Requirement already satisfied: annotated-types>=0.6.0 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.2.0) (0.7.0)\n", + "Requirement already satisfied: pydantic-core==2.41.5 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.2.0) (2.41.5)\n", + "Requirement already satisfied: typing-inspection>=0.4.2 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.2.0) (0.4.2)\n", + "Requirement already satisfied: click>=8.0.0 in /opt/conda/lib/python3.13/site-packages (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (8.3.1)\n", + "Requirement already satisfied: shellingham>=1.3.0 in /opt/conda/lib/python3.13/site-packages (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (1.5.4)\n", + "Requirement already satisfied: rich>=10.11.0 in /opt/conda/lib/python3.13/site-packages (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (14.2.0)\n", + "Requirement already satisfied: fastuuid>=0.13.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (0.14.0)\n", + "Requirement already satisfied: grpcio!=1.68.*,!=1.69.*,!=1.70.*,!=1.71.0,!=1.71.1,!=1.72.0,!=1.72.1,!=1.73.0,>=1.62.3 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (1.76.0)\n", + "Requirement already satisfied: importlib-metadata>=6.8.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (8.7.0)\n", + "Requirement already satisfied: jsonschema<5.0.0,>=4.23.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (4.25.1)\n", + "Requirement already satisfied: openai>=2.8.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (2.15.0)\n", + "Requirement already satisfied: python-dotenv>=0.2.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (1.2.1)\n", + "Requirement already satisfied: tiktoken>=0.7.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (0.12.0)\n", + "Requirement already satisfied: tokenizers in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (0.22.2)\n", + "Requirement already satisfied: jsonschema-specifications>=2023.03.6 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.2.0) (2025.9.1)\n", + "Requirement already satisfied: referencing>=0.28.4 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.2.0) (0.37.0)\n", + "Requirement already satisfied: rpds-py>=0.7.1 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.2.0) (0.30.0)\n", + "Requirement already satisfied: zipp>=3.20 in /opt/conda/lib/python3.13/site-packages (from importlib-metadata>=6.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (3.23.0)\n", + "Requirement already satisfied: distro<2,>=1.7.0 in /opt/conda/lib/python3.13/site-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (1.9.0)\n", + "Requirement already satisfied: jiter<1,>=0.10.0 in /opt/conda/lib/python3.13/site-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (0.12.0)\n", + "Requirement already satisfied: sniffio in /opt/conda/lib/python3.13/site-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (1.3.1)\n", + "Requirement already satisfied: psutil in /opt/conda/lib/python3.13/site-packages (from peft>=0.7->llmrouter-lib==0.2.0) (7.2.1)\n", + "Requirement already satisfied: accelerate>=0.21.0 in /opt/conda/lib/python3.13/site-packages (from peft>=0.7->llmrouter-lib==0.2.0) (1.12.0)\n", + "Requirement already satisfied: safetensors in /opt/conda/lib/python3.13/site-packages (from peft>=0.7->llmrouter-lib==0.2.0) (0.7.0)\n", + "Requirement already satisfied: six>=1.5 in /opt/conda/lib/python3.13/site-packages (from python-dateutil>=2.8.2->pandas>=1.5->llmrouter-lib==0.2.0) (1.17.0)\n", + "Requirement already satisfied: charset_normalizer<4,>=2 in /opt/conda/lib/python3.13/site-packages (from requests>=2.32.2->datasets>=2.14->llmrouter-lib==0.2.0) (3.4.4)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/conda/lib/python3.13/site-packages (from requests>=2.32.2->datasets>=2.14->llmrouter-lib==0.2.0) (2.6.2)\n", + "Requirement already satisfied: markdown-it-py>=2.2.0 in /opt/conda/lib/python3.13/site-packages (from rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (4.0.0)\n", + "Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /opt/conda/lib/python3.13/site-packages (from rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (2.19.2)\n", + "Requirement already satisfied: mdurl~=0.1 in /opt/conda/lib/python3.13/site-packages (from markdown-it-py>=2.2.0->rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (0.1.2)\n", + "Requirement already satisfied: joblib>=1.3.0 in /opt/conda/lib/python3.13/site-packages (from scikit-learn>=1.2->llmrouter-lib==0.2.0) (1.5.3)\n", + "Requirement already satisfied: threadpoolctl>=3.2.0 in /opt/conda/lib/python3.13/site-packages (from scikit-learn>=1.2->llmrouter-lib==0.2.0) (3.6.0)\n", + "Requirement already satisfied: regex>=2022.1.18 in /opt/conda/lib/python3.13/site-packages (from tiktoken>=0.7.0->litellm>=1.0->llmrouter-lib==0.2.0) (2026.1.15)\n", + "Requirement already satisfied: setuptools in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (80.9.0)\n", + "Requirement already satisfied: sympy>=1.13.3 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (1.14.0)\n", + "Requirement already satisfied: networkx>=2.5.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (3.6.1)\n", + "Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.93)\n", + "Requirement already satisfied: nvidia-cuda-runtime-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.90)\n", + "Requirement already satisfied: nvidia-cuda-cupti-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.90)\n", + "Requirement already satisfied: nvidia-cudnn-cu12==9.10.2.21 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (9.10.2.21)\n", + "Requirement already satisfied: nvidia-cublas-cu12==12.8.4.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.4.1)\n", + "Requirement already satisfied: nvidia-cufft-cu12==11.3.3.83 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (11.3.3.83)\n", + "Requirement already satisfied: nvidia-curand-cu12==10.3.9.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (10.3.9.90)\n", + "Requirement already satisfied: nvidia-cusolver-cu12==11.7.3.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (11.7.3.90)\n", + "Requirement already satisfied: nvidia-cusparse-cu12==12.5.8.93 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.5.8.93)\n", + "Requirement already satisfied: nvidia-cusparselt-cu12==0.7.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (0.7.1)\n", + "Requirement already satisfied: nvidia-nccl-cu12==2.27.5 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (2.27.5)\n", + "Requirement already satisfied: nvidia-nvshmem-cu12==3.3.20 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (3.3.20)\n", + "Requirement already satisfied: nvidia-nvtx-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.90)\n", + "Requirement already satisfied: nvidia-nvjitlink-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.93)\n", + "Requirement already satisfied: nvidia-cufile-cu12==1.13.1.3 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (1.13.1.3)\n", + "Requirement already satisfied: triton==3.5.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (3.5.1)\n", + "Requirement already satisfied: mpmath<1.4,>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from sympy>=1.13.3->torch>=2.0->llmrouter-lib==0.2.0) (1.3.0)\n", + "Requirement already satisfied: pyparsing in /opt/conda/lib/python3.13/site-packages (from torch-geometric>=2.3->llmrouter-lib==0.2.0) (3.3.1)\n", + "Building wheels for collected packages: llmrouter-lib\n", + " Building editable for llmrouter-lib (pyproject.toml) ... \u001b[?25ldone\n", + "\u001b[?25h Created wheel for llmrouter-lib: filename=llmrouter_lib-0.2.0-0.editable-py3-none-any.whl size=15709 sha256=25bea689e22427a883c02c524cf8900fc599db01e316d57542c77cafa23c906b\n", + " Stored in directory: /tmp/pip-ephem-wheel-cache-e6eexm8o/wheels/82/4a/fd/59c4aec93c356c380d86a88ab74bece164c0aa855d68b155e4\n", + "Successfully built llmrouter-lib\n", + "Installing collected packages: llmrouter-lib\n", + " Attempting uninstall: llmrouter-lib\n", + " Found existing installation: llmrouter-lib 0.2.0\n", + " Uninstalling llmrouter-lib-0.2.0:\n", + " Successfully uninstalled llmrouter-lib-0.2.0\n", + "Successfully installed llmrouter-lib-0.2.0\n", + "Requirement already satisfied: transformers in /opt/conda/lib/python3.13/site-packages (4.57.6)\n", + "Requirement already satisfied: torch in /opt/conda/lib/python3.13/site-packages (2.9.1)\n", + "Requirement already satisfied: peft in /opt/conda/lib/python3.13/site-packages (0.18.1)\n", + "Requirement already satisfied: accelerate in /opt/conda/lib/python3.13/site-packages (1.12.0)\n", + "Collecting bitsandbytes\n", + " Using cached bitsandbytes-0.49.1-py3-none-manylinux_2_24_x86_64.whl.metadata (10 kB)\n", + "Requirement already satisfied: filelock in /opt/conda/lib/python3.13/site-packages (from transformers) (3.20.2)\n", + "Requirement already satisfied: huggingface-hub<1.0,>=0.34.0 in /opt/conda/lib/python3.13/site-packages (from transformers) (0.36.0)\n", + "Requirement already satisfied: numpy>=1.17 in /opt/conda/lib/python3.13/site-packages (from transformers) (2.3.5)\n", + "Requirement already satisfied: packaging>=20.0 in /opt/conda/lib/python3.13/site-packages (from transformers) (25.0)\n", + "Requirement already satisfied: pyyaml>=5.1 in /opt/conda/lib/python3.13/site-packages (from transformers) (6.0.3)\n", + "Requirement already satisfied: regex!=2019.12.17 in /opt/conda/lib/python3.13/site-packages (from transformers) (2026.1.15)\n", + "Requirement already satisfied: requests in /opt/conda/lib/python3.13/site-packages (from transformers) (2.32.5)\n", + "Requirement already satisfied: tokenizers<=0.23.0,>=0.22.0 in /opt/conda/lib/python3.13/site-packages (from transformers) (0.22.2)\n", + "Requirement already satisfied: safetensors>=0.4.3 in /opt/conda/lib/python3.13/site-packages (from transformers) (0.7.0)\n", + "Requirement already satisfied: tqdm>=4.27 in /opt/conda/lib/python3.13/site-packages (from transformers) (4.67.1)\n", + "Requirement already satisfied: fsspec>=2023.5.0 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (2025.10.0)\n", + "Requirement already satisfied: typing-extensions>=3.7.4.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (4.15.0)\n", + "Requirement already satisfied: hf-xet<2.0.0,>=1.1.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (1.2.0)\n", + "Requirement already satisfied: setuptools in /opt/conda/lib/python3.13/site-packages (from torch) (80.9.0)\n", + "Requirement already satisfied: sympy>=1.13.3 in /opt/conda/lib/python3.13/site-packages (from torch) (1.14.0)\n", + "Requirement already satisfied: networkx>=2.5.1 in /opt/conda/lib/python3.13/site-packages (from torch) (3.6.1)\n", + "Requirement already satisfied: jinja2 in /opt/conda/lib/python3.13/site-packages (from torch) (3.1.6)\n", + "Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.93)\n", + "Requirement already satisfied: nvidia-cuda-runtime-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-cuda-cupti-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-cudnn-cu12==9.10.2.21 in /opt/conda/lib/python3.13/site-packages (from torch) (9.10.2.21)\n", + "Requirement already satisfied: nvidia-cublas-cu12==12.8.4.1 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.4.1)\n", + "Requirement already satisfied: nvidia-cufft-cu12==11.3.3.83 in /opt/conda/lib/python3.13/site-packages (from torch) (11.3.3.83)\n", + "Requirement already satisfied: nvidia-curand-cu12==10.3.9.90 in /opt/conda/lib/python3.13/site-packages (from torch) (10.3.9.90)\n", + "Requirement already satisfied: nvidia-cusolver-cu12==11.7.3.90 in /opt/conda/lib/python3.13/site-packages (from torch) (11.7.3.90)\n", + "Requirement already satisfied: nvidia-cusparse-cu12==12.5.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.5.8.93)\n", + "Requirement already satisfied: nvidia-cusparselt-cu12==0.7.1 in /opt/conda/lib/python3.13/site-packages (from torch) (0.7.1)\n", + "Requirement already satisfied: nvidia-nccl-cu12==2.27.5 in /opt/conda/lib/python3.13/site-packages (from torch) (2.27.5)\n", + "Requirement already satisfied: nvidia-nvshmem-cu12==3.3.20 in /opt/conda/lib/python3.13/site-packages (from torch) (3.3.20)\n", + "Requirement already satisfied: nvidia-nvtx-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-nvjitlink-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.93)\n", + "Requirement already satisfied: nvidia-cufile-cu12==1.13.1.3 in /opt/conda/lib/python3.13/site-packages (from torch) (1.13.1.3)\n", + "Requirement already satisfied: triton==3.5.1 in /opt/conda/lib/python3.13/site-packages (from torch) (3.5.1)\n", + "Requirement already satisfied: psutil in /opt/conda/lib/python3.13/site-packages (from peft) (7.2.1)\n", + "Requirement already satisfied: mpmath<1.4,>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from sympy>=1.13.3->torch) (1.3.0)\n", + "Requirement already satisfied: MarkupSafe>=2.0 in /opt/conda/lib/python3.13/site-packages (from jinja2->torch) (3.0.3)\n", + "Requirement already satisfied: charset_normalizer<4,>=2 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (3.4.4)\n", + "Requirement already satisfied: idna<4,>=2.5 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (3.11)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (2.6.2)\n", + "Requirement already satisfied: certifi>=2017.4.17 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (2026.1.4)\n", + "Using cached bitsandbytes-0.49.1-py3-none-manylinux_2_24_x86_64.whl (59.1 MB)\n", + "Installing collected packages: bitsandbytes\n", + "Successfully installed bitsandbytes-0.49.1\n" + ] + } + ], + "source": [ + "# Install required packages (for Colab)\n", + "!git clone https://github.com/ulab-uiuc/LLMRouter.git\n", + "%cd LLMRouter\n", + "!pip install -e .\n", + "!pip install transformers torch peft accelerate bitsandbytes" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "os.environ['OPENAI_API_KEY'] = 'your-key'\n", + "os.environ['ANTHROPIC_API_KEY'] = 'your-key'\n", + "# Or for multiple keys:\n", + "os.environ['API_KEYS'] = '[\"key1\", \"key2\"]'" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Environment setup complete!\n" + ] + } + ], + "source": [ + "from llmrouter.models.automix import AutomixRouter, AutomixRouterTrainer\n", + "from llmrouter.utils import setup_environment\n", + "\n", + "setup_environment()\n", + "print(\"Environment setup complete!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Configuration\n", + "\n", + "AutomixRouter uses the following configuration parameters:\n", + "\n", + "| Parameter | Description | Default |\n", + "|-----------|-------------|--------|\n", + "| `routing_method` | Routing strategy | \"POMDP\" |\n", + "| `num_bins` | Discretization bins | 8 |\n", + "| `small_model_cost` | Cost of small model | 1 |\n", + "| `large_model_cost` | Cost of large model | 50 |\n", + "| `verifier_cost` | Cost of verifier | 1 |" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Current Configuration:\n", + "==================================================\n", + "data_path:\n", + " llm_data: data/example_data/llm_candidates/default_llm.json\n", + " llm_embedding_data: data/example_data/llm_candidates/default_llm_embeddings.json\n", + " query_data_test: data/example_data/query_data/default_query_test.jsonl\n", + " query_data_train: data/example_data/query_data/default_query_train.jsonl\n", + " routing_data_test: data/example_data/routing_data/temp_test.jsonl\n", + " routing_data_train: data/example_data/routing_data/temp.jsonl\n", + "hparam:\n", + " cost_constraint: null\n", + " device: cpu\n", + " large_model_cost: 50\n", + " max_workers: 1\n", + " num_bins: 8\n", + " num_inference_samples: 2\n", + " routing_method: POMDP\n", + " small_model_cost: 1\n", + " verbose: true\n", + " verifier_cost: 1\n", + "metric:\n", + " weights:\n", + " cost: 0\n", + " llm_judge: 0\n", + " performance: 1\n", + "model_path:\n", + " ini_model_path: ''\n", + " save_model_path: saved_models/automix/automix_model.pkl\n", + "\n" + ] + } + ], + "source": [ + "import yaml\n", + "\n", + "CONFIG_PATH = \"configs/model_config_train/automix.yaml\"\n", + "\n", + "with open(CONFIG_PATH, 'r') as f:\n", + " config = yaml.safe_load(f)\n", + "\n", + "print(\"Current Configuration:\")\n", + "print(\"=\" * 50)\n", + "print(yaml.dump(config, default_flow_style=False))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Initialize Router" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "router = AutomixRouter(yaml_path=CONFIG_PATH)\n", + "\n", + "print(\"Router initialized successfully!\")\n", + "print(f\"Number of training samples: {len(router.routing_data_train)}\")\n", + "print(f\"Routing method: {config['hparam']['routing_method']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Training" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Trainer initialized!\n" + ] + } + ], + "source": [ + "trainer = AutomixRouterTrainer(router=router, device='cpu')\n", + "\n", + "print(\"Trainer initialized!\")" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting training...\n", + "==================================================\n", + "==================================================\n", + "Training completed!\n" + ] + } + ], + "source": [ + "print(\"Starting training...\")\n", + "print(\"=\" * 50)\n", + "\n", + "trainer.train()\n", + "\n", + "print(\"=\" * 50)\n", + "print(\"Training completed!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Model Verification" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=qwen/qwen2.5-7b-instruct\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|██████████| 1/1 [00:02<00:00, 2.65s/it]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=qwen/qwen2.5-7b-instruct\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 0%| | 0/1 [00:00 qwen/qwen2.5-7b-instruct\n", + "✅ Auto-detected large model: mixtral-8x22b-instruct-v0.1 (141.0B) -> mistralai/mixtral-8x22b-instruct-v0.1\n", + "Warning: HF_TOKEN not set; skipping HuggingFace login\n", + "[DEBUG] init_providers: API key from OPENAI_API_KEY, length=70, first_10=nvapi-rEKW...\n", + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=qwen/qwen2.5-7b-instruct\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 5%|▍ | 1/22 [00:01<00:33, 1.60s/it]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=qwen/qwen2.5-7b-instruct\n", + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=qwen/qwen2.5-7b-instruct\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 14%|█▎ | 3/22 [00:02<00:10, 1.88it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=qwen/qwen2.5-7b-instruct\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 18%|█▊ | 4/22 [00:02<00:07, 2.45it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=qwen/qwen2.5-7b-instruct\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 23%|██▎ | 5/22 [00:02<00:05, 2.98it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=qwen/qwen2.5-7b-instruct\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 27%|██▋ | 6/22 [00:02<00:04, 3.29it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=qwen/qwen2.5-7b-instruct\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 36%|███▋ | 8/22 [00:03<00:03, 4.00it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=qwen/qwen2.5-7b-instruct\n", + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=qwen/qwen2.5-7b-instruct\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 45%|████▌ | 10/22 [00:03<00:02, 4.61it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=qwen/qwen2.5-7b-instruct\n", + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=qwen/qwen2.5-7b-instruct\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 55%|█████▍ | 12/22 [00:03<00:02, 4.86it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=qwen/qwen2.5-7b-instruct\n", + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=qwen/qwen2.5-7b-instruct\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 59%|█████▉ | 13/22 [00:04<00:01, 4.95it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=qwen/qwen2.5-7b-instruct\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 64%|██████▎ | 14/22 [00:04<00:02, 2.69it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=qwen/qwen2.5-7b-instruct\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 68%|██████▊ | 15/22 [00:05<00:04, 1.75it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=qwen/qwen2.5-7b-instruct\n", + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=qwen/qwen2.5-7b-instruct\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 82%|████████▏ | 18/22 [00:06<00:01, 3.04it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=qwen/qwen2.5-7b-instruct\n", + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=qwen/qwen2.5-7b-instruct\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 86%|████████▋ | 19/22 [00:06<00:00, 3.40it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=qwen/qwen2.5-7b-instruct\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 95%|█████████▌| 21/22 [00:07<00:00, 4.04it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=qwen/qwen2.5-7b-instruct\n", + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=qwen/qwen2.5-7b-instruct\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|██████████| 22/22 [00:07<00:00, 3.01it/s]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=mistralai/mixtral-8x22b-instruct-v0.1\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 5%|▍ | 1/22 [00:00<00:16, 1.26it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=mistralai/mixtral-8x22b-instruct-v0.1\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 9%|▉ | 2/22 [00:01<00:16, 1.21it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=mistralai/mixtral-8x22b-instruct-v0.1\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 14%|█▎ | 3/22 [00:01<00:11, 1.65it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=mistralai/mixtral-8x22b-instruct-v0.1\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 18%|█▊ | 4/22 [00:02<00:09, 1.97it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=mistralai/mixtral-8x22b-instruct-v0.1\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 23%|██▎ | 5/22 [00:02<00:07, 2.37it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=mistralai/mixtral-8x22b-instruct-v0.1\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 27%|██▋ | 6/22 [00:02<00:06, 2.55it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=mistralai/mixtral-8x22b-instruct-v0.1\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 32%|███▏ | 7/22 [00:03<00:05, 2.87it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=mistralai/mixtral-8x22b-instruct-v0.1\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 36%|███▋ | 8/22 [00:03<00:04, 3.15it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=mistralai/mixtral-8x22b-instruct-v0.1\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 41%|████ | 9/22 [00:03<00:04, 3.11it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=mistralai/mixtral-8x22b-instruct-v0.1\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 45%|████▌ | 10/22 [00:04<00:03, 3.09it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=mistralai/mixtral-8x22b-instruct-v0.1\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 50%|█████ | 11/22 [00:04<00:03, 3.28it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=mistralai/mixtral-8x22b-instruct-v0.1\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 55%|█████▍ | 12/22 [00:04<00:03, 3.25it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=mistralai/mixtral-8x22b-instruct-v0.1\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 59%|█████▉ | 13/22 [00:05<00:02, 3.18it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=mistralai/mixtral-8x22b-instruct-v0.1\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 64%|██████▎ | 14/22 [00:05<00:02, 3.18it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=mistralai/mixtral-8x22b-instruct-v0.1\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 68%|██████▊ | 15/22 [00:05<00:02, 3.15it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=mistralai/mixtral-8x22b-instruct-v0.1\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 73%|███████▎ | 16/22 [00:06<00:01, 3.08it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=mistralai/mixtral-8x22b-instruct-v0.1\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 77%|███████▋ | 17/22 [00:06<00:01, 3.07it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=mistralai/mixtral-8x22b-instruct-v0.1\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 82%|████████▏ | 18/22 [00:06<00:01, 3.03it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=mistralai/mixtral-8x22b-instruct-v0.1\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 86%|████████▋ | 19/22 [00:06<00:00, 3.06it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=mistralai/mixtral-8x22b-instruct-v0.1\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 91%|█████████ | 20/22 [00:07<00:00, 3.09it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=mistralai/mixtral-8x22b-instruct-v0.1\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 95%|█████████▌| 21/22 [00:07<00:00, 3.08it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=mistralai/mixtral-8x22b-instruct-v0.1\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|██████████| 22/22 [00:07<00:00, 2.79it/s]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[DEBUG] get_llm_response_via_api: Using api_key=nvapi-rEKW...hJfP, base_url=https://integrate.api.nvidia.com/v1, model=qwen/qwen2.5-7b-instruct\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 0%| | 0/22 [00:00 mistralai/mixtral-8x22b-instruct-v0.1\n", + " 2. Q: There are 3 houses in a row, numbered... -> qwen/qwen2.5-7b-instruct\n", + " 3. Q: There are 3 houses in a row, numbered... -> mistralai/mixtral-8x22b-instruct-v0.1\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "import json\n", + "\n", + "# Load queries from a JSONL file\n", + "def load_queries_from_file(file_path):\n", + " \"\"\"Load queries from a JSONL file.\"\"\"\n", + " queries = []\n", + " with open(file_path, 'r', encoding='utf-8') as f:\n", + " for line in f:\n", + " if line.strip():\n", + " queries.append(json.loads(line))\n", + " return queries\n", + "\n", + "# Save results to a JSONL file\n", + "def save_results_to_file(results, output_path):\n", + " \"\"\"Save routing results to a JSONL file.\"\"\"\n", + " os.makedirs(os.path.dirname(output_path), exist_ok=True)\n", + " with open(output_path, 'w', encoding='utf-8') as f:\n", + " for result in results:\n", + " f.write(json.dumps(result, ensure_ascii=False) + '\\n')\n", + " print(f\"Results saved to: {output_path}\")\n", + "\n", + "# Example: Load from default query file\n", + "QUERY_FILE = \"data/example_data/query_data/default_query_test.jsonl\"\n", + "OUTPUT_FILE = \"outputs/automix_router_results.jsonl\"\n", + "\n", + "if os.path.exists(QUERY_FILE):\n", + " # Load queries\n", + " file_queries = load_queries_from_file(QUERY_FILE)\n", + " print(f\"Loaded {len(file_queries)} queries from: {QUERY_FILE}\")\n", + " \n", + " # Route queries\n", + " file_results = router.route_batch(batch=file_queries[:10])\n", + " print(f\"Routed {len(file_results)} queries\")\n", + " \n", + " # Save results\n", + " save_results_to_file(file_results, OUTPUT_FILE)\n", + " \n", + " # Show sample results\n", + " print(f\"\\nSample results:\")\n", + " for i, result in enumerate(file_results[:3], 1):\n", + " print(f\" {i}. {result.get('query', '')[:40]}... -> {result['model_name']}\")\n", + "else:\n", + " print(f\"Query file not found: {QUERY_FILE}\")\n", + " print(\"Create a JSONL file with format: {\\\"query\\\": \\\"Your question\\\"}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "AutomixRouter intelligently routes:\n", + "- Simple queries to small, cheap models\n", + "- Complex queries to large, capable models\n", + "- Uses self-verification to validate routing decisions" + ] + } + ], + "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.13.11" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/colab_notebooks/causallm_router/01_causallm_router_training_and_inference.ipynb b/colab_notebooks/causallm_router/01_causallm_router_training_and_inference.ipynb new file mode 100644 index 0000000..1109150 --- /dev/null +++ b/colab_notebooks/causallm_router/01_causallm_router_training_and_inference.ipynb @@ -0,0 +1,1063 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# CausalLMRouter - Training\n", + "\n", + "This notebook demonstrates how to train the **CausalLMRouter** (Causal Language Model Router).\n", + "\n", + "## Overview\n", + "\n", + "CausalLMRouter finetunes a causal language model (e.g., Llama-2-7B) to predict the best LLM for routing.\n", + "It uses LoRA (Low-Rank Adaptation) for efficient finetuning.\n", + "\n", + "**Key Features**:\n", + "- Uses powerful LLM backbone (Llama-2)\n", + "- Efficient LoRA finetuning\n", + "- Can understand complex query semantics\n", + "- Supports vLLM for fast inference\n", + "\n", + "**Requirements**:\n", + "- GPU with at least 16GB VRAM recommended\n", + "- HuggingFace access to Llama-2 models" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Environment Setup" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "# For Google Colab: Ensure GPU runtime is enabled\n", + "# Runtime -> Change runtime type -> Hardware accelerator -> GPU\n", + "\n", + "import os\n", + "\n", + "if 'COLAB_GPU' in os.environ:\n", + " !git clone https://github.com/ulab-uiuc/LLMRouter.git\n", + " %cd LLMRouter\n", + " !pip install -e .\n", + " !pip install transformers torch peft accelerate bitsandbytes" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "fatal: destination path 'LLMRouter' already exists and is not an empty directory.\n", + "/home/zhongjie/LLMRouter\n", + "Obtaining file:///home/zhongjie/LLMRouter\n", + " Installing build dependencies ... \u001b[?25ldone\n", + "\u001b[?25h Checking if build backend supports build_editable ... \u001b[?25ldone\n", + "\u001b[?25h Getting requirements to build editable ... \u001b[?25ldone\n", + "\u001b[?25h Preparing editable metadata (pyproject.toml) ... \u001b[?25ldone\n", + "\u001b[?25hCollecting torch>=2.0 (from llmrouter-lib==0.1.1)\n", + " Using cached torch-2.9.1-cp313-cp313-manylinux_2_28_x86_64.whl.metadata (30 kB)\n", + "Collecting transformers>=4.40 (from llmrouter-lib==0.1.1)\n", + " Using cached transformers-4.57.3-py3-none-any.whl.metadata (43 kB)\n", + "Collecting sentencepiece>=0.1.99 (from llmrouter-lib==0.1.1)\n", + " Using cached sentencepiece-0.2.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.metadata (10 kB)\n", + "Collecting numpy>=1.21 (from llmrouter-lib==0.1.1)\n", + " Using cached numpy-2.4.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.metadata (6.6 kB)\n", + "Collecting pandas>=1.5 (from llmrouter-lib==0.1.1)\n", + " Using cached pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl.metadata (91 kB)\n", + "Collecting scikit-learn>=1.2 (from llmrouter-lib==0.1.1)\n", + " Using cached scikit_learn-1.8.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.metadata (11 kB)\n", + "Requirement already satisfied: pyyaml>=6.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (6.0.3)\n", + "Requirement already satisfied: tqdm>=4.65 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (4.67.1)\n", + "Collecting datasets>=2.14 (from llmrouter-lib==0.1.1)\n", + " Using cached datasets-4.4.2-py3-none-any.whl.metadata (19 kB)\n", + "Requirement already satisfied: pydantic>=2.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (2.12.5)\n", + "Collecting gradio>=4.0 (from llmrouter-lib==0.1.1)\n", + " Using cached gradio-6.3.0-py3-none-any.whl.metadata (16 kB)\n", + "Collecting litellm>=1.0 (from llmrouter-lib==0.1.1)\n", + " Using cached litellm-1.80.13-py3-none-any.whl.metadata (29 kB)\n", + "Collecting peft>=0.7 (from llmrouter-lib==0.1.1)\n", + " Using cached peft-0.18.1-py3-none-any.whl.metadata (14 kB)\n", + "Collecting torch-geometric>=2.3 (from llmrouter-lib==0.1.1)\n", + " Using cached torch_geometric-2.7.0-py3-none-any.whl.metadata (63 kB)\n", + "Collecting scipy>=1.10 (from llmrouter-lib==0.1.1)\n", + " Using cached scipy-1.17.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.metadata (62 kB)\n", + "Collecting protobuf>=3.20 (from llmrouter-lib==0.1.1)\n", + " Using cached protobuf-6.33.3-cp39-abi3-manylinux2014_x86_64.whl.metadata (593 bytes)\n", + "Collecting filelock (from datasets>=2.14->llmrouter-lib==0.1.1)\n", + " Using cached filelock-3.20.3-py3-none-any.whl.metadata (2.1 kB)\n", + "Collecting pyarrow>=21.0.0 (from datasets>=2.14->llmrouter-lib==0.1.1)\n", + " Using cached pyarrow-22.0.0-cp313-cp313-manylinux_2_28_x86_64.whl.metadata (3.2 kB)\n", + "Collecting dill<0.4.1,>=0.3.0 (from datasets>=2.14->llmrouter-lib==0.1.1)\n", + " Using cached dill-0.4.0-py3-none-any.whl.metadata (10 kB)\n", + "Requirement already satisfied: requests>=2.32.2 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.1.1) (2.32.5)\n", + "Requirement already satisfied: httpx<1.0.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.1.1) (0.28.1)\n", + "Collecting xxhash (from datasets>=2.14->llmrouter-lib==0.1.1)\n", + " Using cached xxhash-3.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (13 kB)\n", + "Collecting multiprocess<0.70.19 (from datasets>=2.14->llmrouter-lib==0.1.1)\n", + " Using cached multiprocess-0.70.18-py313-none-any.whl.metadata (7.2 kB)\n", + "Collecting fsspec<=2025.10.0,>=2023.1.0 (from fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.1.1)\n", + " Using cached fsspec-2025.10.0-py3-none-any.whl.metadata (10 kB)\n", + "Collecting huggingface-hub<2.0,>=0.25.0 (from datasets>=2.14->llmrouter-lib==0.1.1)\n", + " Using cached huggingface_hub-1.3.1-py3-none-any.whl.metadata (13 kB)\n", + "Requirement already satisfied: packaging in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.1.1) (25.0)\n", + "Collecting aiohttp!=4.0.0a0,!=4.0.0a1 (from fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.1.1)\n", + " Using cached aiohttp-3.13.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (8.1 kB)\n", + "Requirement already satisfied: anyio in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.1.1) (4.12.0)\n", + "Requirement already satisfied: certifi in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.1.1) (2026.1.4)\n", + "Requirement already satisfied: httpcore==1.* in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.1.1) (1.0.9)\n", + "Requirement already satisfied: idna in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.1.1) (3.11)\n", + "Requirement already satisfied: h11>=0.16 in /opt/conda/lib/python3.13/site-packages (from httpcore==1.*->httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.1.1) (0.16.0)\n", + "Collecting hf-xet<2.0.0,>=1.2.0 (from huggingface-hub<2.0,>=0.25.0->datasets>=2.14->llmrouter-lib==0.1.1)\n", + " Using cached hf_xet-1.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (4.9 kB)\n", + "Collecting shellingham (from huggingface-hub<2.0,>=0.25.0->datasets>=2.14->llmrouter-lib==0.1.1)\n", + " Using cached shellingham-1.5.4-py2.py3-none-any.whl.metadata (3.5 kB)\n", + "Collecting typer-slim (from huggingface-hub<2.0,>=0.25.0->datasets>=2.14->llmrouter-lib==0.1.1)\n", + " Using cached typer_slim-0.21.1-py3-none-any.whl.metadata (16 kB)\n", + "Requirement already satisfied: typing-extensions>=4.1.0 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<2.0,>=0.25.0->datasets>=2.14->llmrouter-lib==0.1.1) (4.15.0)\n", + "Collecting aiohappyeyeballs>=2.5.0 (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.1.1)\n", + " Using cached aiohappyeyeballs-2.6.1-py3-none-any.whl.metadata (5.9 kB)\n", + "Collecting aiosignal>=1.4.0 (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.1.1)\n", + " Using cached aiosignal-1.4.0-py3-none-any.whl.metadata (3.7 kB)\n", + "Requirement already satisfied: attrs>=17.3.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.1.1) (25.4.0)\n", + "Collecting frozenlist>=1.1.1 (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.1.1)\n", + " Using cached frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (20 kB)\n", + "Collecting multidict<7.0,>=4.5 (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.1.1)\n", + " Using cached multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (5.3 kB)\n", + "Collecting propcache>=0.2.0 (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.1.1)\n", + " Using cached propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (13 kB)\n", + "Collecting yarl<2.0,>=1.17.0 (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.1.1)\n", + " Using cached yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (75 kB)\n", + "Collecting aiofiles<25.0,>=22.0 (from gradio>=4.0->llmrouter-lib==0.1.1)\n", + " Using cached aiofiles-24.1.0-py3-none-any.whl.metadata (10 kB)\n", + "Collecting audioop-lts<1.0 (from gradio>=4.0->llmrouter-lib==0.1.1)\n", + " Using cached audioop_lts-0.2.2-cp313-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (2.0 kB)\n", + "Requirement already satisfied: brotli>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (1.2.0)\n", + "Collecting fastapi<1.0,>=0.115.2 (from gradio>=4.0->llmrouter-lib==0.1.1)\n", + " Using cached fastapi-0.128.0-py3-none-any.whl.metadata (30 kB)\n", + "Collecting ffmpy (from gradio>=4.0->llmrouter-lib==0.1.1)\n", + " Using cached ffmpy-1.0.0-py3-none-any.whl.metadata (3.0 kB)\n", + "Collecting gradio-client==2.0.3 (from gradio>=4.0->llmrouter-lib==0.1.1)\n", + " Using cached gradio_client-2.0.3-py3-none-any.whl.metadata (7.1 kB)\n", + "Collecting groovy~=0.1 (from gradio>=4.0->llmrouter-lib==0.1.1)\n", + " Using cached groovy-0.1.2-py3-none-any.whl.metadata (6.1 kB)\n", + "Requirement already satisfied: jinja2<4.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (3.1.6)\n", + "Requirement already satisfied: markupsafe<4.0,>=2.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (3.0.3)\n", + "Collecting orjson~=3.0 (from gradio>=4.0->llmrouter-lib==0.1.1)\n", + " Using cached orjson-3.11.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (41 kB)\n", + "Collecting pillow<13.0,>=8.0 (from gradio>=4.0->llmrouter-lib==0.1.1)\n", + " Using cached pillow-12.1.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.metadata (8.8 kB)\n", + "Collecting pydub (from gradio>=4.0->llmrouter-lib==0.1.1)\n", + " Using cached pydub-0.25.1-py2.py3-none-any.whl.metadata (1.4 kB)\n", + "Collecting python-multipart>=0.0.18 (from gradio>=4.0->llmrouter-lib==0.1.1)\n", + " Using cached python_multipart-0.0.21-py3-none-any.whl.metadata (1.8 kB)\n", + "Collecting safehttpx<0.2.0,>=0.1.7 (from gradio>=4.0->llmrouter-lib==0.1.1)\n", + " Using cached safehttpx-0.1.7-py3-none-any.whl.metadata (4.2 kB)\n", + "Collecting semantic-version~=2.0 (from gradio>=4.0->llmrouter-lib==0.1.1)\n", + " Using cached semantic_version-2.10.0-py2.py3-none-any.whl.metadata (9.7 kB)\n", + "Collecting starlette<1.0,>=0.40.0 (from gradio>=4.0->llmrouter-lib==0.1.1)\n", + " Using cached starlette-0.51.0-py3-none-any.whl.metadata (6.3 kB)\n", + "Collecting tomlkit<0.14.0,>=0.12.0 (from gradio>=4.0->llmrouter-lib==0.1.1)\n", + " Using cached tomlkit-0.13.3-py3-none-any.whl.metadata (2.8 kB)\n", + "Collecting typer<1.0,>=0.12 (from gradio>=4.0->llmrouter-lib==0.1.1)\n", + " Using cached typer-0.21.1-py3-none-any.whl.metadata (16 kB)\n", + "Collecting uvicorn>=0.14.0 (from gradio>=4.0->llmrouter-lib==0.1.1)\n", + " Using cached uvicorn-0.40.0-py3-none-any.whl.metadata (6.7 kB)\n", + "Collecting starlette<1.0,>=0.40.0 (from gradio>=4.0->llmrouter-lib==0.1.1)\n", + " Using cached starlette-0.50.0-py3-none-any.whl.metadata (6.3 kB)\n", + "Collecting annotated-doc>=0.0.2 (from fastapi<1.0,>=0.115.2->gradio>=4.0->llmrouter-lib==0.1.1)\n", + " Using cached annotated_doc-0.0.4-py3-none-any.whl.metadata (6.6 kB)\n", + "Requirement already satisfied: python-dateutil>=2.8.2 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.1.1) (2.9.0.post0)\n", + "Requirement already satisfied: pytz>=2020.1 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.1.1) (2025.2)\n", + "Requirement already satisfied: tzdata>=2022.7 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.1.1) (2025.3)\n", + "Requirement already satisfied: annotated-types>=0.6.0 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.1.1) (0.7.0)\n", + "Requirement already satisfied: pydantic-core==2.41.5 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.1.1) (2.41.5)\n", + "Requirement already satisfied: typing-inspection>=0.4.2 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.1.1) (0.4.2)\n", + "Collecting click>=8.0.0 (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.1.1)\n", + " Using cached click-8.3.1-py3-none-any.whl.metadata (2.6 kB)\n", + "Collecting rich>=10.11.0 (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.1.1)\n", + " Using cached rich-14.2.0-py3-none-any.whl.metadata (18 kB)\n", + "Collecting fastuuid>=0.13.0 (from litellm>=1.0->llmrouter-lib==0.1.1)\n", + " Using cached fastuuid-0.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.1 kB)\n", + "Collecting grpcio!=1.68.*,!=1.69.*,!=1.70.*,!=1.71.0,!=1.71.1,!=1.72.0,!=1.72.1,!=1.73.0,>=1.62.3 (from litellm>=1.0->llmrouter-lib==0.1.1)\n", + " Using cached grpcio-1.76.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (3.7 kB)\n", + "Requirement already satisfied: importlib-metadata>=6.8.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.1.1) (8.7.0)\n", + "Requirement already satisfied: jsonschema<5.0.0,>=4.23.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.1.1) (4.25.1)\n", + "Collecting openai>=2.8.0 (from litellm>=1.0->llmrouter-lib==0.1.1)\n", + " Using cached openai-2.15.0-py3-none-any.whl.metadata (29 kB)\n", + "Collecting python-dotenv>=0.2.0 (from litellm>=1.0->llmrouter-lib==0.1.1)\n", + " Using cached python_dotenv-1.2.1-py3-none-any.whl.metadata (25 kB)\n", + "Collecting tiktoken>=0.7.0 (from litellm>=1.0->llmrouter-lib==0.1.1)\n", + " Using cached tiktoken-0.12.0-cp313-cp313-manylinux_2_28_x86_64.whl.metadata (6.7 kB)\n", + "Collecting tokenizers (from litellm>=1.0->llmrouter-lib==0.1.1)\n", + " Using cached tokenizers-0.22.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (7.3 kB)\n", + "Requirement already satisfied: jsonschema-specifications>=2023.03.6 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.1.1) (2025.9.1)\n", + "Requirement already satisfied: referencing>=0.28.4 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.1.1) (0.37.0)\n", + "Requirement already satisfied: rpds-py>=0.7.1 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.1.1) (0.30.0)\n", + "Requirement already satisfied: zipp>=3.20 in /opt/conda/lib/python3.13/site-packages (from importlib-metadata>=6.8.0->litellm>=1.0->llmrouter-lib==0.1.1) (3.23.0)\n", + "Requirement already satisfied: distro<2,>=1.7.0 in /opt/conda/lib/python3.13/site-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.1.1) (1.9.0)\n", + "Collecting jiter<1,>=0.10.0 (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.1.1)\n", + " Using cached jiter-0.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (5.2 kB)\n", + "Requirement already satisfied: sniffio in /opt/conda/lib/python3.13/site-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.1.1) (1.3.1)\n", + "Requirement already satisfied: psutil in /opt/conda/lib/python3.13/site-packages (from peft>=0.7->llmrouter-lib==0.1.1) (7.2.1)\n", + "Collecting accelerate>=0.21.0 (from peft>=0.7->llmrouter-lib==0.1.1)\n", + " Using cached accelerate-1.12.0-py3-none-any.whl.metadata (19 kB)\n", + "Collecting safetensors (from peft>=0.7->llmrouter-lib==0.1.1)\n", + " Using cached safetensors-0.7.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (4.1 kB)\n", + "Requirement already satisfied: six>=1.5 in /opt/conda/lib/python3.13/site-packages (from python-dateutil>=2.8.2->pandas>=1.5->llmrouter-lib==0.1.1) (1.17.0)\n", + "Requirement already satisfied: charset_normalizer<4,>=2 in /opt/conda/lib/python3.13/site-packages (from requests>=2.32.2->datasets>=2.14->llmrouter-lib==0.1.1) (3.4.4)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/conda/lib/python3.13/site-packages (from requests>=2.32.2->datasets>=2.14->llmrouter-lib==0.1.1) (2.6.2)\n", + "Collecting markdown-it-py>=2.2.0 (from rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.1.1)\n", + " Using cached markdown_it_py-4.0.0-py3-none-any.whl.metadata (7.3 kB)\n", + "Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /opt/conda/lib/python3.13/site-packages (from rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.1.1) (2.19.2)\n", + "Collecting mdurl~=0.1 (from markdown-it-py>=2.2.0->rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.1.1)\n", + " Using cached mdurl-0.1.2-py3-none-any.whl.metadata (1.6 kB)\n", + "Collecting joblib>=1.3.0 (from scikit-learn>=1.2->llmrouter-lib==0.1.1)\n", + " Using cached joblib-1.5.3-py3-none-any.whl.metadata (5.5 kB)\n", + "Collecting threadpoolctl>=3.2.0 (from scikit-learn>=1.2->llmrouter-lib==0.1.1)\n", + " Using cached threadpoolctl-3.6.0-py3-none-any.whl.metadata (13 kB)\n", + "Collecting regex>=2022.1.18 (from tiktoken>=0.7.0->litellm>=1.0->llmrouter-lib==0.1.1)\n", + " Using cached regex-2025.11.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (40 kB)\n", + "Requirement already satisfied: setuptools in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (80.9.0)\n", + "Collecting sympy>=1.13.3 (from torch>=2.0->llmrouter-lib==0.1.1)\n", + " Using cached sympy-1.14.0-py3-none-any.whl.metadata (12 kB)\n", + "Collecting networkx>=2.5.1 (from torch>=2.0->llmrouter-lib==0.1.1)\n", + " Using cached networkx-3.6.1-py3-none-any.whl.metadata (6.8 kB)\n", + "Collecting nvidia-cuda-nvrtc-cu12==12.8.93 (from torch>=2.0->llmrouter-lib==0.1.1)\n", + " Using cached nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl.metadata (1.7 kB)\n", + "Collecting nvidia-cuda-runtime-cu12==12.8.90 (from torch>=2.0->llmrouter-lib==0.1.1)\n", + " Using cached nvidia_cuda_runtime_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (1.7 kB)\n", + "Collecting nvidia-cuda-cupti-cu12==12.8.90 (from torch>=2.0->llmrouter-lib==0.1.1)\n", + " Using cached nvidia_cuda_cupti_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (1.7 kB)\n", + "Collecting nvidia-cudnn-cu12==9.10.2.21 (from torch>=2.0->llmrouter-lib==0.1.1)\n", + " Using cached nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl.metadata (1.8 kB)\n", + "Collecting nvidia-cublas-cu12==12.8.4.1 (from torch>=2.0->llmrouter-lib==0.1.1)\n", + " Using cached nvidia_cublas_cu12-12.8.4.1-py3-none-manylinux_2_27_x86_64.whl.metadata (1.7 kB)\n", + "Collecting nvidia-cufft-cu12==11.3.3.83 (from torch>=2.0->llmrouter-lib==0.1.1)\n", + " Using cached nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (1.7 kB)\n", + "Collecting nvidia-curand-cu12==10.3.9.90 (from torch>=2.0->llmrouter-lib==0.1.1)\n", + " Using cached nvidia_curand_cu12-10.3.9.90-py3-none-manylinux_2_27_x86_64.whl.metadata (1.7 kB)\n", + "Collecting nvidia-cusolver-cu12==11.7.3.90 (from torch>=2.0->llmrouter-lib==0.1.1)\n", + " Using cached nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl.metadata (1.8 kB)\n", + "Collecting nvidia-cusparse-cu12==12.5.8.93 (from torch>=2.0->llmrouter-lib==0.1.1)\n", + " Using cached nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (1.8 kB)\n", + "Collecting nvidia-cusparselt-cu12==0.7.1 (from torch>=2.0->llmrouter-lib==0.1.1)\n", + " Using cached nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_x86_64.whl.metadata (7.0 kB)\n", + "Collecting nvidia-nccl-cu12==2.27.5 (from torch>=2.0->llmrouter-lib==0.1.1)\n", + " Using cached nvidia_nccl_cu12-2.27.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (2.0 kB)\n", + "Collecting nvidia-nvshmem-cu12==3.3.20 (from torch>=2.0->llmrouter-lib==0.1.1)\n", + " Using cached nvidia_nvshmem_cu12-3.3.20-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (2.1 kB)\n", + "Collecting nvidia-nvtx-cu12==12.8.90 (from torch>=2.0->llmrouter-lib==0.1.1)\n", + " Using cached nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (1.8 kB)\n", + "Collecting nvidia-nvjitlink-cu12==12.8.93 (from torch>=2.0->llmrouter-lib==0.1.1)\n", + " Using cached nvidia_nvjitlink_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl.metadata (1.7 kB)\n", + "Collecting nvidia-cufile-cu12==1.13.1.3 (from torch>=2.0->llmrouter-lib==0.1.1)\n", + " Using cached nvidia_cufile_cu12-1.13.1.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (1.7 kB)\n", + "Collecting triton==3.5.1 (from torch>=2.0->llmrouter-lib==0.1.1)\n", + " Using cached triton-3.5.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.metadata (1.7 kB)\n", + "Collecting mpmath<1.4,>=1.1.0 (from sympy>=1.13.3->torch>=2.0->llmrouter-lib==0.1.1)\n", + " Using cached mpmath-1.3.0-py3-none-any.whl.metadata (8.6 kB)\n", + "Collecting pyparsing (from torch-geometric>=2.3->llmrouter-lib==0.1.1)\n", + " Using cached pyparsing-3.3.1-py3-none-any.whl.metadata (5.6 kB)\n", + "Collecting huggingface-hub<2.0,>=0.25.0 (from datasets>=2.14->llmrouter-lib==0.1.1)\n", + " Using cached huggingface_hub-0.36.0-py3-none-any.whl.metadata (14 kB)\n", + "Using cached datasets-4.4.2-py3-none-any.whl (512 kB)\n", + "Using cached dill-0.4.0-py3-none-any.whl (119 kB)\n", + "Using cached fsspec-2025.10.0-py3-none-any.whl (200 kB)\n", + "Using cached hf_xet-1.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.3 MB)\n", + "Using cached multiprocess-0.70.18-py313-none-any.whl (151 kB)\n", + "Using cached aiohttp-3.13.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (1.7 MB)\n", + "Using cached multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (254 kB)\n", + "Using cached yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (377 kB)\n", + "Using cached aiohappyeyeballs-2.6.1-py3-none-any.whl (15 kB)\n", + "Using cached aiosignal-1.4.0-py3-none-any.whl (7.5 kB)\n", + "Using cached frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (234 kB)\n", + "Using cached gradio-6.3.0-py3-none-any.whl (23.0 MB)\n", + "Using cached gradio_client-2.0.3-py3-none-any.whl (55 kB)\n", + "Using cached aiofiles-24.1.0-py3-none-any.whl (15 kB)\n", + "Using cached audioop_lts-0.2.2-cp313-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (85 kB)\n", + "Using cached fastapi-0.128.0-py3-none-any.whl (103 kB)\n", + "Using cached groovy-0.1.2-py3-none-any.whl (14 kB)\n", + "Using cached numpy-2.4.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (16.4 MB)\n", + "Using cached orjson-3.11.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (138 kB)\n", + "Using cached pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (12.3 MB)\n", + "Using cached pillow-12.1.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (7.0 MB)\n", + "Using cached safehttpx-0.1.7-py3-none-any.whl (9.0 kB)\n", + "Using cached semantic_version-2.10.0-py2.py3-none-any.whl (15 kB)\n", + "Using cached starlette-0.50.0-py3-none-any.whl (74 kB)\n", + "Using cached tomlkit-0.13.3-py3-none-any.whl (38 kB)\n", + "Using cached typer-0.21.1-py3-none-any.whl (47 kB)\n", + "Using cached annotated_doc-0.0.4-py3-none-any.whl (5.3 kB)\n", + "Using cached click-8.3.1-py3-none-any.whl (108 kB)\n", + "Using cached litellm-1.80.13-py3-none-any.whl (11.6 MB)\n", + "Using cached fastuuid-0.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (278 kB)\n", + "Using cached grpcio-1.76.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (6.6 MB)\n", + "Using cached openai-2.15.0-py3-none-any.whl (1.1 MB)\n", + "Using cached jiter-0.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (361 kB)\n", + "Using cached peft-0.18.1-py3-none-any.whl (556 kB)\n", + "Using cached accelerate-1.12.0-py3-none-any.whl (380 kB)\n", + "Using cached propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (204 kB)\n", + "Using cached protobuf-6.33.3-cp39-abi3-manylinux2014_x86_64.whl (323 kB)\n", + "Using cached pyarrow-22.0.0-cp313-cp313-manylinux_2_28_x86_64.whl (47.7 MB)\n", + "Using cached python_dotenv-1.2.1-py3-none-any.whl (21 kB)\n", + "Using cached python_multipart-0.0.21-py3-none-any.whl (24 kB)\n", + "Using cached rich-14.2.0-py3-none-any.whl (243 kB)\n", + "Using cached markdown_it_py-4.0.0-py3-none-any.whl (87 kB)\n", + "Using cached mdurl-0.1.2-py3-none-any.whl (10.0 kB)\n", + "Using cached safetensors-0.7.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (507 kB)\n", + "Using cached scikit_learn-1.8.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (8.9 MB)\n", + "Using cached joblib-1.5.3-py3-none-any.whl (309 kB)\n", + "Using cached scipy-1.17.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (35.0 MB)\n", + "Using cached sentencepiece-0.2.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (1.4 MB)\n", + "Using cached shellingham-1.5.4-py2.py3-none-any.whl (9.8 kB)\n", + "Using cached threadpoolctl-3.6.0-py3-none-any.whl (18 kB)\n", + "Using cached tiktoken-0.12.0-cp313-cp313-manylinux_2_28_x86_64.whl (1.2 MB)\n", + "Using cached regex-2025.11.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (803 kB)\n", + "Using cached torch-2.9.1-cp313-cp313-manylinux_2_28_x86_64.whl (899.7 MB)\n", + "Using cached nvidia_cublas_cu12-12.8.4.1-py3-none-manylinux_2_27_x86_64.whl (594.3 MB)\n", + "Using cached nvidia_cuda_cupti_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (10.2 MB)\n", + "Using cached nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl (88.0 MB)\n", + "Using cached nvidia_cuda_runtime_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (954 kB)\n", + "Using cached nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl (706.8 MB)\n", + "Using cached nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (193.1 MB)\n", + "Using cached nvidia_cufile_cu12-1.13.1.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (1.2 MB)\n", + "Using cached nvidia_curand_cu12-10.3.9.90-py3-none-manylinux_2_27_x86_64.whl (63.6 MB)\n", + "Using cached nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl (267.5 MB)\n", + "Using cached nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (288.2 MB)\n", + "Using cached nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_x86_64.whl (287.2 MB)\n", + "Using cached nvidia_nccl_cu12-2.27.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (322.3 MB)\n", + "Using cached nvidia_nvjitlink_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl (39.3 MB)\n", + "Using cached nvidia_nvshmem_cu12-3.3.20-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (124.7 MB)\n", + "Using cached nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (89 kB)\n", + "Using cached triton-3.5.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (170.5 MB)\n", + "Using cached networkx-3.6.1-py3-none-any.whl (2.1 MB)\n", + "Using cached sympy-1.14.0-py3-none-any.whl (6.3 MB)\n", + "Using cached mpmath-1.3.0-py3-none-any.whl (536 kB)\n", + "Using cached torch_geometric-2.7.0-py3-none-any.whl (1.3 MB)\n", + "Using cached transformers-4.57.3-py3-none-any.whl (12.0 MB)\n", + "Using cached huggingface_hub-0.36.0-py3-none-any.whl (566 kB)\n", + "Using cached tokenizers-0.22.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.3 MB)\n", + "Using cached uvicorn-0.40.0-py3-none-any.whl (68 kB)\n", + "Using cached ffmpy-1.0.0-py3-none-any.whl (5.6 kB)\n", + "Using cached filelock-3.20.3-py3-none-any.whl (16 kB)\n", + "Using cached pydub-0.25.1-py2.py3-none-any.whl (32 kB)\n", + "Using cached pyparsing-3.3.1-py3-none-any.whl (121 kB)\n", + "Using cached xxhash-3.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (193 kB)\n", + "Building wheels for collected packages: llmrouter-lib\n", + " Building editable for llmrouter-lib (pyproject.toml) ... \u001b[?25ldone\n", + "\u001b[?25h Created wheel for llmrouter-lib: filename=llmrouter_lib-0.1.1-0.editable-py3-none-any.whl size=14451 sha256=f01d43d7244aca2d1267c780eb24e587f7a8e7f5c11a2a2b57a565152f948093\n", + " Stored in directory: /tmp/pip-ephem-wheel-cache-k1j0u9t8/wheels/82/4a/fd/59c4aec93c356c380d86a88ab74bece164c0aa855d68b155e4\n", + "Successfully built llmrouter-lib\n", + "Installing collected packages: pydub, nvidia-cusparselt-cu12, mpmath, xxhash, triton, tomlkit, threadpoolctl, sympy, shellingham, sentencepiece, semantic-version, safetensors, regex, python-multipart, python-dotenv, pyparsing, pyarrow, protobuf, propcache, pillow, orjson, nvidia-nvtx-cu12, nvidia-nvshmem-cu12, nvidia-nvjitlink-cu12, nvidia-nccl-cu12, nvidia-curand-cu12, nvidia-cufile-cu12, nvidia-cuda-runtime-cu12, nvidia-cuda-nvrtc-cu12, nvidia-cuda-cupti-cu12, nvidia-cublas-cu12, numpy, networkx, multidict, mdurl, joblib, jiter, hf-xet, grpcio, groovy, fsspec, frozenlist, filelock, ffmpy, fastuuid, dill, click, audioop-lts, annotated-doc, aiohappyeyeballs, aiofiles, yarl, uvicorn, tiktoken, starlette, scipy, pandas, nvidia-cusparse-cu12, nvidia-cufft-cu12, nvidia-cudnn-cu12, multiprocess, markdown-it-py, huggingface-hub, aiosignal, tokenizers, scikit-learn, safehttpx, rich, openai, nvidia-cusolver-cu12, gradio-client, fastapi, aiohttp, typer, transformers, torch-geometric, torch, litellm, gradio, datasets, accelerate, peft, llmrouter-lib\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m83/83\u001b[0m [llmrouter-lib]0m [accelerate]tric]cu12]2]\n", + "\u001b[1A\u001b[2KSuccessfully installed accelerate-1.12.0 aiofiles-24.1.0 aiohappyeyeballs-2.6.1 aiohttp-3.13.3 aiosignal-1.4.0 annotated-doc-0.0.4 audioop-lts-0.2.2 click-8.3.1 datasets-4.4.2 dill-0.4.0 fastapi-0.128.0 fastuuid-0.14.0 ffmpy-1.0.0 filelock-3.20.3 frozenlist-1.8.0 fsspec-2025.10.0 gradio-6.3.0 gradio-client-2.0.3 groovy-0.1.2 grpcio-1.76.0 hf-xet-1.2.0 huggingface-hub-0.36.0 jiter-0.12.0 joblib-1.5.3 litellm-1.80.13 llmrouter-lib-0.1.1 markdown-it-py-4.0.0 mdurl-0.1.2 mpmath-1.3.0 multidict-6.7.0 multiprocess-0.70.18 networkx-3.6.1 numpy-2.4.1 nvidia-cublas-cu12-12.8.4.1 nvidia-cuda-cupti-cu12-12.8.90 nvidia-cuda-nvrtc-cu12-12.8.93 nvidia-cuda-runtime-cu12-12.8.90 nvidia-cudnn-cu12-9.10.2.21 nvidia-cufft-cu12-11.3.3.83 nvidia-cufile-cu12-1.13.1.3 nvidia-curand-cu12-10.3.9.90 nvidia-cusolver-cu12-11.7.3.90 nvidia-cusparse-cu12-12.5.8.93 nvidia-cusparselt-cu12-0.7.1 nvidia-nccl-cu12-2.27.5 nvidia-nvjitlink-cu12-12.8.93 nvidia-nvshmem-cu12-3.3.20 nvidia-nvtx-cu12-12.8.90 openai-2.15.0 orjson-3.11.5 pandas-2.3.3 peft-0.18.1 pillow-12.1.0 propcache-0.4.1 protobuf-6.33.3 pyarrow-22.0.0 pydub-0.25.1 pyparsing-3.3.1 python-dotenv-1.2.1 python-multipart-0.0.21 regex-2025.11.3 rich-14.2.0 safehttpx-0.1.7 safetensors-0.7.0 scikit-learn-1.8.0 scipy-1.17.0 semantic-version-2.10.0 sentencepiece-0.2.1 shellingham-1.5.4 starlette-0.50.0 sympy-1.14.0 threadpoolctl-3.6.0 tiktoken-0.12.0 tokenizers-0.22.2 tomlkit-0.13.3 torch-2.9.1 torch-geometric-2.7.0 transformers-4.57.3 triton-3.5.1 typer-0.21.1 uvicorn-0.40.0 xxhash-3.6.0 yarl-1.22.0\n", + "Requirement already satisfied: transformers in /opt/conda/lib/python3.13/site-packages (4.57.3)\n", + "Requirement already satisfied: torch in /opt/conda/lib/python3.13/site-packages (2.9.1)\n", + "Requirement already satisfied: peft in /opt/conda/lib/python3.13/site-packages (0.18.1)\n", + "Requirement already satisfied: accelerate in /opt/conda/lib/python3.13/site-packages (1.12.0)\n", + "Collecting bitsandbytes\n", + " Using cached bitsandbytes-0.49.1-py3-none-manylinux_2_24_x86_64.whl.metadata (10 kB)\n", + "Requirement already satisfied: filelock in /opt/conda/lib/python3.13/site-packages (from transformers) (3.20.3)\n", + "Requirement already satisfied: huggingface-hub<1.0,>=0.34.0 in /opt/conda/lib/python3.13/site-packages (from transformers) (0.36.0)\n", + "Requirement already satisfied: numpy>=1.17 in /opt/conda/lib/python3.13/site-packages (from transformers) (2.4.1)\n", + "Requirement already satisfied: packaging>=20.0 in /opt/conda/lib/python3.13/site-packages (from transformers) (25.0)\n", + "Requirement already satisfied: pyyaml>=5.1 in /opt/conda/lib/python3.13/site-packages (from transformers) (6.0.3)\n", + "Requirement already satisfied: regex!=2019.12.17 in /opt/conda/lib/python3.13/site-packages (from transformers) (2025.11.3)\n", + "Requirement already satisfied: requests in /opt/conda/lib/python3.13/site-packages (from transformers) (2.32.5)\n", + "Requirement already satisfied: tokenizers<=0.23.0,>=0.22.0 in /opt/conda/lib/python3.13/site-packages (from transformers) (0.22.2)\n", + "Requirement already satisfied: safetensors>=0.4.3 in /opt/conda/lib/python3.13/site-packages (from transformers) (0.7.0)\n", + "Requirement already satisfied: tqdm>=4.27 in /opt/conda/lib/python3.13/site-packages (from transformers) (4.67.1)\n", + "Requirement already satisfied: fsspec>=2023.5.0 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (2025.10.0)\n", + "Requirement already satisfied: typing-extensions>=3.7.4.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (4.15.0)\n", + "Requirement already satisfied: hf-xet<2.0.0,>=1.1.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (1.2.0)\n", + "Requirement already satisfied: setuptools in /opt/conda/lib/python3.13/site-packages (from torch) (80.9.0)\n", + "Requirement already satisfied: sympy>=1.13.3 in /opt/conda/lib/python3.13/site-packages (from torch) (1.14.0)\n", + "Requirement already satisfied: networkx>=2.5.1 in /opt/conda/lib/python3.13/site-packages (from torch) (3.6.1)\n", + "Requirement already satisfied: jinja2 in /opt/conda/lib/python3.13/site-packages (from torch) (3.1.6)\n", + "Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.93)\n", + "Requirement already satisfied: nvidia-cuda-runtime-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-cuda-cupti-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-cudnn-cu12==9.10.2.21 in /opt/conda/lib/python3.13/site-packages (from torch) (9.10.2.21)\n", + "Requirement already satisfied: nvidia-cublas-cu12==12.8.4.1 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.4.1)\n", + "Requirement already satisfied: nvidia-cufft-cu12==11.3.3.83 in /opt/conda/lib/python3.13/site-packages (from torch) (11.3.3.83)\n", + "Requirement already satisfied: nvidia-curand-cu12==10.3.9.90 in /opt/conda/lib/python3.13/site-packages (from torch) (10.3.9.90)\n", + "Requirement already satisfied: nvidia-cusolver-cu12==11.7.3.90 in /opt/conda/lib/python3.13/site-packages (from torch) (11.7.3.90)\n", + "Requirement already satisfied: nvidia-cusparse-cu12==12.5.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.5.8.93)\n", + "Requirement already satisfied: nvidia-cusparselt-cu12==0.7.1 in /opt/conda/lib/python3.13/site-packages (from torch) (0.7.1)\n", + "Requirement already satisfied: nvidia-nccl-cu12==2.27.5 in /opt/conda/lib/python3.13/site-packages (from torch) (2.27.5)\n", + "Requirement already satisfied: nvidia-nvshmem-cu12==3.3.20 in /opt/conda/lib/python3.13/site-packages (from torch) (3.3.20)\n", + "Requirement already satisfied: nvidia-nvtx-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-nvjitlink-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.93)\n", + "Requirement already satisfied: nvidia-cufile-cu12==1.13.1.3 in /opt/conda/lib/python3.13/site-packages (from torch) (1.13.1.3)\n", + "Requirement already satisfied: triton==3.5.1 in /opt/conda/lib/python3.13/site-packages (from torch) (3.5.1)\n", + "Requirement already satisfied: psutil in /opt/conda/lib/python3.13/site-packages (from peft) (7.2.1)\n", + "Requirement already satisfied: mpmath<1.4,>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from sympy>=1.13.3->torch) (1.3.0)\n", + "Requirement already satisfied: MarkupSafe>=2.0 in /opt/conda/lib/python3.13/site-packages (from jinja2->torch) (3.0.3)\n", + "Requirement already satisfied: charset_normalizer<4,>=2 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (3.4.4)\n", + "Requirement already satisfied: idna<4,>=2.5 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (3.11)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (2.6.2)\n", + "Requirement already satisfied: certifi>=2017.4.17 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (2026.1.4)\n", + "Using cached bitsandbytes-0.49.1-py3-none-manylinux_2_24_x86_64.whl (59.1 MB)\n", + "Installing collected packages: bitsandbytes\n", + "Successfully installed bitsandbytes-0.49.1\n" + ] + } + ], + "source": [ + "!git clone https://github.com/ulab-uiuc/LLMRouter.git\n", + "%cd LLMRouter\n", + "!pip install -e .\n", + "!pip install transformers torch peft accelerate bitsandbytes" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from huggingface_hub import login\n", + "os.environ['OPENAI_API_KEY'] = 'your-key'\n", + "os.environ['ANTHROPIC_API_KEY'] = 'your-key'\n", + "# Or for multiple keys:\n", + "os.environ['API_KEYS'] = '[\"key1\", \"key2\"]'\n", + "# HuggingFace login (required for Llama-2 access)\n", + "login(token=\"your_hf_token\")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using device: cuda\n", + "GPU: NVIDIA A100-SXM4-80GB\n", + "Memory: 85.10 GB\n" + ] + } + ], + "source": [ + "import torch\n", + "from llmrouter.models.causallm_router import CausalLMRouter, CausalLMTrainer\n", + "from llmrouter.utils import setup_environment\n", + "\n", + "setup_environment()\n", + "\n", + "device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n", + "print(f\"Using device: {device}\")\n", + "if device == \"cuda\":\n", + " print(f\"GPU: {torch.cuda.get_device_name(0)}\")\n", + " print(f\"Memory: {torch.cuda.get_device_properties(0).total_memory / 1e9:.2f} GB\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Configuration\n", + "\n", + "CausalLMRouter uses the following configuration parameters:\n", + "\n", + "| Parameter | Description | Default |\n", + "|-----------|-------------|--------|\n", + "| `base_model` | Base LLM for finetuning | \"meta-llama/Llama-2-7b-hf\" |\n", + "| `use_lora` | Enable LoRA finetuning | true |\n", + "| `lora_r` | LoRA rank | 16 |\n", + "| `lora_alpha` | LoRA alpha | 32 |\n", + "| `lora_dropout` | LoRA dropout | 0.1 |\n", + "| `num_epochs` | Training epochs | 3 |\n", + "| `batch_size` | Batch size | 4 |\n", + "| `learning_rate` | Learning rate | 2e-5 |" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Current Configuration:\n", + "==================================================\n", + "data_path:\n", + " llm_data: data/example_data/llm_candidates/default_llm.json\n", + " llm_embedding_data: data/example_data/llm_candidates/default_llm_embeddings.json\n", + " query_data_test: data/example_data/query_data/default_query_test.jsonl\n", + " query_data_train: data/example_data/query_data/default_query_train.jsonl\n", + " query_embedding_data: data/example_data/routing_data/query_embeddings_longformer.pt\n", + " routing_data_test: data/example_data/routing_data/default_routing_test_data.jsonl\n", + " routing_data_train: data/example_data/routing_data/default_routing_train_data.jsonl\n", + "hparam:\n", + " base_model: meta-llama/Llama-2-7b-hf\n", + " batch_size: 1\n", + " fp16: true\n", + " gpu_memory_utilization: 0.9\n", + " gradient_accumulation_steps: 4\n", + " learning_rate: 2.0e-05\n", + " logging_steps: 10\n", + " lora_alpha: 16\n", + " lora_dropout: 0.1\n", + " lora_r: 8\n", + " lora_target_modules:\n", + " - q_proj\n", + " - k_proj\n", + " - v_proj\n", + " - o_proj\n", + " max_length: 256\n", + " max_new_tokens: 32\n", + " merge_lora: true\n", + " num_epochs: 3\n", + " report_to: none\n", + " save_steps: 100\n", + " temperature: 0.1\n", + " tensor_parallel_size: 1\n", + " top_p: 0.95\n", + " use_lora: true\n", + " warmup_ratio: 0.1\n", + " weight_decay: 0.01\n", + "metric:\n", + " weights:\n", + " cost: 0\n", + " llm_judge: 0\n", + " performance: 1\n", + "model_path:\n", + " ini_model_path: ''\n", + " load_model_path: saved_models/causallm_router/merged\n", + " save_model_path: saved_models/causallm_router\n", + "\n" + ] + } + ], + "source": [ + "import yaml\n", + "\n", + "CONFIG_PATH = \"configs/model_config_train/causallm_router.yaml\"\n", + "\n", + "with open(CONFIG_PATH, 'r') as f:\n", + " config = yaml.safe_load(f)\n", + "\n", + "print(\"Current Configuration:\")\n", + "print(\"=\" * 50)\n", + "print(yaml.dump(config, default_flow_style=False))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Initialize Router" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "✅ MetaRouter initialized successfully (YAML + data loaded).\n", + "Router initialized successfully!\n", + "Number of training samples: 50544\n", + "Number of LLM candidates: 14\n", + "LLM candidates: ['qwen2.5-7b-instruct', 'codegemma-7b', 'gemma-2-9b-it', 'llama-3.1-8b-instruct', 'llama3-chatqa-1.5-8b', 'mistral-7b-instruct-v0.3', 'llama-3.3-nemotron-super-49b-v1', 'llama-3.1-nemotron-51b-instruct', 'llama3-chatqa-1.5-70b', 'llama3-70b-instruct', 'mixtral-8x7b-instruct-v0.1', 'mixtral-8x22b-instruct-v0.1', 'palmyra-creative-122b', 'mistral-nemo-12b-instruct']\n", + "Base model: meta-llama/Llama-2-7b-hf\n" + ] + } + ], + "source": [ + "router = CausalLMRouter(yaml_path=CONFIG_PATH)\n", + "\n", + "print(\"Router initialized successfully!\")\n", + "print(f\"Number of training samples: {len(router.routing_data_train)}\")\n", + "print(f\"Number of LLM candidates: {len(router.llm_data)}\")\n", + "print(f\"LLM candidates: {list(router.llm_data.keys())}\")\n", + "print(f\"Base model: {config['hparam']['base_model']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Training Data Preparation" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Training Data Format:\n", + "==================================================\n", + "\n", + "The model is trained to predict the best LLM given a query.\n", + "\n", + "Input format:\n", + " Query: {user query}\n", + " Best model: \n", + "\n", + "Target format:\n", + " {best_model_name}\n" + ] + } + ], + "source": [ + "# Understand the training data format\n", + "print(\"Training Data Format:\")\n", + "print(\"=\" * 50)\n", + "print(\"\\nThe model is trained to predict the best LLM given a query.\")\n", + "print(\"\\nInput format:\")\n", + "print(\" Query: {user query}\")\n", + "print(\" Best model: \")\n", + "print(\"\\nTarget format:\")\n", + "print(\" {best_model_name}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Training" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "`torch_dtype` is deprecated! Use `dtype` instead!\n", + "Loading checkpoint shards: 100%|██████████| 2/2 [00:47<00:00, 23.55s/it]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "trainable params: 8,388,608 || all params: 6,746,804,224 || trainable%: 0.1243\n", + "[CausalLMTrainer] Initialized with base model: meta-llama/Llama-2-7b-hf\n", + "Trainer initialized!\n", + "Device: cuda\n", + "Save path: /home/zhongjie/LLMRouter/llmrouter/saved_models/causallm_router\n", + "LoRA enabled: True\n" + ] + } + ], + "source": [ + "trainer = CausalLMTrainer(router=router, device=device)\n", + "\n", + "print(\"Trainer initialized!\")\n", + "print(f\"Device: {device}\")\n", + "print(f\"Save path: {trainer.save_model_path}\")\n", + "print(f\"LoRA enabled: {config['hparam'].get('use_lora', True)}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Total parameters: 6,746,804,224\n", + "Trainable parameters: 8,388,608\n", + "Trainable %: 0.12%\n" + ] + } + ], + "source": [ + "# Show trainable parameters\n", + "if hasattr(trainer, 'model'):\n", + " total_params = sum(p.numel() for p in trainer.model.parameters())\n", + " trainable_params = sum(p.numel() for p in trainer.model.parameters() if p.requires_grad)\n", + " print(f\"\\nTotal parameters: {total_params:,}\")\n", + " print(f\"Trainable parameters: {trainable_params:,}\")\n", + " print(f\"Trainable %: {100 * trainable_params / total_params:.2f}%\")" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting training...\n", + "==================================================\n", + "Note: CausalLM training requires significant GPU memory.\n", + "Consider reducing batch_size if you encounter OOM errors.\n", + "==================================================\n" + ] + }, + { + "ename": "NameError", + "evalue": "name 'trainer' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mNameError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[1]\u001b[39m\u001b[32m, line 7\u001b[39m\n\u001b[32m 4\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33m\"\u001b[39m\u001b[33mConsider reducing batch_size if you encounter OOM errors.\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m 5\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33m\"\u001b[39m\u001b[33m=\u001b[39m\u001b[33m\"\u001b[39m * \u001b[32m50\u001b[39m)\n\u001b[32m----> \u001b[39m\u001b[32m7\u001b[39m \u001b[43mtrainer\u001b[49m.train()\n\u001b[32m 9\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33m\"\u001b[39m\u001b[33m=\u001b[39m\u001b[33m\"\u001b[39m * \u001b[32m50\u001b[39m)\n\u001b[32m 10\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33m\"\u001b[39m\u001b[33mTraining completed!\u001b[39m\u001b[33m\"\u001b[39m)\n", + "\u001b[31mNameError\u001b[39m: name 'trainer' is not defined" + ] + } + ], + "source": [ + "print(\"Starting training...\")\n", + "print(\"=\" * 50)\n", + "print(\"Note: CausalLM training requires significant GPU memory.\")\n", + "print(\"Consider reducing batch_size if you encounter OOM errors.\")\n", + "print(\"=\" * 50)\n", + "\n", + "trainer.train()\n", + "\n", + "print(\"=\" * 50)\n", + "print(\"Training completed!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. Model Verification" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Check saved model\n", + "import os\n", + "\n", + "save_path = trainer.save_model_path\n", + "if os.path.exists(save_path):\n", + " print(f\"Model saved at: {save_path}\")\n", + " \n", + " # List saved files\n", + " if os.path.isdir(save_path):\n", + " files = os.listdir(save_path)\n", + " print(f\"\\nSaved files:\")\n", + " for f in files:\n", + " size = os.path.getsize(os.path.join(save_path, f)) / 1e6\n", + " print(f\" {f}: {size:.2f} MB\")\n", + "else:\n", + " print(f\"Model not found at: {save_path}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Test prediction\n", + "test_query = {\"query\": \"What is the capital of France?\"}\n", + "result = router.route_single(test_query)\n", + "\n", + "print(f\"Test query: {test_query['query']}\")\n", + "print(f\"Routed to: {result['model_name']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "In this notebook, we:\n", + "\n", + "1. **Loaded Configuration**: Set up CausalLMRouter with YAML configuration\n", + "2. **Initialized Router**: Created router with Llama-2 backbone\n", + "3. **Applied LoRA**: Efficient finetuning with low-rank adaptation\n", + "4. **Trained Model**: Finetuned to predict best LLM for queries\n", + "5. **Saved Model**: LoRA weights and merged model saved\n", + "\n", + "**Key Takeaways**:\n", + "- CausalLMRouter uses powerful LLM understanding\n", + "- LoRA enables efficient finetuning (only ~0.1% params trainable)\n", + "- Requires GPU with sufficient memory\n", + "\n", + "**Next Steps**:\n", + "- Use next part of notebook for inference\n", + "- Consider using vLLM for faster inference" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# CausalLMRouter - Inference\n", + "\n", + "This part of notebook demonstrates how to use a trained **CausalLMRouter** for inference." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Environment Setup (optional)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Install required packages (for Colab)\n", + "!pip install llmrouter-lib transformers torch peft accelerate bitsandbytes\n", + "!git clone https://github.com/ulab-uiuc/LLMRouter.git\n", + "%cd LLMRouter" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "os.environ['OPENAI_API_KEY'] = 'your-key'\n", + "os.environ['ANTHROPIC_API_KEY'] = 'your-key'\n", + "# Or for multiple keys:\n", + "os.environ['API_KEYS'] = '[\"key1\", \"key2\"]'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import torch\n", + "from llmrouter.models.causallm_router import CausalLMRouter\n", + "from llmrouter.utils import setup_environment\n", + "import yaml\n", + "\n", + "setup_environment()\n", + "\n", + "device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n", + "print(f\"Using device: {device}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Load Trained Router" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "CONFIG_PATH = \"configs/model_config_train/causallm_router.yaml\"\n", + "\n", + "with open(CONFIG_PATH, 'r') as f:\n", + " config = yaml.safe_load(f)\n", + "\n", + "# Use merged model for inference\n", + "config['model_path']['load_model_path'] = config['model_path'].get(\n", + " 'load_model_path', \n", + " os.path.join(config['model_path']['save_model_path'], 'merged')\n", + ")\n", + "\n", + "router = CausalLMRouter(yaml_path=CONFIG_PATH)\n", + "print(f\"Router loaded with {len(router.llm_data)} LLM candidates\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Query Routing" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "EXAMPLE_QUERIES = [\n", + " {\"query\": \"What is the capital of France?\"},\n", + " {\"query\": \"Solve the equation: 2x + 5 = 15\"},\n", + " {\"query\": \"Write a Python function to check if a number is prime.\"},\n", + " {\"query\": \"Explain the difference between supervised and unsupervised learning.\"},\n", + " {\"query\": \"What are the main causes of climate change?\"},\n", + "]\n", + "\n", + "print(\"Routing Results:\")\n", + "print(\"=\" * 70)\n", + "\n", + "for i, query in enumerate(EXAMPLE_QUERIES, 1):\n", + " result = router.route_single(query)\n", + " print(f\"{i}. {query['query'][:55]}...\")\n", + " print(f\" Routed to: {result['model_name']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Using vLLM for Fast Inference (Optional)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# vLLM provides faster inference for CausalLM models\n", + "# Install: pip install vllm\n", + "\n", + "try:\n", + " from vllm import LLM, SamplingParams\n", + " vllm_available = True\n", + " print(\"vLLM is available for accelerated inference\")\n", + "except ImportError:\n", + " vllm_available = False\n", + " print(\"vLLM not installed. Using standard HuggingFace inference.\")\n", + " print(\"For faster inference, install vLLM: pip install vllm\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. File-Based Inference\n", + "\n", + "Load queries from a file and save results." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "\n", + "# Load queries from a JSONL file\n", + "def load_queries_from_file(file_path):\n", + " \"\"\"Load queries from a JSONL file.\"\"\"\n", + " queries = []\n", + " with open(file_path, 'r', encoding='utf-8') as f:\n", + " for line in f:\n", + " if line.strip():\n", + " queries.append(json.loads(line))\n", + " return queries\n", + "\n", + "# Save results to a JSONL file\n", + "def save_results_to_file(results, output_path):\n", + " \"\"\"Save routing results to a JSONL file.\"\"\"\n", + " os.makedirs(os.path.dirname(output_path), exist_ok=True)\n", + " with open(output_path, 'w', encoding='utf-8') as f:\n", + " for result in results:\n", + " f.write(json.dumps(result, ensure_ascii=False) + '\\n')\n", + " print(f\"Results saved to: {output_path}\")\n", + "\n", + "# Example: Load from default query file\n", + "QUERY_FILE = \"data/example_data/query_data/default_query_test.jsonl\"\n", + "OUTPUT_FILE = \"outputs/causallm_router_results.jsonl\"\n", + "\n", + "if os.path.exists(QUERY_FILE):\n", + " # Load queries\n", + " file_queries = load_queries_from_file(QUERY_FILE)\n", + " print(f\"Loaded {len(file_queries)} queries from: {QUERY_FILE}\")\n", + " \n", + " # Route queries\n", + " file_results = router.route_batch(batch=file_queries[:10])\n", + " print(f\"Routed {len(file_results)} queries\")\n", + " \n", + " # Save results\n", + " save_results_to_file(file_results, OUTPUT_FILE)\n", + " \n", + " # Show sample results\n", + " print(f\"\\nSample results:\")\n", + " for i, result in enumerate(file_results[:3], 1):\n", + " print(f\" {i}. {result.get('query', '')[:40]}... -> {result['model_name']}\")\n", + "else:\n", + " print(f\"Query file not found: {QUERY_FILE}\")\n", + " print(\"Create a JSONL file with format: {\\\"query\\\": \\\"Your question\\\"}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "This notebook demonstrated:\n", + "1. Loading a trained CausalLMRouter\n", + "2. Routing queries using LLM-based inference\n", + "\n", + "CausalLMRouter is effective for:\n", + "- Complex queries requiring deep semantic understanding\n", + "- High-quality routing with LLM reasoning\n", + "\n", + "**Tips for Production**:\n", + "- Use vLLM for faster inference\n", + "- Consider quantization (4-bit, 8-bit) for memory efficiency\n", + "- Batch queries for better throughput" + ] + } + ], + "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.13.11" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/colab_notebooks/custom_router/01_creating_custom_routers.ipynb b/colab_notebooks/custom_router/01_creating_custom_routers.ipynb new file mode 100644 index 0000000..eab2c58 --- /dev/null +++ b/colab_notebooks/custom_router/01_creating_custom_routers.ipynb @@ -0,0 +1,1039 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "cell-0", + "metadata": {}, + "source": [ + "# Creating Custom Routers\n", + "\n", + "This notebook provides a comprehensive guide to creating your own custom routers in LLMRouter.\n", + "\n", + "## Overview\n", + "\n", + "LLMRouter uses a modular architecture where all routers inherit from `MetaRouter`. This design allows you to:\n", + "\n", + "1. **Create simple rule-based routers** (no training required)\n", + "2. **Build ML-based routers** with custom training logic\n", + "3. **Implement API-based routers** that use external services\n", + "\n", + "## Architecture\n", + "\n", + "```\n", + "MetaRouter (Base Class)\n", + " ├── route_single(query) # Route one query\n", + " ├── route_batch(batch) # Route multiple queries\n", + " ├── save_router(path) # Save model state\n", + " └── load_router(path) # Load model state\n", + "\n", + "BaseTrainer (For trainable routers)\n", + " ├── train() # Training loop\n", + " └── loss_func() # Loss computation\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "cell-1", + "metadata": {}, + "source": [ + "## 1. Environment Setup" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-2", + "metadata": {}, + "outputs": [], + "source": [ + "# For Google Colab\n", + "import os\n", + "\n", + "if 'COLAB_GPU' in os.environ:\n", + " !git clone https://github.com/ulab-uiuc/LLMRouter.git\n", + " %cd LLMRouter\n", + " !pip install -e .\n", + " !pip install pyyaml scikit-learn" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-3", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import sys\n", + "from pathlib import Path\n", + "\n", + "PROJECT_ROOT = Path(os.getcwd()).parent.parent\n", + "if str(PROJECT_ROOT) not in sys.path:\n", + " sys.path.insert(0, str(PROJECT_ROOT))\n", + "\n", + "os.chdir(PROJECT_ROOT)\n", + "print(f\"Working directory: {os.getcwd()}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-4", + "metadata": {}, + "outputs": [], + "source": [ + "from llmrouter.utils import setup_environment\n", + "setup_environment()\n", + "\n", + "import json\n", + "import yaml\n", + "import copy\n", + "import random\n", + "import numpy as np\n", + "import torch\n", + "import torch.nn as nn\n", + "from typing import Any, Dict, List, Optional\n", + "from abc import ABC, abstractmethod\n", + "\n", + "print(\"Environment ready!\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-5", + "metadata": {}, + "source": [ + "## 2. Understanding the MetaRouter Base Class\n", + "\n", + "All routers must inherit from `MetaRouter` and implement two abstract methods:\n", + "\n", + "| Method | Description | Input | Output |\n", + "|--------|-------------|-------|--------|\n", + "| `route_single(query)` | Route a single query | `dict` with \"query\" key | `dict` with \"model_name\" key |\n", + "| `route_batch(batch)` | Route multiple queries | `list` of query dicts | `list` of result dicts |" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-6", + "metadata": {}, + "outputs": [], + "source": [ + "# Let's examine the MetaRouter interface\n", + "from llmrouter.models.meta_router import MetaRouter\n", + "\n", + "print(\"MetaRouter Abstract Methods:\")\n", + "print(\"=\" * 50)\n", + "print(\"1. route_single(query: Dict) -> Dict\")\n", + "print(\" - Routes a single query to a model\")\n", + "print(\" - Must return dict with 'model_name' key\")\n", + "print()\n", + "print(\"2. route_batch(batch: List) -> List[Dict]\")\n", + "print(\" - Routes multiple queries\")\n", + "print(\" - Can include API calls for execution\")\n", + "print()\n", + "print(\"MetaRouter provides:\")\n", + "print(\"- Automatic YAML config loading\")\n", + "print(\"- Data loading via DataLoader\")\n", + "print(\"- save_router() / load_router() utilities\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-7", + "metadata": {}, + "source": [ + "## 3. Example 1: Simple Rule-Based Router\n", + "\n", + "Let's create a router that selects models based on query length.\n", + "\n", + "**Logic**:\n", + "- Short queries (< 50 chars) → Small, fast model\n", + "- Medium queries (50-200 chars) → Medium model \n", + "- Long queries (> 200 chars) → Large, capable model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-8", + "metadata": {}, + "outputs": [], + "source": [ + "from llmrouter.models.meta_router import MetaRouter\n", + "\n", + "class QueryLengthRouter(MetaRouter):\n", + " \"\"\"\n", + " A simple router that selects models based on query length.\n", + " \n", + " No training required - pure rule-based routing.\n", + " \"\"\"\n", + " \n", + " def __init__(self, yaml_path: str = None, thresholds: tuple = (50, 200)):\n", + " \"\"\"\n", + " Args:\n", + " yaml_path: Path to YAML config (optional)\n", + " thresholds: (short_threshold, long_threshold) for categorizing queries\n", + " \"\"\"\n", + " # Use dummy model since no neural network is needed\n", + " dummy_model = nn.Identity()\n", + " super().__init__(model=dummy_model, yaml_path=yaml_path)\n", + " \n", + " self.short_threshold = thresholds[0]\n", + " self.long_threshold = thresholds[1]\n", + " \n", + " # Define model mapping (can be overridden via config)\n", + " self.model_mapping = {\n", + " \"short\": None, # Will be set from llm_data\n", + " \"medium\": None,\n", + " \"long\": None\n", + " }\n", + " \n", + " # Auto-assign models based on size if llm_data is available\n", + " if hasattr(self, 'llm_data') and self.llm_data:\n", + " self._assign_models_by_size()\n", + " \n", + " print(f\"QueryLengthRouter initialized!\")\n", + " print(f\" Short (<{self.short_threshold} chars): {self.model_mapping['short']}\")\n", + " print(f\" Medium: {self.model_mapping['medium']}\")\n", + " print(f\" Long (>{self.long_threshold} chars): {self.model_mapping['long']}\")\n", + " \n", + " def _assign_models_by_size(self):\n", + " \"\"\"Automatically assign models based on their size.\"\"\"\n", + " def parse_size(size_str):\n", + " try:\n", + " size_str = str(size_str).upper().strip()\n", + " if size_str.endswith('B'):\n", + " return float(size_str[:-1])\n", + " return float(size_str)\n", + " except:\n", + " return 0.0\n", + " \n", + " # Sort models by size\n", + " sorted_models = sorted(\n", + " self.llm_data.items(),\n", + " key=lambda x: parse_size(x[1].get('size', '0'))\n", + " )\n", + " \n", + " if len(sorted_models) >= 3:\n", + " self.model_mapping['short'] = sorted_models[0][0]\n", + " self.model_mapping['medium'] = sorted_models[len(sorted_models)//2][0]\n", + " self.model_mapping['long'] = sorted_models[-1][0]\n", + " elif len(sorted_models) == 2:\n", + " self.model_mapping['short'] = sorted_models[0][0]\n", + " self.model_mapping['medium'] = sorted_models[0][0]\n", + " self.model_mapping['long'] = sorted_models[1][0]\n", + " elif len(sorted_models) == 1:\n", + " self.model_mapping['short'] = sorted_models[0][0]\n", + " self.model_mapping['medium'] = sorted_models[0][0]\n", + " self.model_mapping['long'] = sorted_models[0][0]\n", + " \n", + " def _categorize_query(self, query_text: str) -> str:\n", + " \"\"\"Categorize query by length.\"\"\"\n", + " length = len(query_text)\n", + " if length < self.short_threshold:\n", + " return \"short\"\n", + " elif length > self.long_threshold:\n", + " return \"long\"\n", + " else:\n", + " return \"medium\"\n", + " \n", + " def route_single(self, query: Dict[str, Any]) -> Dict[str, Any]:\n", + " \"\"\"\n", + " Route a single query based on its length.\n", + " \n", + " Args:\n", + " query: Dict with 'query' key containing the text\n", + " \n", + " Returns:\n", + " Dict with original query data plus 'model_name'\n", + " \"\"\"\n", + " query_text = query.get(\"query\", \"\")\n", + " category = self._categorize_query(query_text)\n", + " model_name = self.model_mapping[category]\n", + " \n", + " result = copy.copy(query)\n", + " result[\"model_name\"] = model_name\n", + " result[\"query_category\"] = category\n", + " result[\"query_length\"] = len(query_text)\n", + " \n", + " return result\n", + " \n", + " def route_batch(self, batch: Optional[List] = None, task_name: str = None) -> List[Dict]:\n", + " \"\"\"\n", + " Route a batch of queries.\n", + " \n", + " Args:\n", + " batch: List of query dicts, or None to use test data\n", + " task_name: Optional task name for formatting\n", + " \n", + " Returns:\n", + " List of results with model assignments\n", + " \"\"\"\n", + " if batch is None:\n", + " if hasattr(self, 'query_data_test'):\n", + " batch = self.query_data_test\n", + " else:\n", + " return []\n", + " \n", + " results = []\n", + " for query in batch:\n", + " if isinstance(query, dict):\n", + " result = self.route_single(query)\n", + " else:\n", + " result = self.route_single({\"query\": str(query)})\n", + " results.append(result)\n", + " \n", + " return results" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-9", + "metadata": {}, + "outputs": [], + "source": [ + "# Create a config file for the custom router\n", + "custom_config = {\n", + " \"data_path\": {\n", + " \"llm_data\": \"data/example_data/llm_candidates/default_llm.json\",\n", + " \"query_data_test\": \"data/example_data/query_data/default_query_test.jsonl\"\n", + " }\n", + "}\n", + "\n", + "CONFIG_PATH = \"configs/model_config_train/custom_router_temp.yaml\"\n", + "os.makedirs(os.path.dirname(CONFIG_PATH), exist_ok=True)\n", + "\n", + "with open(CONFIG_PATH, 'w') as f:\n", + " yaml.dump(custom_config, f)\n", + "\n", + "print(\"Config saved!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-10", + "metadata": {}, + "outputs": [], + "source": [ + "# Initialize and test the QueryLengthRouter\n", + "router = QueryLengthRouter(yaml_path=CONFIG_PATH)\n", + "\n", + "# Test queries of different lengths\n", + "test_queries = [\n", + " {\"query\": \"Hi there!\"}, # Short\n", + " {\"query\": \"What is the capital of France and what is its population? Also tell me about its history.\"}, # Medium\n", + " {\"query\": \"Please provide a comprehensive analysis of the economic, political, and social factors that contributed to the Industrial Revolution in 18th century Britain, including the role of technological innovations, colonial trade, agricultural changes, and the emergence of new social classes. Compare this transformation with similar industrialization processes in other countries.\"}, # Long\n", + "]\n", + "\n", + "print(\"\\nRouting Results:\")\n", + "print(\"=\" * 70)\n", + "\n", + "for i, query in enumerate(test_queries, 1):\n", + " result = router.route_single(query)\n", + " print(f\"\\n{i}. Query ({result['query_length']} chars): {query['query'][:50]}...\")\n", + " print(f\" Category: {result['query_category']}\")\n", + " print(f\" Routed to: {result['model_name']}\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-11", + "metadata": {}, + "source": [ + "## 4. Example 2: Keyword-Based Router\n", + "\n", + "A router that matches queries to models based on keyword patterns.\n", + "\n", + "**Use Case**: Route math questions to a math-specialized model, code questions to a coding model, etc." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-12", + "metadata": {}, + "outputs": [], + "source": [ + "import re\n", + "\n", + "class KeywordRouter(MetaRouter):\n", + " \"\"\"\n", + " Routes queries based on keyword matching.\n", + " \n", + " Maps keyword patterns to specific models.\n", + " \"\"\"\n", + " \n", + " def __init__(self, yaml_path: str = None, keyword_rules: dict = None):\n", + " dummy_model = nn.Identity()\n", + " super().__init__(model=dummy_model, yaml_path=yaml_path)\n", + " \n", + " # Default keyword rules (can be overridden)\n", + " self.keyword_rules = keyword_rules or {\n", + " \"math\": {\n", + " \"patterns\": [r\"\\bcalculate\\b\", r\"\\bsolve\\b\", r\"\\bequation\\b\", \n", + " r\"\\bmath\\b\", r\"\\d+\\s*[+\\-*/]\\s*\\d+\", r\"\\bderivative\\b\",\n", + " r\"\\bintegral\\b\", r\"\\bprove\\b\"],\n", + " \"model\": None # Will be assigned\n", + " },\n", + " \"code\": {\n", + " \"patterns\": [r\"\\bcode\\b\", r\"\\bprogram\\b\", r\"\\bfunction\\b\",\n", + " r\"\\bpython\\b\", r\"\\bjavascript\\b\", r\"\\bdebug\\b\",\n", + " r\"\\balgorithm\\b\", r\"\\bAPI\\b\"],\n", + " \"model\": None\n", + " },\n", + " \"general\": {\n", + " \"patterns\": [], # Fallback\n", + " \"model\": None\n", + " }\n", + " }\n", + " \n", + " # Compile regex patterns\n", + " for category in self.keyword_rules:\n", + " patterns = self.keyword_rules[category][\"patterns\"]\n", + " self.keyword_rules[category][\"compiled\"] = [\n", + " re.compile(p, re.IGNORECASE) for p in patterns\n", + " ]\n", + " \n", + " # Assign models from llm_data if available\n", + " if hasattr(self, 'llm_data') and self.llm_data:\n", + " model_names = list(self.llm_data.keys())\n", + " # Simple assignment - you can customize this\n", + " for i, category in enumerate(self.keyword_rules.keys()):\n", + " self.keyword_rules[category][\"model\"] = model_names[i % len(model_names)]\n", + " \n", + " print(\"KeywordRouter initialized!\")\n", + " for cat, info in self.keyword_rules.items():\n", + " print(f\" {cat}: {info['model']}\")\n", + " \n", + " def _match_category(self, query_text: str) -> str:\n", + " \"\"\"Match query to a category based on keywords.\"\"\"\n", + " for category, info in self.keyword_rules.items():\n", + " if category == \"general\":\n", + " continue\n", + " for pattern in info.get(\"compiled\", []):\n", + " if pattern.search(query_text):\n", + " return category\n", + " return \"general\"\n", + " \n", + " def route_single(self, query: Dict[str, Any]) -> Dict[str, Any]:\n", + " query_text = query.get(\"query\", \"\")\n", + " category = self._match_category(query_text)\n", + " model_name = self.keyword_rules[category][\"model\"]\n", + " \n", + " result = copy.copy(query)\n", + " result[\"model_name\"] = model_name\n", + " result[\"matched_category\"] = category\n", + " return result\n", + " \n", + " def route_batch(self, batch: Optional[List] = None, task_name: str = None) -> List[Dict]:\n", + " if batch is None:\n", + " batch = getattr(self, 'query_data_test', [])\n", + " \n", + " return [self.route_single(q if isinstance(q, dict) else {\"query\": str(q)}) \n", + " for q in batch]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-13", + "metadata": {}, + "outputs": [], + "source": [ + "# Test the KeywordRouter\n", + "keyword_router = KeywordRouter(yaml_path=CONFIG_PATH)\n", + "\n", + "test_queries = [\n", + " {\"query\": \"Calculate the integral of x^2 from 0 to 5\"},\n", + " {\"query\": \"Write a Python function to sort a list\"},\n", + " {\"query\": \"What is the capital of Japan?\"},\n", + " {\"query\": \"Debug this JavaScript code for me\"},\n", + " {\"query\": \"Solve the equation 2x + 5 = 15\"},\n", + "]\n", + "\n", + "print(\"\\nKeyword Routing Results:\")\n", + "print(\"=\" * 70)\n", + "\n", + "for query in test_queries:\n", + " result = keyword_router.route_single(query)\n", + " print(f\"Query: {query['query'][:50]}...\")\n", + " print(f\" Category: {result['matched_category']} -> {result['model_name']}\")\n", + " print()" + ] + }, + { + "cell_type": "markdown", + "id": "cell-14", + "metadata": {}, + "source": [ + "## 5. Example 3: Trainable Custom Router\n", + "\n", + "Now let's create a router that requires training. We'll build a simple logistic regression router.\n", + "\n", + "**Architecture**:\n", + "- Extract features from query text\n", + "- Train a classifier to predict the best model\n", + "- Use BaseTrainer for training logic" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-15", + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.feature_extraction.text import TfidfVectorizer\n", + "from sklearn.linear_model import LogisticRegression\n", + "from llmrouter.models.base_trainer import BaseTrainer\n", + "import pickle\n", + "\n", + "class TfidfRouter(MetaRouter):\n", + " \"\"\"\n", + " A trainable router using TF-IDF features and Logistic Regression.\n", + " \n", + " This demonstrates how to create a custom ML-based router.\n", + " \"\"\"\n", + " \n", + " def __init__(self, yaml_path: str):\n", + " dummy_model = nn.Identity()\n", + " super().__init__(model=dummy_model, yaml_path=yaml_path)\n", + " \n", + " # Initialize TF-IDF vectorizer\n", + " self.vectorizer = TfidfVectorizer(\n", + " max_features=1000,\n", + " stop_words='english',\n", + " ngram_range=(1, 2)\n", + " )\n", + " \n", + " # Initialize classifier\n", + " self.classifier = LogisticRegression(\n", + " max_iter=1000,\n", + " multi_class='multinomial'\n", + " )\n", + " \n", + " # Prepare training data from routing_data_train\n", + " if hasattr(self, 'routing_data_train') and self.routing_data_train is not None:\n", + " # Get best model for each query\n", + " best_routes = self.routing_data_train.loc[\n", + " self.routing_data_train.groupby(\"query\")[\"performance\"].idxmax()\n", + " ].reset_index(drop=True)\n", + " \n", + " self.train_queries = best_routes[\"query\"].tolist()\n", + " self.train_labels = best_routes[\"model_name\"].tolist()\n", + " print(f\"Prepared {len(self.train_queries)} training samples\")\n", + " \n", + " self.is_trained = False\n", + " print(\"TfidfRouter initialized!\")\n", + " \n", + " def route_single(self, query: Dict[str, Any]) -> Dict[str, Any]:\n", + " if not self.is_trained:\n", + " raise RuntimeError(\"Router not trained! Call trainer.train() first.\")\n", + " \n", + " query_text = query.get(\"query\", \"\")\n", + " \n", + " # Transform query to TF-IDF features\n", + " features = self.vectorizer.transform([query_text])\n", + " \n", + " # Predict model\n", + " model_name = self.classifier.predict(features)[0]\n", + " probabilities = self.classifier.predict_proba(features)[0]\n", + " confidence = max(probabilities)\n", + " \n", + " result = copy.copy(query)\n", + " result[\"model_name\"] = model_name\n", + " result[\"confidence\"] = float(confidence)\n", + " return result\n", + " \n", + " def route_batch(self, batch: Optional[List] = None, task_name: str = None) -> List[Dict]:\n", + " if batch is None:\n", + " batch = getattr(self, 'query_data_test', [])\n", + " \n", + " return [self.route_single(q if isinstance(q, dict) else {\"query\": str(q)}) \n", + " for q in batch]\n", + " \n", + " def save_model(self, path: str):\n", + " \"\"\"Save trained model to disk.\"\"\"\n", + " os.makedirs(os.path.dirname(path), exist_ok=True)\n", + " with open(path, 'wb') as f:\n", + " pickle.dump({\n", + " 'vectorizer': self.vectorizer,\n", + " 'classifier': self.classifier,\n", + " 'is_trained': self.is_trained\n", + " }, f)\n", + " print(f\"Model saved to: {path}\")\n", + " \n", + " def load_model(self, path: str):\n", + " \"\"\"Load trained model from disk.\"\"\"\n", + " with open(path, 'rb') as f:\n", + " data = pickle.load(f)\n", + " self.vectorizer = data['vectorizer']\n", + " self.classifier = data['classifier']\n", + " self.is_trained = data['is_trained']\n", + " print(f\"Model loaded from: {path}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-16", + "metadata": {}, + "outputs": [], + "source": [ + "class TfidfRouterTrainer(BaseTrainer):\n", + " \"\"\"\n", + " Trainer for TfidfRouter.\n", + " \n", + " Handles the training loop for the TF-IDF + LogisticRegression router.\n", + " \"\"\"\n", + " \n", + " def __init__(self, router: TfidfRouter, save_path: str = None):\n", + " super().__init__(router=router, optimizer=None, device=\"cpu\")\n", + " self.save_path = save_path or \"models/tfidf_router/model.pkl\"\n", + " \n", + " def train(self, dataloader=None):\n", + " \"\"\"Train the TF-IDF router.\"\"\"\n", + " print(\"Training TfidfRouter...\")\n", + " print(\"=\" * 50)\n", + " \n", + " # Get training data from router\n", + " queries = self.router.train_queries\n", + " labels = self.router.train_labels\n", + " \n", + " print(f\"Training samples: {len(queries)}\")\n", + " print(f\"Unique models: {len(set(labels))}\")\n", + " \n", + " # Fit TF-IDF vectorizer\n", + " print(\"\\n1. Fitting TF-IDF vectorizer...\")\n", + " X = self.router.vectorizer.fit_transform(queries)\n", + " print(f\" Feature matrix shape: {X.shape}\")\n", + " \n", + " # Train classifier\n", + " print(\"\\n2. Training classifier...\")\n", + " self.router.classifier.fit(X, labels)\n", + " \n", + " # Evaluate on training data\n", + " train_accuracy = self.router.classifier.score(X, labels)\n", + " print(f\" Training accuracy: {train_accuracy:.4f}\")\n", + " \n", + " self.router.is_trained = True\n", + " \n", + " # Save model\n", + " print(f\"\\n3. Saving model...\")\n", + " self.router.save_model(self.save_path)\n", + " \n", + " print(\"\\nTraining complete!\")\n", + " return {\"train_accuracy\": train_accuracy}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-17", + "metadata": {}, + "outputs": [], + "source": [ + "# Create config with training data\n", + "tfidf_config = {\n", + " \"data_path\": {\n", + " \"llm_data\": \"data/example_data/llm_candidates/default_llm.json\",\n", + " \"query_data_test\": \"data/example_data/query_data/default_query_test.jsonl\",\n", + " \"routing_data_train\": \"data/example_data/routing_data/default_routing_train_data.jsonl\",\n", + " \"routing_data_test\": \"data/example_data/routing_data/default_routing_test_data.jsonl\"\n", + " },\n", + " \"model_path\": {\n", + " \"save_model_path\": \"models/tfidf_router/model.pkl\"\n", + " }\n", + "}\n", + "\n", + "TFIDF_CONFIG_PATH = \"configs/model_config_train/tfidf_router_temp.yaml\"\n", + "with open(TFIDF_CONFIG_PATH, 'w') as f:\n", + " yaml.dump(tfidf_config, f)\n", + "\n", + "print(\"TF-IDF Router config saved!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-18", + "metadata": {}, + "outputs": [], + "source": [ + "# Initialize and train the TfidfRouter\n", + "tfidf_router = TfidfRouter(yaml_path=TFIDF_CONFIG_PATH)\n", + "\n", + "# Create trainer and train\n", + "trainer = TfidfRouterTrainer(\n", + " router=tfidf_router,\n", + " save_path=\"models/tfidf_router/model.pkl\"\n", + ")\n", + "\n", + "metrics = trainer.train()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-19", + "metadata": {}, + "outputs": [], + "source": [ + "# Test the trained router\n", + "test_queries = [\n", + " {\"query\": \"What is machine learning?\"},\n", + " {\"query\": \"Solve the quadratic equation x^2 - 5x + 6 = 0\"},\n", + " {\"query\": \"Write a function to reverse a string in Python\"},\n", + " {\"query\": \"Explain the theory of relativity\"},\n", + "]\n", + "\n", + "print(\"\\nTF-IDF Router Results:\")\n", + "print(\"=\" * 70)\n", + "\n", + "for query in test_queries:\n", + " result = tfidf_router.route_single(query)\n", + " print(f\"Query: {query['query'][:50]}...\")\n", + " print(f\" Model: {result['model_name']}\")\n", + " print(f\" Confidence: {result['confidence']:.4f}\")\n", + " print()" + ] + }, + { + "cell_type": "markdown", + "id": "cell-20", + "metadata": {}, + "source": [ + "## 6. Example 4: Ensemble Router\n", + "\n", + "Combine multiple routing strategies for better decisions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-21", + "metadata": {}, + "outputs": [], + "source": [ + "from collections import Counter\n", + "\n", + "class EnsembleRouter(MetaRouter):\n", + " \"\"\"\n", + " Combines multiple routers using voting.\n", + " \n", + " Each sub-router votes for a model, and the ensemble\n", + " selects the model with the most votes.\n", + " \"\"\"\n", + " \n", + " def __init__(self, routers: List[MetaRouter], weights: List[float] = None):\n", + " \"\"\"\n", + " Args:\n", + " routers: List of router instances to combine\n", + " weights: Optional weights for each router's vote\n", + " \"\"\"\n", + " dummy_model = nn.Identity()\n", + " super().__init__(model=dummy_model, yaml_path=None)\n", + " \n", + " self.routers = routers\n", + " self.weights = weights or [1.0] * len(routers)\n", + " \n", + " print(f\"EnsembleRouter initialized with {len(routers)} sub-routers\")\n", + " \n", + " def route_single(self, query: Dict[str, Any]) -> Dict[str, Any]:\n", + " # Collect votes from all routers\n", + " votes = []\n", + " for router, weight in zip(self.routers, self.weights):\n", + " try:\n", + " result = router.route_single(query)\n", + " model_name = result.get(\"model_name\")\n", + " if model_name:\n", + " votes.extend([model_name] * int(weight * 10))\n", + " except Exception as e:\n", + " print(f\"Router {type(router).__name__} failed: {e}\")\n", + " \n", + " # Count votes and select winner\n", + " if votes:\n", + " vote_counts = Counter(votes)\n", + " winner = vote_counts.most_common(1)[0][0]\n", + " else:\n", + " winner = \"unknown\"\n", + " \n", + " result = copy.copy(query)\n", + " result[\"model_name\"] = winner\n", + " result[\"vote_distribution\"] = dict(Counter(votes))\n", + " return result\n", + " \n", + " def route_batch(self, batch: Optional[List] = None, task_name: str = None) -> List[Dict]:\n", + " if batch is None:\n", + " return []\n", + " return [self.route_single(q if isinstance(q, dict) else {\"query\": str(q)}) \n", + " for q in batch]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-22", + "metadata": {}, + "outputs": [], + "source": [ + "# Create ensemble with QueryLengthRouter and KeywordRouter\n", + "ensemble = EnsembleRouter(\n", + " routers=[router, keyword_router], # Using previously created routers\n", + " weights=[1.0, 1.5] # Give more weight to keyword router\n", + ")\n", + "\n", + "test_queries = [\n", + " {\"query\": \"Calculate 2 + 2\"},\n", + " {\"query\": \"Write a comprehensive essay about the impact of artificial intelligence on modern society, including economic, social, and ethical considerations.\"},\n", + "]\n", + "\n", + "print(\"\\nEnsemble Router Results:\")\n", + "print(\"=\" * 70)\n", + "\n", + "for query in test_queries:\n", + " result = ensemble.route_single(query)\n", + " print(f\"Query: {query['query'][:60]}...\")\n", + " print(f\" Winner: {result['model_name']}\")\n", + " print(f\" Vote distribution: {result['vote_distribution']}\")\n", + " print()" + ] + }, + { + "cell_type": "markdown", + "id": "cell-23", + "metadata": {}, + "source": [ + "## 7. Best Practices\n", + "\n", + "### Router Design Guidelines\n", + "\n", + "1. **Always inherit from MetaRouter**\n", + " ```python\n", + " class MyRouter(MetaRouter):\n", + " def __init__(self, yaml_path: str):\n", + " dummy_model = nn.Identity() # If no neural network needed\n", + " super().__init__(model=dummy_model, yaml_path=yaml_path)\n", + " ```\n", + "\n", + "2. **Implement both abstract methods**\n", + " - `route_single()` - For single query routing\n", + " - `route_batch()` - For batch processing (can call route_single internally)\n", + "\n", + "3. **Return consistent output format**\n", + " ```python\n", + " result = copy.copy(query) # Preserve input\n", + " result[\"model_name\"] = selected_model\n", + " return result\n", + " ```\n", + "\n", + "4. **Use YAML config for flexibility**\n", + " - Data paths\n", + " - Hyperparameters\n", + " - Model paths\n", + "\n", + "5. **Separate training logic into Trainer class**\n", + " - Inherit from `BaseTrainer`\n", + " - Implement `train()` method\n", + " - Keep router focused on inference" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-24", + "metadata": {}, + "outputs": [], + "source": [ + "# Template for creating your own router\n", + "ROUTER_TEMPLATE = '''\n", + "from typing import Any, Dict, List, Optional\n", + "import copy\n", + "import torch.nn as nn\n", + "from llmrouter.models.meta_router import MetaRouter\n", + "from llmrouter.models.base_trainer import BaseTrainer\n", + "\n", + "\n", + "class MyCustomRouter(MetaRouter):\n", + " \"\"\"\n", + " Description of your router.\n", + " \"\"\"\n", + " \n", + " def __init__(self, yaml_path: str):\n", + " # Use dummy model if no neural network needed\n", + " dummy_model = nn.Identity()\n", + " super().__init__(model=dummy_model, yaml_path=yaml_path)\n", + " \n", + " # Initialize your router-specific components\n", + " # self.my_model = ...\n", + " \n", + " print(\"MyCustomRouter initialized!\")\n", + " \n", + " def route_single(self, query: Dict[str, Any]) -> Dict[str, Any]:\n", + " \"\"\"\n", + " Route a single query.\n", + " \n", + " Args:\n", + " query: Dict with 'query' key\n", + " \n", + " Returns:\n", + " Dict with 'model_name' key added\n", + " \"\"\"\n", + " query_text = query.get(\"query\", \"\")\n", + " \n", + " # YOUR ROUTING LOGIC HERE\n", + " model_name = \"your_selected_model\"\n", + " \n", + " result = copy.copy(query)\n", + " result[\"model_name\"] = model_name\n", + " return result\n", + " \n", + " def route_batch(self, batch: Optional[List] = None, \n", + " task_name: str = None) -> List[Dict]:\n", + " \"\"\"\n", + " Route a batch of queries.\n", + " \"\"\"\n", + " if batch is None:\n", + " batch = getattr(self, 'query_data_test', [])\n", + " \n", + " return [self.route_single(q if isinstance(q, dict) else {\"query\": str(q)}) \n", + " for q in batch]\n", + "\n", + "\n", + "class MyCustomRouterTrainer(BaseTrainer):\n", + " \"\"\"\n", + " Trainer for MyCustomRouter (if training is needed).\n", + " \"\"\"\n", + " \n", + " def __init__(self, router: MyCustomRouter, **kwargs):\n", + " super().__init__(router=router, optimizer=None, device=\"cpu\")\n", + " \n", + " def train(self, dataloader=None):\n", + " \"\"\"\n", + " Training loop.\n", + " \"\"\"\n", + " print(\"Training MyCustomRouter...\")\n", + " \n", + " # YOUR TRAINING LOGIC HERE\n", + " \n", + " print(\"Training complete!\")\n", + " return {\"status\": \"success\"}\n", + "'''\n", + "\n", + "print(ROUTER_TEMPLATE)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-25", + "metadata": {}, + "source": [ + "## 8. File-Based Inference\n", + "\n", + "Load queries from a file and save results." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-26", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "\n", + "# Load queries from a JSONL file\n", + "def load_queries_from_file(file_path):\n", + " \"\"\"Load queries from a JSONL file.\"\"\"\n", + " queries = []\n", + " with open(file_path, 'r', encoding='utf-8') as f:\n", + " for line in f:\n", + " if line.strip():\n", + " queries.append(json.loads(line))\n", + " return queries\n", + "\n", + "# Save results to a JSONL file\n", + "def save_results_to_file(results, output_path):\n", + " \"\"\"Save routing results to a JSONL file.\"\"\"\n", + " os.makedirs(os.path.dirname(output_path), exist_ok=True)\n", + " with open(output_path, 'w', encoding='utf-8') as f:\n", + " for result in results:\n", + " f.write(json.dumps(result, ensure_ascii=False) + '\\n')\n", + " print(f\"Results saved to: {output_path}\")\n", + "\n", + "# Example: Load from default query file\n", + "QUERY_FILE = \"data/example_data/query_data/default_query_test.jsonl\"\n", + "OUTPUT_FILE = \"outputs/custom_router_results.jsonl\"\n", + "\n", + "if os.path.exists(QUERY_FILE):\n", + " # Load queries\n", + " file_queries = load_queries_from_file(QUERY_FILE)\n", + " print(f\"Loaded {len(file_queries)} queries from: {QUERY_FILE}\")\n", + " \n", + " # Route using our custom QueryLengthRouter\n", + " file_results = router.route_batch(batch=file_queries[:10])\n", + " print(f\"Routed {len(file_results)} queries\")\n", + " \n", + " # Save results\n", + " save_results_to_file(file_results, OUTPUT_FILE)\n", + " \n", + " # Show sample results\n", + " print(f\"\\nSample results:\")\n", + " for i, result in enumerate(file_results[:3], 1):\n", + " print(f\" {i}. {result.get('query', '')[:40]}...\")\n", + " print(f\" Category: {result.get('query_category', 'N/A')}\")\n", + " print(f\" Model: {result['model_name']}\")\n", + "else:\n", + " print(f\"Query file not found: {QUERY_FILE}\")\n", + " print(\"Create a JSONL file with format: {\\\"query\\\": \\\"Your question\\\"}\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-27", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "This notebook demonstrated how to create custom routers:\n", + "\n", + "| Example | Type | Training | Use Case |\n", + "|---------|------|----------|----------|\n", + "| QueryLengthRouter | Rule-based | No | Simple length-based routing |\n", + "| KeywordRouter | Rule-based | No | Domain-specific routing |\n", + "| TfidfRouter | ML-based | Yes | Text classification routing |\n", + "| EnsembleRouter | Hybrid | No | Combining multiple strategies |\n", + "\n", + "**Key Takeaways**:\n", + "1. Inherit from `MetaRouter` for consistent interface\n", + "2. Implement `route_single()` and `route_batch()` methods\n", + "3. Use `BaseTrainer` for trainable routers\n", + "4. YAML config provides flexibility\n", + "5. Always return dict with `model_name` key\n", + "\n", + "**Next Steps**:\n", + "- Experiment with different routing strategies\n", + "- Combine with evaluation using `Evaluator`\n", + "- Deploy your custom router in production" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.10.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/colab_notebooks/data_preparation/01_data_preparation.ipynb b/colab_notebooks/data_preparation/01_data_preparation.ipynb new file mode 100644 index 0000000..b1bc168 --- /dev/null +++ b/colab_notebooks/data_preparation/01_data_preparation.ipynb @@ -0,0 +1,772 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# LLMRouter - Data Preparation\n", + "\n", + "This notebook covers the complete data preparation pipeline for LLMRouter:\n", + "1. **Dataset Download**: Download benchmark datasets from HuggingFace\n", + "2. **Query Data Generation**: Generate query data JSONL files\n", + "3. **LLM Embeddings Generation**: Generate LLM feature embeddings\n", + "4. **API Calling & Evaluation**: Call LLM APIs and evaluate responses\n", + "5. **Final Routing Data**: Generate unified embeddings and routing data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Environment Setup" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Install required packages (for Colab)\n", + "# !pip install llmrouter-lib datasets transformers torch pandas numpy tqdm litellm peft" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import sys\n", + "import json\n", + "import random\n", + "from pathlib import Path\n", + "\n", + "# Set project root\n", + "PROJECT_ROOT = Path(os.getcwd()).parent.parent\n", + "if str(PROJECT_ROOT) not in sys.path:\n", + " sys.path.insert(0, str(PROJECT_ROOT))\n", + "\n", + "print(f\"Project root: {PROJECT_ROOT}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Import LLMRouter utilities\n", + "from llmrouter.utils import setup_environment\n", + "from llmrouter.data.data_loader import DataLoader\n", + "\n", + "setup_environment()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Configuration" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Configuration for data generation\n", + "CONFIG = {\n", + " # Number of samples per task\n", + " \"sample_size\": 100, # Set to smaller number for testing, increase for full training\n", + " \n", + " # Train/test split ratio\n", + " \"train_ratio\": 0.8,\n", + " \n", + " # Random seed for reproducibility\n", + " \"random_seed\": 42,\n", + " \n", + " # Output paths (relative to project root)\n", + " \"output_paths\": {\n", + " \"query_data_train\": \"data/example_data/query_data/default_query_train.jsonl\",\n", + " \"query_data_test\": \"data/example_data/query_data/default_query_test.jsonl\",\n", + " \"query_embedding_data\": \"data/example_data/routing_data/query_embeddings_longformer.pt\",\n", + " \"routing_data_train\": \"data/example_data/routing_data/default_routing_train_data.jsonl\",\n", + " \"routing_data_test\": \"data/example_data/routing_data/default_routing_test_data.jsonl\",\n", + " \"llm_data\": \"data/example_data/llm_candidates/default_llm.json\",\n", + " \"llm_embedding_data\": \"data/example_data/llm_candidates/default_llm_embeddings.json\"\n", + " },\n", + " \n", + " # API settings (for LLM calling)\n", + " \"max_workers\": 10, # Number of parallel API calls\n", + "}\n", + "\n", + "print(\"Configuration loaded!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Dataset Download\n", + "\n", + "LLMRouter uses 11 benchmark datasets covering different task categories:\n", + "- **Math**: GSM8K, MATH\n", + "- **Code**: MBPP, HumanEval\n", + "- **World Knowledge**: Natural QA, Trivia QA\n", + "- **Popular Benchmarks**: MMLU, GPQA\n", + "- **Commonsense Reasoning**: CommonsenseQA, OpenbookQA, ARC-Challenge" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from datasets import load_dataset\n", + "\n", + "# Set cache directory (optional)\n", + "CACHE_DIR = str(PROJECT_ROOT / \"data\" / \"cache\")\n", + "os.makedirs(CACHE_DIR, exist_ok=True)\n", + "\n", + "def download_datasets(sample_size=100, random_seed=42):\n", + " \"\"\"Download and sample from benchmark datasets.\"\"\"\n", + " random.seed(random_seed)\n", + " samples = {}\n", + " \n", + " # 1. Natural QA\n", + " print(\"Downloading Natural QA...\")\n", + " try:\n", + " natural_qa = load_dataset('RUC-NLPIR/FlashRAG_datasets', 'nq', cache_dir=CACHE_DIR)\n", + " split_name = 'train' if 'train' in natural_qa else list(natural_qa.keys())[0]\n", + " indices = random.sample(range(len(natural_qa[split_name])), min(sample_size, len(natural_qa[split_name])))\n", + " samples['natural_qa'] = [natural_qa[split_name][i] for i in indices]\n", + " print(f\" Extracted {len(samples['natural_qa'])} samples\")\n", + " except Exception as e:\n", + " print(f\" Error: {e}\")\n", + " samples['natural_qa'] = []\n", + " \n", + " # 2. Trivia QA\n", + " print(\"Downloading Trivia QA...\")\n", + " try:\n", + " trivia_qa = load_dataset(\"trivia_qa\", \"rc.nocontext\", cache_dir=CACHE_DIR)\n", + " split_name = 'train' if 'train' in trivia_qa else list(trivia_qa.keys())[0]\n", + " indices = random.sample(range(len(trivia_qa[split_name])), min(sample_size, len(trivia_qa[split_name])))\n", + " samples['trivia_qa'] = [trivia_qa[split_name][i] for i in indices]\n", + " print(f\" Extracted {len(samples['trivia_qa'])} samples\")\n", + " except Exception as e:\n", + " print(f\" Error: {e}\")\n", + " samples['trivia_qa'] = []\n", + " \n", + " # 3. MMLU\n", + " print(\"Downloading MMLU...\")\n", + " try:\n", + " mmlu = load_dataset(\"cais/mmlu\", \"all\", cache_dir=CACHE_DIR)\n", + " split_name = 'auxiliary_train' if 'auxiliary_train' in mmlu else list(mmlu.keys())[0]\n", + " indices = random.sample(range(len(mmlu[split_name])), min(sample_size, len(mmlu[split_name])))\n", + " samples['mmlu'] = [mmlu[split_name][i] for i in indices]\n", + " print(f\" Extracted {len(samples['mmlu'])} samples\")\n", + " except Exception as e:\n", + " print(f\" Error: {e}\")\n", + " samples['mmlu'] = []\n", + " \n", + " # 4. GPQA\n", + " print(\"Downloading GPQA...\")\n", + " try:\n", + " gpqa = load_dataset(\"Idavidrein/gpqa\", \"gpqa_main\", cache_dir=CACHE_DIR)\n", + " split_name = 'train' if 'train' in gpqa else list(gpqa.keys())[0]\n", + " indices = random.sample(range(len(gpqa[split_name])), min(sample_size, len(gpqa[split_name])))\n", + " samples['gpqa'] = [gpqa[split_name][i] for i in indices]\n", + " print(f\" Extracted {len(samples['gpqa'])} samples\")\n", + " except Exception as e:\n", + " print(f\" Error: {e}\")\n", + " samples['gpqa'] = []\n", + " \n", + " # 5. GSM8K\n", + " print(\"Downloading GSM8K...\")\n", + " try:\n", + " gsm8k = load_dataset('gsm8k', 'main', cache_dir=CACHE_DIR)\n", + " split_name = 'train' if 'train' in gsm8k else list(gsm8k.keys())[0]\n", + " indices = random.sample(range(len(gsm8k[split_name])), min(sample_size, len(gsm8k[split_name])))\n", + " samples['gsm8k'] = [gsm8k[split_name][i] for i in indices]\n", + " print(f\" Extracted {len(samples['gsm8k'])} samples\")\n", + " except Exception as e:\n", + " print(f\" Error: {e}\")\n", + " samples['gsm8k'] = []\n", + " \n", + " # 6. CommonsenseQA\n", + " print(\"Downloading CommonsenseQA...\")\n", + " try:\n", + " commonsense_qa = load_dataset('commonsense_qa', cache_dir=CACHE_DIR)\n", + " split_name = 'train' if 'train' in commonsense_qa else list(commonsense_qa.keys())[0]\n", + " indices = random.sample(range(len(commonsense_qa[split_name])), min(sample_size, len(commonsense_qa[split_name])))\n", + " samples['commonsense_qa'] = [commonsense_qa[split_name][i] for i in indices]\n", + " print(f\" Extracted {len(samples['commonsense_qa'])} samples\")\n", + " except Exception as e:\n", + " print(f\" Error: {e}\")\n", + " samples['commonsense_qa'] = []\n", + " \n", + " # 7. ARC-Challenge\n", + " print(\"Downloading ARC-Challenge...\")\n", + " try:\n", + " arc = load_dataset('allenai/ai2_arc', 'ARC-Challenge', cache_dir=CACHE_DIR)\n", + " split_name = 'train' if 'train' in arc else list(arc.keys())[0]\n", + " indices = random.sample(range(len(arc[split_name])), min(sample_size, len(arc[split_name])))\n", + " samples['arc_challenge'] = [arc[split_name][i] for i in indices]\n", + " print(f\" Extracted {len(samples['arc_challenge'])} samples\")\n", + " except Exception as e:\n", + " print(f\" Error: {e}\")\n", + " samples['arc_challenge'] = []\n", + " \n", + " # 8. OpenbookQA\n", + " print(\"Downloading OpenbookQA...\")\n", + " try:\n", + " openbook = load_dataset('allenai/openbookqa', 'main', cache_dir=CACHE_DIR)\n", + " split_name = 'train' if 'train' in openbook else list(openbook.keys())[0]\n", + " indices = random.sample(range(len(openbook[split_name])), min(sample_size, len(openbook[split_name])))\n", + " samples['openbook_qa'] = [openbook[split_name][i] for i in indices]\n", + " print(f\" Extracted {len(samples['openbook_qa'])} samples\")\n", + " except Exception as e:\n", + " print(f\" Error: {e}\")\n", + " samples['openbook_qa'] = []\n", + " \n", + " return samples\n", + "\n", + "# Download datasets\n", + "raw_samples = download_datasets(sample_size=CONFIG['sample_size'], random_seed=CONFIG['random_seed'])\n", + "print(f\"\\nTotal tasks downloaded: {len([k for k, v in raw_samples.items() if v])}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Query Data Generation\n", + "\n", + "Convert raw samples into standardized query format." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def process_samples_to_query_data(samples):\n", + " \"\"\"Convert raw samples to standardized query data format.\"\"\"\n", + " data_all = []\n", + " \n", + " # Process Natural QA\n", + " for sample in samples.get('natural_qa', []):\n", + " data_all.append({\n", + " 'task_name': 'natural_qa',\n", + " 'query': sample['question'],\n", + " 'ground_truth': sample['golden_answers'][0] if sample.get('golden_answers') else sample.get('answer', ''),\n", + " 'metric': 'f1_score',\n", + " 'choices': None,\n", + " 'task_id': None\n", + " })\n", + " \n", + " # Process Trivia QA\n", + " for sample in samples.get('trivia_qa', []):\n", + " data_all.append({\n", + " 'task_name': 'trivia_qa',\n", + " 'query': sample['question'],\n", + " 'ground_truth': sample['answer']['normalized_aliases'][0] if sample.get('answer') else '',\n", + " 'metric': 'f1_score',\n", + " 'choices': None,\n", + " 'task_id': None\n", + " })\n", + " \n", + " # Process MMLU\n", + " for sample in samples.get('mmlu', []):\n", + " data_all.append({\n", + " 'task_name': 'mmlu',\n", + " 'query': sample['question'],\n", + " 'ground_truth': chr(65 + sample['answer']), # Convert 0,1,2,3 to A,B,C,D\n", + " 'metric': 'em_mc',\n", + " 'choices': {'text': sample['choices'], 'labels': ['A', 'B', 'C', 'D']},\n", + " 'task_id': None\n", + " })\n", + " \n", + " # Process GPQA\n", + " for sample in samples.get('gpqa', []):\n", + " options = [\n", + " sample['Correct Answer'],\n", + " sample['Incorrect Answer 1'],\n", + " sample['Incorrect Answer 2'],\n", + " sample['Incorrect Answer 3']\n", + " ]\n", + " # Shuffle options\n", + " mapping = list(range(4))\n", + " random.shuffle(mapping)\n", + " shuffled_options = [options[mapping.index(i)] for i in range(4)]\n", + " correct_idx = mapping.index(0)\n", + " \n", + " data_all.append({\n", + " 'task_name': 'gpqa',\n", + " 'query': sample['Question'],\n", + " 'ground_truth': chr(65 + correct_idx),\n", + " 'metric': 'em_mc',\n", + " 'choices': {'text': shuffled_options, 'labels': ['A', 'B', 'C', 'D']},\n", + " 'task_id': None\n", + " })\n", + " \n", + " # Process GSM8K\n", + " for sample in samples.get('gsm8k', []):\n", + " data_all.append({\n", + " 'task_name': 'gsm8k',\n", + " 'query': sample['question'],\n", + " 'ground_truth': sample['answer'],\n", + " 'metric': 'GSM8K',\n", + " 'choices': None,\n", + " 'task_id': None\n", + " })\n", + " \n", + " # Process CommonsenseQA\n", + " for sample in samples.get('commonsense_qa', []):\n", + " data_all.append({\n", + " 'task_name': 'commonsense_qa',\n", + " 'query': sample['question'],\n", + " 'ground_truth': sample['answerKey'],\n", + " 'metric': 'em_mc',\n", + " 'choices': sample['choices'],\n", + " 'task_id': None\n", + " })\n", + " \n", + " # Process ARC-Challenge\n", + " for sample in samples.get('arc_challenge', []):\n", + " data_all.append({\n", + " 'task_name': 'arc_challenge',\n", + " 'query': sample['question'],\n", + " 'ground_truth': sample['answerKey'],\n", + " 'metric': 'em_mc',\n", + " 'choices': sample['choices'],\n", + " 'task_id': None\n", + " })\n", + " \n", + " # Process OpenbookQA\n", + " for sample in samples.get('openbook_qa', []):\n", + " data_all.append({\n", + " 'task_name': 'openbook_qa',\n", + " 'query': sample['question_stem'],\n", + " 'ground_truth': sample['answerKey'],\n", + " 'metric': 'em_mc',\n", + " 'choices': sample['choices'],\n", + " 'task_id': None\n", + " })\n", + " \n", + " return data_all\n", + "\n", + "# Process samples\n", + "query_data = process_samples_to_query_data(raw_samples)\n", + "print(f\"Total processed samples: {len(query_data)}\")\n", + "\n", + "# Show sample counts by task\n", + "from collections import Counter\n", + "task_counts = Counter(item['task_name'] for item in query_data)\n", + "for task, count in sorted(task_counts.items()):\n", + " print(f\" {task}: {count}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Split into train/test\n", + "random.seed(CONFIG['random_seed'])\n", + "random.shuffle(query_data)\n", + "\n", + "train_size = int(len(query_data) * CONFIG['train_ratio'])\n", + "train_data = query_data[:train_size]\n", + "test_data = query_data[train_size:]\n", + "\n", + "print(f\"Train samples: {len(train_data)}\")\n", + "print(f\"Test samples: {len(test_data)}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Save query data to JSONL files\n", + "def save_query_data_jsonl(data_list, output_path):\n", + " \"\"\"Save query data to JSONL file.\"\"\"\n", + " os.makedirs(os.path.dirname(output_path), exist_ok=True)\n", + " \n", + " with open(output_path, 'w', encoding='utf-8') as f:\n", + " for item in data_list:\n", + " record = {\n", + " 'task_name': item['task_name'],\n", + " 'query': item['query'],\n", + " 'ground_truth': item['ground_truth'],\n", + " 'metric': item['metric'],\n", + " 'choices': json.dumps(item['choices']) if item['choices'] is not None else None,\n", + " 'task_id': item['task_id']\n", + " }\n", + " f.write(json.dumps(record, ensure_ascii=False) + '\\n')\n", + " \n", + " print(f\"Saved {len(data_list)} records to {output_path}\")\n", + "\n", + "# Save files\n", + "train_output_path = str(PROJECT_ROOT / CONFIG['output_paths']['query_data_train'])\n", + "test_output_path = str(PROJECT_ROOT / CONFIG['output_paths']['query_data_test'])\n", + "\n", + "save_query_data_jsonl(train_data, train_output_path)\n", + "save_query_data_jsonl(test_data, test_output_path)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. LLM Candidates Configuration\n", + "\n", + "Define the LLM candidates for routing." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Example LLM candidates configuration\n", + "# You can modify this based on your available LLMs\n", + "\n", + "LLM_CANDIDATES = {\n", + " \"gpt-4o-mini\": {\n", + " \"size\": \"small\",\n", + " \"feature\": \"Fast and cost-effective model for simple tasks\",\n", + " \"input_price\": 0.15,\n", + " \"output_price\": 0.60,\n", + " \"model\": \"gpt-4o-mini\",\n", + " \"service\": \"OpenAI\",\n", + " \"api_endpoint\": \"https://api.openai.com/v1\"\n", + " },\n", + " \"gpt-4o\": {\n", + " \"size\": \"large\",\n", + " \"feature\": \"Most capable GPT-4 model for complex tasks\",\n", + " \"input_price\": 2.50,\n", + " \"output_price\": 10.00,\n", + " \"model\": \"gpt-4o\",\n", + " \"service\": \"OpenAI\",\n", + " \"api_endpoint\": \"https://api.openai.com/v1\"\n", + " },\n", + " \"claude-3-haiku\": {\n", + " \"size\": \"small\",\n", + " \"feature\": \"Fast and efficient Claude model\",\n", + " \"input_price\": 0.25,\n", + " \"output_price\": 1.25,\n", + " \"model\": \"claude-3-haiku-20240307\",\n", + " \"service\": \"Anthropic\",\n", + " \"api_endpoint\": \"https://api.anthropic.com/v1\"\n", + " },\n", + " \"claude-3-5-sonnet\": {\n", + " \"size\": \"large\",\n", + " \"feature\": \"Balanced Claude model for most tasks\",\n", + " \"input_price\": 3.00,\n", + " \"output_price\": 15.00,\n", + " \"model\": \"claude-3-5-sonnet-20241022\",\n", + " \"service\": \"Anthropic\",\n", + " \"api_endpoint\": \"https://api.anthropic.com/v1\"\n", + " }\n", + "}\n", + "\n", + "# Save LLM candidates\n", + "llm_data_path = str(PROJECT_ROOT / CONFIG['output_paths']['llm_data'])\n", + "os.makedirs(os.path.dirname(llm_data_path), exist_ok=True)\n", + "\n", + "with open(llm_data_path, 'w', encoding='utf-8') as f:\n", + " json.dump(LLM_CANDIDATES, f, indent=2, ensure_ascii=False)\n", + "\n", + "print(f\"Saved {len(LLM_CANDIDATES)} LLM candidates to {llm_data_path}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. Generate Query Embeddings\n", + "\n", + "Generate Longformer embeddings for all queries." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import torch\n", + "from tqdm import tqdm\n", + "\n", + "# Import embedding function\n", + "from llmrouter.utils import get_longformer_embedding\n", + "\n", + "def generate_query_embeddings(query_data_list):\n", + " \"\"\"Generate Longformer embeddings for all queries.\"\"\"\n", + " embeddings = []\n", + " \n", + " for item in tqdm(query_data_list, desc=\"Generating embeddings\"):\n", + " query = item['query']\n", + " embedding = get_longformer_embedding(query)\n", + " embeddings.append(embedding)\n", + " \n", + " return torch.stack(embeddings)\n", + "\n", + "# Combine train and test data for unified embeddings\n", + "all_data = train_data + test_data\n", + "\n", + "print(f\"Generating embeddings for {len(all_data)} queries...\")\n", + "all_embeddings = generate_query_embeddings(all_data)\n", + "\n", + "print(f\"Embeddings shape: {all_embeddings.shape}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Save embeddings\n", + "embedding_path = str(PROJECT_ROOT / CONFIG['output_paths']['query_embedding_data'])\n", + "os.makedirs(os.path.dirname(embedding_path), exist_ok=True)\n", + "\n", + "torch.save(all_embeddings, embedding_path)\n", + "print(f\"Saved embeddings to {embedding_path}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7. API Calling and Evaluation (Optional)\n", + "\n", + "This step calls LLM APIs to generate responses and evaluates their performance.\n", + "\n", + "**Note**: This step requires API keys to be set in the environment." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Set API keys (replace with your actual keys)\n", + "# os.environ['OPENAI_API_KEY'] = 'your-openai-key'\n", + "# os.environ['ANTHROPIC_API_KEY'] = 'your-anthropic-key'\n", + "# os.environ['API_KEYS'] = json.dumps(['key1', 'key2']) # For multiple keys\n", + "\n", + "# Check if API keys are set\n", + "api_keys_available = bool(os.environ.get('API_KEYS') or os.environ.get('OPENAI_API_KEY'))\n", + "print(f\"API keys available: {api_keys_available}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Skip this cell if you don't have API keys\n", + "# This will use the existing routing data from the repository\n", + "\n", + "if api_keys_available:\n", + " from llmrouter.utils import call_api, generate_task_query\n", + " import pandas as pd\n", + " from concurrent.futures import ThreadPoolExecutor, as_completed\n", + " \n", + " def call_llm_for_query(args):\n", + " \"\"\"Call a single LLM for a query.\"\"\"\n", + " query_item, model_name, llm_config = args\n", + " \n", + " try:\n", + " # Format query based on task\n", + " formatted_query = generate_task_query(query_item['task_name'], query_item)\n", + " \n", + " # Prepare API request\n", + " request = {\n", + " 'api_endpoint': llm_config['api_endpoint'],\n", + " 'query': formatted_query,\n", + " 'model_name': model_name,\n", + " 'api_name': llm_config['model']\n", + " }\n", + " \n", + " # Call API\n", + " result = call_api(request, max_tokens=512, temperature=0.7)\n", + " \n", + " return {\n", + " **query_item,\n", + " 'model_name': model_name,\n", + " 'response': result.get('response', ''),\n", + " 'prompt_tokens': result.get('prompt_tokens', 0),\n", + " 'completion_tokens': result.get('completion_tokens', 0),\n", + " 'success': 'error' not in result\n", + " }\n", + " except Exception as e:\n", + " return {\n", + " **query_item,\n", + " 'model_name': model_name,\n", + " 'response': f'ERROR: {str(e)}',\n", + " 'prompt_tokens': 0,\n", + " 'completion_tokens': 0,\n", + " 'success': False\n", + " }\n", + " \n", + " # Create tasks for all query-model combinations\n", + " tasks = []\n", + " for item in train_data[:10]: # Limit for demo\n", + " for model_name, config in LLM_CANDIDATES.items():\n", + " tasks.append((item, model_name, config))\n", + " \n", + " print(f\"Processing {len(tasks)} query-model combinations...\")\n", + " \n", + " # Execute in parallel\n", + " results = []\n", + " with ThreadPoolExecutor(max_workers=CONFIG['max_workers']) as executor:\n", + " futures = {executor.submit(call_llm_for_query, task): task for task in tasks}\n", + " for future in tqdm(as_completed(futures), total=len(futures)):\n", + " results.append(future.result())\n", + " \n", + " print(f\"Completed {len(results)} API calls\")\n", + "else:\n", + " print(\"Skipping API calling - no API keys configured\")\n", + " print(\"You can use the existing routing data from: data/example_data/routing_data/\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 8. Using Existing Data\n", + "\n", + "If you skipped the API calling step, you can use the existing example data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Load existing routing data (if available)\n", + "from llmrouter.utils import load_jsonl\n", + "\n", + "routing_train_path = str(PROJECT_ROOT / CONFIG['output_paths']['routing_data_train'])\n", + "routing_test_path = str(PROJECT_ROOT / CONFIG['output_paths']['routing_data_test'])\n", + "\n", + "if os.path.exists(routing_train_path):\n", + " routing_train = load_jsonl(routing_train_path)\n", + " print(f\"Loaded {len(routing_train)} training routing samples\")\n", + "else:\n", + " print(f\"Routing train data not found at {routing_train_path}\")\n", + "\n", + "if os.path.exists(routing_test_path):\n", + " routing_test = load_jsonl(routing_test_path)\n", + " print(f\"Loaded {len(routing_test)} test routing samples\")\n", + "else:\n", + " print(f\"Routing test data not found at {routing_test_path}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 9. Data Verification\n", + "\n", + "Verify that all required data files are available for training." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Verify all data files\n", + "required_files = [\n", + " ('Query Train Data', CONFIG['output_paths']['query_data_train']),\n", + " ('Query Test Data', CONFIG['output_paths']['query_data_test']),\n", + " ('Query Embeddings', CONFIG['output_paths']['query_embedding_data']),\n", + " ('Routing Train Data', CONFIG['output_paths']['routing_data_train']),\n", + " ('Routing Test Data', CONFIG['output_paths']['routing_data_test']),\n", + " ('LLM Data', CONFIG['output_paths']['llm_data']),\n", + "]\n", + "\n", + "print(\"Data file verification:\")\n", + "print(\"=\" * 60)\n", + "\n", + "all_available = True\n", + "for name, path in required_files:\n", + " full_path = str(PROJECT_ROOT / path)\n", + " exists = os.path.exists(full_path)\n", + " status = \"OK\" if exists else \"MISSING\"\n", + " if not exists:\n", + " all_available = False\n", + " print(f\"{name:25} [{status}]\")\n", + "\n", + "print(\"=\" * 60)\n", + "if all_available:\n", + " print(\"All data files are ready for training!\")\n", + "else:\n", + " print(\"Some files are missing. Please generate them or use example data.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "After running this notebook, you should have:\n", + "\n", + "1. **Query Data Files**:\n", + " - `query_data_train.jsonl` - Training queries\n", + " - `query_data_test.jsonl` - Test queries\n", + "\n", + "2. **Embedding File**:\n", + " - `query_embeddings_longformer.pt` - Unified query embeddings\n", + "\n", + "3. **Routing Data Files** (if API calling was performed):\n", + " - `routing_data_train.jsonl` - Training routing data with responses\n", + " - `routing_data_test.jsonl` - Test routing data with responses\n", + "\n", + "4. **LLM Configuration**:\n", + " - `default_llm.json` - LLM candidates configuration\n", + "\n", + "Now you can proceed to train any of the routers using the method-specific notebooks!" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.0" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/colab_notebooks/dcrouter/01_dcrouter_training_and_inference.ipynb b/colab_notebooks/dcrouter/01_dcrouter_training_and_inference.ipynb new file mode 100644 index 0000000..1e1cbc1 --- /dev/null +++ b/colab_notebooks/dcrouter/01_dcrouter_training_and_inference.ipynb @@ -0,0 +1,988 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# DCRouter - Training\n", + "\n", + "This notebook demonstrates how to train the **DCRouter** (Dual Contrastive Router).\n", + "\n", + "## Overview\n", + "\n", + "DCRouter uses dual contrastive learning with a mDEBERTa transformer backbone to route queries.\n", + "It learns to distinguish between good and bad LLM matches using contrastive loss.\n", + "\n", + "**Key Features**:\n", + "- Transformer-based (mDEBERTa) backbone\n", + "- Dual contrastive loss for better discrimination\n", + "- Cluster-based negative sampling\n", + "- State-of-the-art routing performance" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Environment Setup" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cloning into 'LLMRouter'...\n", + "remote: Enumerating objects: 6017, done.\u001b[K\n", + "remote: Counting objects: 100% (180/180), done.\u001b[K\n", + "remote: Compressing objects: 100% (90/90), done.\u001b[K\n", + "remote: Total 6017 (delta 105), reused 96 (delta 90), pack-reused 5837 (from 2)\u001b[K\n", + "Receiving objects: 100% (6017/6017), 89.41 MiB | 53.57 MiB/s, done.\n", + "Resolving deltas: 100% (2946/2946), done.\n", + "Updating files: 100% (288/288), done.\n", + "/home/zhongjie/LLMRouter\n", + "Obtaining file:///home/zhongjie/LLMRouter\n", + " Installing build dependencies ... \u001b[?25ldone\n", + "\u001b[?25h Checking if build backend supports build_editable ... \u001b[?25ldone\n", + "\u001b[?25h Getting requirements to build editable ... \u001b[?25ldone\n", + "\u001b[?25h Preparing editable metadata (pyproject.toml) ... \u001b[?25ldone\n", + "\u001b[?25hRequirement already satisfied: torch>=2.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.9.1)\n", + "Collecting transformers>=4.40 (from llmrouter-lib==0.2.0)\n", + " Using cached transformers-4.57.6-py3-none-any.whl.metadata (43 kB)\n", + "Collecting sentencepiece>=0.1.99 (from llmrouter-lib==0.2.0)\n", + " Using cached sentencepiece-0.2.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.metadata (10 kB)\n", + "Requirement already satisfied: numpy>=1.21 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.3.5)\n", + "Requirement already satisfied: pandas>=1.5 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.3.3)\n", + "Requirement already satisfied: scikit-learn>=1.2 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (1.8.0)\n", + "Requirement already satisfied: pyyaml>=6.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (6.0.3)\n", + "Requirement already satisfied: tqdm>=4.65 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (4.67.1)\n", + "Collecting datasets>=2.14 (from llmrouter-lib==0.2.0)\n", + " Using cached datasets-4.5.0-py3-none-any.whl.metadata (19 kB)\n", + "Requirement already satisfied: pydantic>=2.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.12.5)\n", + "Collecting gradio>=4.0 (from llmrouter-lib==0.2.0)\n", + " Using cached gradio-6.3.0-py3-none-any.whl.metadata (16 kB)\n", + "Collecting litellm>=1.0 (from llmrouter-lib==0.2.0)\n", + " Using cached litellm-1.81.0-py3-none-any.whl.metadata (29 kB)\n", + "Collecting peft>=0.7 (from llmrouter-lib==0.2.0)\n", + " Using cached peft-0.18.1-py3-none-any.whl.metadata (14 kB)\n", + "Collecting torch-geometric>=2.3 (from llmrouter-lib==0.2.0)\n", + " Using cached torch_geometric-2.7.0-py3-none-any.whl.metadata (63 kB)\n", + "Requirement already satisfied: scipy>=1.10 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (1.16.3)\n", + "Requirement already satisfied: protobuf>=3.20 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (6.31.1)\n", + "Requirement already satisfied: filelock in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (3.20.2)\n", + "Requirement already satisfied: pyarrow>=21.0.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (22.0.0)\n", + "Requirement already satisfied: dill<0.4.1,>=0.3.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (0.4.0)\n", + "Requirement already satisfied: requests>=2.32.2 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (2.32.5)\n", + "Requirement already satisfied: httpx<1.0.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (0.28.1)\n", + "Collecting xxhash (from datasets>=2.14->llmrouter-lib==0.2.0)\n", + " Using cached xxhash-3.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (13 kB)\n", + "Collecting multiprocess<0.70.19 (from datasets>=2.14->llmrouter-lib==0.2.0)\n", + " Using cached multiprocess-0.70.18-py313-none-any.whl.metadata (7.2 kB)\n", + "Collecting fsspec<=2025.10.0,>=2023.1.0 (from fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0)\n", + " Using cached fsspec-2025.10.0-py3-none-any.whl.metadata (10 kB)\n", + "Collecting huggingface-hub<2.0,>=0.25.0 (from datasets>=2.14->llmrouter-lib==0.2.0)\n", + " Using cached huggingface_hub-1.3.2-py3-none-any.whl.metadata (13 kB)\n", + "Requirement already satisfied: packaging in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (25.0)\n", + "Collecting aiohttp!=4.0.0a0,!=4.0.0a1 (from fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0)\n", + " Using cached aiohttp-3.13.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (8.1 kB)\n", + "Requirement already satisfied: anyio in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (4.12.0)\n", + "Requirement already satisfied: certifi in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (2026.1.4)\n", + "Requirement already satisfied: httpcore==1.* in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.0.9)\n", + "Requirement already satisfied: idna in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (3.11)\n", + "Requirement already satisfied: h11>=0.16 in /opt/conda/lib/python3.13/site-packages (from httpcore==1.*->httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (0.16.0)\n", + "Collecting hf-xet<2.0.0,>=1.2.0 (from huggingface-hub<2.0,>=0.25.0->datasets>=2.14->llmrouter-lib==0.2.0)\n", + " Using cached hf_xet-1.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (4.9 kB)\n", + "Collecting shellingham (from huggingface-hub<2.0,>=0.25.0->datasets>=2.14->llmrouter-lib==0.2.0)\n", + " Using cached shellingham-1.5.4-py2.py3-none-any.whl.metadata (3.5 kB)\n", + "Collecting typer-slim (from huggingface-hub<2.0,>=0.25.0->datasets>=2.14->llmrouter-lib==0.2.0)\n", + " Using cached typer_slim-0.21.1-py3-none-any.whl.metadata (16 kB)\n", + "Requirement already satisfied: typing-extensions>=4.1.0 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<2.0,>=0.25.0->datasets>=2.14->llmrouter-lib==0.2.0) (4.15.0)\n", + "Collecting aiohappyeyeballs>=2.5.0 (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0)\n", + " Using cached aiohappyeyeballs-2.6.1-py3-none-any.whl.metadata (5.9 kB)\n", + "Collecting aiosignal>=1.4.0 (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0)\n", + " Using cached aiosignal-1.4.0-py3-none-any.whl.metadata (3.7 kB)\n", + "Requirement already satisfied: attrs>=17.3.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (25.4.0)\n", + "Collecting frozenlist>=1.1.1 (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0)\n", + " Using cached frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (20 kB)\n", + "Collecting multidict<7.0,>=4.5 (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0)\n", + " Using cached multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (5.3 kB)\n", + "Collecting propcache>=0.2.0 (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0)\n", + " Using cached propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (13 kB)\n", + "Collecting yarl<2.0,>=1.17.0 (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0)\n", + " Using cached yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (75 kB)\n", + "Collecting aiofiles<25.0,>=22.0 (from gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached aiofiles-24.1.0-py3-none-any.whl.metadata (10 kB)\n", + "Collecting audioop-lts<1.0 (from gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached audioop_lts-0.2.2-cp313-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (2.0 kB)\n", + "Requirement already satisfied: brotli>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (1.2.0)\n", + "Collecting fastapi<1.0,>=0.115.2 (from gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached fastapi-0.128.0-py3-none-any.whl.metadata (30 kB)\n", + "Collecting ffmpy (from gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached ffmpy-1.0.0-py3-none-any.whl.metadata (3.0 kB)\n", + "Collecting gradio-client==2.0.3 (from gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached gradio_client-2.0.3-py3-none-any.whl.metadata (7.1 kB)\n", + "Collecting groovy~=0.1 (from gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached groovy-0.1.2-py3-none-any.whl.metadata (6.1 kB)\n", + "Requirement already satisfied: jinja2<4.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (3.1.6)\n", + "Requirement already satisfied: markupsafe<4.0,>=2.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (3.0.3)\n", + "Collecting orjson~=3.0 (from gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached orjson-3.11.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (41 kB)\n", + "Requirement already satisfied: pillow<13.0,>=8.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (12.1.0)\n", + "Collecting pydub (from gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached pydub-0.25.1-py2.py3-none-any.whl.metadata (1.4 kB)\n", + "Collecting python-multipart>=0.0.18 (from gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached python_multipart-0.0.21-py3-none-any.whl.metadata (1.8 kB)\n", + "Collecting safehttpx<0.2.0,>=0.1.7 (from gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached safehttpx-0.1.7-py3-none-any.whl.metadata (4.2 kB)\n", + "Collecting semantic-version~=2.0 (from gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached semantic_version-2.10.0-py2.py3-none-any.whl.metadata (9.7 kB)\n", + "Collecting starlette<1.0,>=0.40.0 (from gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Downloading starlette-0.52.1-py3-none-any.whl.metadata (6.3 kB)\n", + "Collecting tomlkit<0.14.0,>=0.12.0 (from gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached tomlkit-0.13.3-py3-none-any.whl.metadata (2.8 kB)\n", + "Collecting typer<1.0,>=0.12 (from gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached typer-0.21.1-py3-none-any.whl.metadata (16 kB)\n", + "Collecting uvicorn>=0.14.0 (from gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached uvicorn-0.40.0-py3-none-any.whl.metadata (6.7 kB)\n", + "Collecting starlette<1.0,>=0.40.0 (from gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached starlette-0.50.0-py3-none-any.whl.metadata (6.3 kB)\n", + "Collecting annotated-doc>=0.0.2 (from fastapi<1.0,>=0.115.2->gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached annotated_doc-0.0.4-py3-none-any.whl.metadata (6.6 kB)\n", + "Requirement already satisfied: python-dateutil>=2.8.2 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.2.0) (2.9.0.post0)\n", + "Requirement already satisfied: pytz>=2020.1 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.2.0) (2025.2)\n", + "Requirement already satisfied: tzdata>=2022.7 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.2.0) (2025.3)\n", + "Requirement already satisfied: annotated-types>=0.6.0 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.2.0) (0.7.0)\n", + "Requirement already satisfied: pydantic-core==2.41.5 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.2.0) (2.41.5)\n", + "Requirement already satisfied: typing-inspection>=0.4.2 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.2.0) (0.4.2)\n", + "Requirement already satisfied: click>=8.0.0 in /opt/conda/lib/python3.13/site-packages (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (8.3.1)\n", + "Collecting rich>=10.11.0 (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached rich-14.2.0-py3-none-any.whl.metadata (18 kB)\n", + "Collecting fastuuid>=0.13.0 (from litellm>=1.0->llmrouter-lib==0.2.0)\n", + " Using cached fastuuid-0.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.1 kB)\n", + "Collecting grpcio!=1.68.*,!=1.69.*,!=1.70.*,!=1.71.0,!=1.71.1,!=1.72.0,!=1.72.1,!=1.73.0,>=1.62.3 (from litellm>=1.0->llmrouter-lib==0.2.0)\n", + " Using cached grpcio-1.76.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (3.7 kB)\n", + "Requirement already satisfied: importlib-metadata>=6.8.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (8.7.0)\n", + "Requirement already satisfied: jsonschema<5.0.0,>=4.23.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (4.25.1)\n", + "Collecting openai>=2.8.0 (from litellm>=1.0->llmrouter-lib==0.2.0)\n", + " Using cached openai-2.15.0-py3-none-any.whl.metadata (29 kB)\n", + "Collecting python-dotenv>=0.2.0 (from litellm>=1.0->llmrouter-lib==0.2.0)\n", + " Using cached python_dotenv-1.2.1-py3-none-any.whl.metadata (25 kB)\n", + "Collecting tiktoken>=0.7.0 (from litellm>=1.0->llmrouter-lib==0.2.0)\n", + " Using cached tiktoken-0.12.0-cp313-cp313-manylinux_2_28_x86_64.whl.metadata (6.7 kB)\n", + "Collecting tokenizers (from litellm>=1.0->llmrouter-lib==0.2.0)\n", + " Using cached tokenizers-0.22.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (7.3 kB)\n", + "Requirement already satisfied: jsonschema-specifications>=2023.03.6 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.2.0) (2025.9.1)\n", + "Requirement already satisfied: referencing>=0.28.4 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.2.0) (0.37.0)\n", + "Requirement already satisfied: rpds-py>=0.7.1 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.2.0) (0.30.0)\n", + "Requirement already satisfied: zipp>=3.20 in /opt/conda/lib/python3.13/site-packages (from importlib-metadata>=6.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (3.23.0)\n", + "Requirement already satisfied: distro<2,>=1.7.0 in /opt/conda/lib/python3.13/site-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (1.9.0)\n", + "Collecting jiter<1,>=0.10.0 (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.2.0)\n", + " Using cached jiter-0.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (5.2 kB)\n", + "Requirement already satisfied: sniffio in /opt/conda/lib/python3.13/site-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (1.3.1)\n", + "Requirement already satisfied: psutil in /opt/conda/lib/python3.13/site-packages (from peft>=0.7->llmrouter-lib==0.2.0) (7.2.1)\n", + "Collecting accelerate>=0.21.0 (from peft>=0.7->llmrouter-lib==0.2.0)\n", + " Using cached accelerate-1.12.0-py3-none-any.whl.metadata (19 kB)\n", + "Collecting safetensors (from peft>=0.7->llmrouter-lib==0.2.0)\n", + " Using cached safetensors-0.7.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (4.1 kB)\n", + "Requirement already satisfied: six>=1.5 in /opt/conda/lib/python3.13/site-packages (from python-dateutil>=2.8.2->pandas>=1.5->llmrouter-lib==0.2.0) (1.17.0)\n", + "Requirement already satisfied: charset_normalizer<4,>=2 in /opt/conda/lib/python3.13/site-packages (from requests>=2.32.2->datasets>=2.14->llmrouter-lib==0.2.0) (3.4.4)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/conda/lib/python3.13/site-packages (from requests>=2.32.2->datasets>=2.14->llmrouter-lib==0.2.0) (2.6.2)\n", + "Collecting markdown-it-py>=2.2.0 (from rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached markdown_it_py-4.0.0-py3-none-any.whl.metadata (7.3 kB)\n", + "Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /opt/conda/lib/python3.13/site-packages (from rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (2.19.2)\n", + "Collecting mdurl~=0.1 (from markdown-it-py>=2.2.0->rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached mdurl-0.1.2-py3-none-any.whl.metadata (1.6 kB)\n", + "Requirement already satisfied: joblib>=1.3.0 in /opt/conda/lib/python3.13/site-packages (from scikit-learn>=1.2->llmrouter-lib==0.2.0) (1.5.3)\n", + "Requirement already satisfied: threadpoolctl>=3.2.0 in /opt/conda/lib/python3.13/site-packages (from scikit-learn>=1.2->llmrouter-lib==0.2.0) (3.6.0)\n", + "Collecting regex>=2022.1.18 (from tiktoken>=0.7.0->litellm>=1.0->llmrouter-lib==0.2.0)\n", + " Using cached regex-2026.1.15-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (40 kB)\n", + "Requirement already satisfied: setuptools in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (80.9.0)\n", + "Requirement already satisfied: sympy>=1.13.3 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (1.14.0)\n", + "Requirement already satisfied: networkx>=2.5.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (3.6.1)\n", + "Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.93)\n", + "Requirement already satisfied: nvidia-cuda-runtime-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.90)\n", + "Requirement already satisfied: nvidia-cuda-cupti-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.90)\n", + "Requirement already satisfied: nvidia-cudnn-cu12==9.10.2.21 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (9.10.2.21)\n", + "Requirement already satisfied: nvidia-cublas-cu12==12.8.4.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.4.1)\n", + "Requirement already satisfied: nvidia-cufft-cu12==11.3.3.83 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (11.3.3.83)\n", + "Requirement already satisfied: nvidia-curand-cu12==10.3.9.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (10.3.9.90)\n", + "Requirement already satisfied: nvidia-cusolver-cu12==11.7.3.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (11.7.3.90)\n", + "Requirement already satisfied: nvidia-cusparse-cu12==12.5.8.93 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.5.8.93)\n", + "Requirement already satisfied: nvidia-cusparselt-cu12==0.7.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (0.7.1)\n", + "Requirement already satisfied: nvidia-nccl-cu12==2.27.5 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (2.27.5)\n", + "Requirement already satisfied: nvidia-nvshmem-cu12==3.3.20 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (3.3.20)\n", + "Requirement already satisfied: nvidia-nvtx-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.90)\n", + "Requirement already satisfied: nvidia-nvjitlink-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.93)\n", + "Requirement already satisfied: nvidia-cufile-cu12==1.13.1.3 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (1.13.1.3)\n", + "Requirement already satisfied: triton==3.5.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (3.5.1)\n", + "Requirement already satisfied: mpmath<1.4,>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from sympy>=1.13.3->torch>=2.0->llmrouter-lib==0.2.0) (1.3.0)\n", + "Requirement already satisfied: pyparsing in /opt/conda/lib/python3.13/site-packages (from torch-geometric>=2.3->llmrouter-lib==0.2.0) (3.3.1)\n", + "Collecting huggingface-hub<2.0,>=0.25.0 (from datasets>=2.14->llmrouter-lib==0.2.0)\n", + " Using cached huggingface_hub-0.36.0-py3-none-any.whl.metadata (14 kB)\n", + "Using cached datasets-4.5.0-py3-none-any.whl (515 kB)\n", + "Using cached fsspec-2025.10.0-py3-none-any.whl (200 kB)\n", + "Using cached hf_xet-1.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.3 MB)\n", + "Using cached multiprocess-0.70.18-py313-none-any.whl (151 kB)\n", + "Using cached aiohttp-3.13.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (1.7 MB)\n", + "Using cached multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (254 kB)\n", + "Using cached yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (377 kB)\n", + "Using cached aiohappyeyeballs-2.6.1-py3-none-any.whl (15 kB)\n", + "Using cached aiosignal-1.4.0-py3-none-any.whl (7.5 kB)\n", + "Using cached frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (234 kB)\n", + "Using cached gradio-6.3.0-py3-none-any.whl (23.0 MB)\n", + "Using cached gradio_client-2.0.3-py3-none-any.whl (55 kB)\n", + "Using cached aiofiles-24.1.0-py3-none-any.whl (15 kB)\n", + "Using cached audioop_lts-0.2.2-cp313-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (85 kB)\n", + "Using cached fastapi-0.128.0-py3-none-any.whl (103 kB)\n", + "Using cached groovy-0.1.2-py3-none-any.whl (14 kB)\n", + "Using cached orjson-3.11.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (138 kB)\n", + "Using cached safehttpx-0.1.7-py3-none-any.whl (9.0 kB)\n", + "Using cached semantic_version-2.10.0-py2.py3-none-any.whl (15 kB)\n", + "Using cached starlette-0.50.0-py3-none-any.whl (74 kB)\n", + "Using cached tomlkit-0.13.3-py3-none-any.whl (38 kB)\n", + "Using cached typer-0.21.1-py3-none-any.whl (47 kB)\n", + "Using cached annotated_doc-0.0.4-py3-none-any.whl (5.3 kB)\n", + "Using cached litellm-1.81.0-py3-none-any.whl (11.8 MB)\n", + "Using cached fastuuid-0.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (278 kB)\n", + "Using cached grpcio-1.76.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (6.6 MB)\n", + "Using cached openai-2.15.0-py3-none-any.whl (1.1 MB)\n", + "Using cached jiter-0.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (361 kB)\n", + "Using cached peft-0.18.1-py3-none-any.whl (556 kB)\n", + "Using cached accelerate-1.12.0-py3-none-any.whl (380 kB)\n", + "Using cached propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (204 kB)\n", + "Using cached python_dotenv-1.2.1-py3-none-any.whl (21 kB)\n", + "Using cached python_multipart-0.0.21-py3-none-any.whl (24 kB)\n", + "Using cached rich-14.2.0-py3-none-any.whl (243 kB)\n", + "Using cached markdown_it_py-4.0.0-py3-none-any.whl (87 kB)\n", + "Using cached mdurl-0.1.2-py3-none-any.whl (10.0 kB)\n", + "Using cached safetensors-0.7.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (507 kB)\n", + "Using cached sentencepiece-0.2.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (1.4 MB)\n", + "Using cached shellingham-1.5.4-py2.py3-none-any.whl (9.8 kB)\n", + "Using cached tiktoken-0.12.0-cp313-cp313-manylinux_2_28_x86_64.whl (1.2 MB)\n", + "Using cached regex-2026.1.15-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (803 kB)\n", + "Using cached torch_geometric-2.7.0-py3-none-any.whl (1.3 MB)\n", + "Using cached transformers-4.57.6-py3-none-any.whl (12.0 MB)\n", + "Using cached huggingface_hub-0.36.0-py3-none-any.whl (566 kB)\n", + "Using cached tokenizers-0.22.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.3 MB)\n", + "Using cached uvicorn-0.40.0-py3-none-any.whl (68 kB)\n", + "Using cached ffmpy-1.0.0-py3-none-any.whl (5.6 kB)\n", + "Using cached pydub-0.25.1-py2.py3-none-any.whl (32 kB)\n", + "Using cached xxhash-3.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (193 kB)\n", + "Building wheels for collected packages: llmrouter-lib\n", + " Building editable for llmrouter-lib (pyproject.toml) ... \u001b[?25ldone\n", + "\u001b[?25h Created wheel for llmrouter-lib: filename=llmrouter_lib-0.2.0-0.editable-py3-none-any.whl size=15709 sha256=0e49b819d4ef787227a2e79cf56924a2ffe6971a749893cfbfbc18e42f228961\n", + " Stored in directory: /tmp/pip-ephem-wheel-cache-dx45x6v7/wheels/82/4a/fd/59c4aec93c356c380d86a88ab74bece164c0aa855d68b155e4\n", + "Successfully built llmrouter-lib\n", + "Installing collected packages: pydub, xxhash, uvicorn, tomlkit, shellingham, sentencepiece, semantic-version, safetensors, regex, python-multipart, python-dotenv, propcache, orjson, multiprocess, multidict, mdurl, jiter, hf-xet, grpcio, groovy, fsspec, frozenlist, ffmpy, fastuuid, audioop-lts, annotated-doc, aiohappyeyeballs, aiofiles, yarl, tiktoken, starlette, markdown-it-py, huggingface-hub, aiosignal, tokenizers, safehttpx, rich, openai, gradio-client, fastapi, aiohttp, typer, transformers, torch-geometric, litellm, accelerate, peft, gradio, datasets, llmrouter-lib\n", + "\u001b[2K Attempting uninstall: fsspecm╺\u001b[0m\u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m18/50\u001b[0m [grpcio]ocess]]\n", + "\u001b[2K Found existing installation: fsspec 2025.12.0━━━━━━━━━━━━━\u001b[0m \u001b[32m18/50\u001b[0m [grpcio]\n", + "\u001b[2K Uninstalling fsspec-2025.12.0:\u001b[0m\u001b[90m━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m20/50\u001b[0m [fsspec]\n", + "\u001b[2K Successfully uninstalled fsspec-2025.12.0━━━━━━━━━━━━━━━\u001b[0m \u001b[32m20/50\u001b[0m [fsspec]\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m50/50\u001b[0m [llmrouter-lib]0m [datasets]e]tric]\n", + "\u001b[1A\u001b[2KSuccessfully installed accelerate-1.12.0 aiofiles-24.1.0 aiohappyeyeballs-2.6.1 aiohttp-3.13.3 aiosignal-1.4.0 annotated-doc-0.0.4 audioop-lts-0.2.2 datasets-4.5.0 fastapi-0.128.0 fastuuid-0.14.0 ffmpy-1.0.0 frozenlist-1.8.0 fsspec-2025.10.0 gradio-6.3.0 gradio-client-2.0.3 groovy-0.1.2 grpcio-1.76.0 hf-xet-1.2.0 huggingface-hub-0.36.0 jiter-0.12.0 litellm-1.81.0 llmrouter-lib-0.2.0 markdown-it-py-4.0.0 mdurl-0.1.2 multidict-6.7.0 multiprocess-0.70.18 openai-2.15.0 orjson-3.11.5 peft-0.18.1 propcache-0.4.1 pydub-0.25.1 python-dotenv-1.2.1 python-multipart-0.0.21 regex-2026.1.15 rich-14.2.0 safehttpx-0.1.7 safetensors-0.7.0 semantic-version-2.10.0 sentencepiece-0.2.1 shellingham-1.5.4 starlette-0.50.0 tiktoken-0.12.0 tokenizers-0.22.2 tomlkit-0.13.3 torch-geometric-2.7.0 transformers-4.57.6 typer-0.21.1 uvicorn-0.40.0 xxhash-3.6.0 yarl-1.22.0\n", + "Requirement already satisfied: transformers in /opt/conda/lib/python3.13/site-packages (4.57.6)\n", + "Requirement already satisfied: torch in /opt/conda/lib/python3.13/site-packages (2.9.1)\n", + "Requirement already satisfied: matplotlib in /opt/conda/lib/python3.13/site-packages (3.10.8)\n", + "Requirement already satisfied: filelock in /opt/conda/lib/python3.13/site-packages (from transformers) (3.20.2)\n", + "Requirement already satisfied: huggingface-hub<1.0,>=0.34.0 in /opt/conda/lib/python3.13/site-packages (from transformers) (0.36.0)\n", + "Requirement already satisfied: numpy>=1.17 in /opt/conda/lib/python3.13/site-packages (from transformers) (2.3.5)\n", + "Requirement already satisfied: packaging>=20.0 in /opt/conda/lib/python3.13/site-packages (from transformers) (25.0)\n", + "Requirement already satisfied: pyyaml>=5.1 in /opt/conda/lib/python3.13/site-packages (from transformers) (6.0.3)\n", + "Requirement already satisfied: regex!=2019.12.17 in /opt/conda/lib/python3.13/site-packages (from transformers) (2026.1.15)\n", + "Requirement already satisfied: requests in /opt/conda/lib/python3.13/site-packages (from transformers) (2.32.5)\n", + "Requirement already satisfied: tokenizers<=0.23.0,>=0.22.0 in /opt/conda/lib/python3.13/site-packages (from transformers) (0.22.2)\n", + "Requirement already satisfied: safetensors>=0.4.3 in /opt/conda/lib/python3.13/site-packages (from transformers) (0.7.0)\n", + "Requirement already satisfied: tqdm>=4.27 in /opt/conda/lib/python3.13/site-packages (from transformers) (4.67.1)\n", + "Requirement already satisfied: fsspec>=2023.5.0 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (2025.10.0)\n", + "Requirement already satisfied: typing-extensions>=3.7.4.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (4.15.0)\n", + "Requirement already satisfied: hf-xet<2.0.0,>=1.1.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (1.2.0)\n", + "Requirement already satisfied: setuptools in /opt/conda/lib/python3.13/site-packages (from torch) (80.9.0)\n", + "Requirement already satisfied: sympy>=1.13.3 in /opt/conda/lib/python3.13/site-packages (from torch) (1.14.0)\n", + "Requirement already satisfied: networkx>=2.5.1 in /opt/conda/lib/python3.13/site-packages (from torch) (3.6.1)\n", + "Requirement already satisfied: jinja2 in /opt/conda/lib/python3.13/site-packages (from torch) (3.1.6)\n", + "Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.93)\n", + "Requirement already satisfied: nvidia-cuda-runtime-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-cuda-cupti-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-cudnn-cu12==9.10.2.21 in /opt/conda/lib/python3.13/site-packages (from torch) (9.10.2.21)\n", + "Requirement already satisfied: nvidia-cublas-cu12==12.8.4.1 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.4.1)\n", + "Requirement already satisfied: nvidia-cufft-cu12==11.3.3.83 in /opt/conda/lib/python3.13/site-packages (from torch) (11.3.3.83)\n", + "Requirement already satisfied: nvidia-curand-cu12==10.3.9.90 in /opt/conda/lib/python3.13/site-packages (from torch) (10.3.9.90)\n", + "Requirement already satisfied: nvidia-cusolver-cu12==11.7.3.90 in /opt/conda/lib/python3.13/site-packages (from torch) (11.7.3.90)\n", + "Requirement already satisfied: nvidia-cusparse-cu12==12.5.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.5.8.93)\n", + "Requirement already satisfied: nvidia-cusparselt-cu12==0.7.1 in /opt/conda/lib/python3.13/site-packages (from torch) (0.7.1)\n", + "Requirement already satisfied: nvidia-nccl-cu12==2.27.5 in /opt/conda/lib/python3.13/site-packages (from torch) (2.27.5)\n", + "Requirement already satisfied: nvidia-nvshmem-cu12==3.3.20 in /opt/conda/lib/python3.13/site-packages (from torch) (3.3.20)\n", + "Requirement already satisfied: nvidia-nvtx-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-nvjitlink-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.93)\n", + "Requirement already satisfied: nvidia-cufile-cu12==1.13.1.3 in /opt/conda/lib/python3.13/site-packages (from torch) (1.13.1.3)\n", + "Requirement already satisfied: triton==3.5.1 in /opt/conda/lib/python3.13/site-packages (from torch) (3.5.1)\n", + "Requirement already satisfied: contourpy>=1.0.1 in /opt/conda/lib/python3.13/site-packages (from matplotlib) (1.3.3)\n", + "Requirement already satisfied: cycler>=0.10 in /opt/conda/lib/python3.13/site-packages (from matplotlib) (0.12.1)\n", + "Requirement already satisfied: fonttools>=4.22.0 in /opt/conda/lib/python3.13/site-packages (from matplotlib) (4.61.1)\n", + "Requirement already satisfied: kiwisolver>=1.3.1 in /opt/conda/lib/python3.13/site-packages (from matplotlib) (1.4.9)\n", + "Requirement already satisfied: pillow>=8 in /opt/conda/lib/python3.13/site-packages (from matplotlib) (12.1.0)\n", + "Requirement already satisfied: pyparsing>=3 in /opt/conda/lib/python3.13/site-packages (from matplotlib) (3.3.1)\n", + "Requirement already satisfied: python-dateutil>=2.7 in /opt/conda/lib/python3.13/site-packages (from matplotlib) (2.9.0.post0)\n", + "Requirement already satisfied: six>=1.5 in /opt/conda/lib/python3.13/site-packages (from python-dateutil>=2.7->matplotlib) (1.17.0)\n", + "Requirement already satisfied: mpmath<1.4,>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from sympy>=1.13.3->torch) (1.3.0)\n", + "Requirement already satisfied: MarkupSafe>=2.0 in /opt/conda/lib/python3.13/site-packages (from jinja2->torch) (3.0.3)\n", + "Requirement already satisfied: charset_normalizer<4,>=2 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (3.4.4)\n", + "Requirement already satisfied: idna<4,>=2.5 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (3.11)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (2.6.2)\n", + "Requirement already satisfied: certifi>=2017.4.17 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (2026.1.4)\n" + ] + } + ], + "source": [ + "# Install required packages (for Colab)\n", + "!git clone https://github.com/ulab-uiuc/LLMRouter.git\n", + "%cd LLMRouter\n", + "!pip install -e .\n", + "!pip install transformers torch matplotlib" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "os.environ['OPENAI_API_KEY'] = 'your-key'\n", + "os.environ['ANTHROPIC_API_KEY'] = 'your-key'\n", + "# Or for multiple keys:\n", + "os.environ['API_KEYS'] = '[\"key1\", \"key2\"]'" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using device: cuda\n", + "GPU: NVIDIA A100-SXM4-80GB\n" + ] + } + ], + "source": [ + "import torch\n", + "from llmrouter.models.routerdc import DCRouter, DCTrainer \n", + "from llmrouter.utils import setup_environment\n", + "\n", + "setup_environment()\n", + "\n", + "# Check GPU availability\n", + "device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n", + "print(f\"Using device: {device}\")\n", + "if device == \"cuda\":\n", + " print(f\"GPU: {torch.cuda.get_device_name(0)}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Configuration\n", + "\n", + "DCRouter uses the following configuration parameters:\n", + "\n", + "| Parameter | Description | Default |\n", + "|-----------|-------------|--------|\n", + "| `hidden_state_dim` | Backbone hidden dimension | 768 |\n", + "| `similarity_function` | Similarity metric | \"cos\" |\n", + "| `batch_size` | Training batch size | 32 |\n", + "| `training_steps` | Total training steps | 500 |\n", + "| `learning_rate` | Learning rate | 5e-5 |\n", + "| `top_k` | Top-k LLMs for positive samples | 3 |\n", + "| `last_k` | Last-k LLMs for negative samples | 3 |\n", + "| `temperature` | Softmax temperature | 1.0 |" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Current Configuration:\n", + "==================================================\n", + "data_path:\n", + " llm_data: data/example_data/llm_candidates/default_llm.json\n", + " llm_embedding_data: data/example_data/llm_candidates/default_llm_embeddings.json\n", + " preprocessed_dir: data/dcrouter_preprocessed\n", + " query_data_test: data/example_data/query_data/default_query_test.jsonl\n", + " query_data_train: data/example_data/query_data/default_query_train.jsonl\n", + " routing_data_test: data/example_data/routing_data/default_routing_test_data.jsonl\n", + " routing_data_train: data/example_data/routing_data/default_routing_train_data.jsonl\n", + "hparam:\n", + " H: 3\n", + " batch_size: 32\n", + " cluster_loss_weight: 1.0\n", + " device: cpu\n", + " eval_steps: 50\n", + " gradient_accumulation: 1\n", + " hidden_state_dim: 768\n", + " inference_batch_size: 64\n", + " inference_temperature: 1.0\n", + " last_k: 3\n", + " learning_rate: 5.0e-05\n", + " max_test_samples: 500\n", + " n_clusters: 3\n", + " sample_loss_weight: 0.0\n", + " seed: 1\n", + " similarity_function: cos\n", + " source_max_token_len: 512\n", + " target_max_token_len: 512\n", + " temperature: 1.0\n", + " top_k: 3\n", + " training_steps: 500\n", + "metric:\n", + " weights:\n", + " cost: 0\n", + " llm_judge: 0\n", + " performance: 1\n", + "model_path:\n", + " backbone_model: microsoft/mdeberta-v3-base\n", + " ini_model_path: ''\n", + " save_model_path: saved_models/dcrouter/dcrouter_model.pth\n", + "\n" + ] + } + ], + "source": [ + "import yaml\n", + "\n", + "CONFIG_PATH = \"configs/model_config_train/dcrouter.yaml\"\n", + "\n", + "with open(CONFIG_PATH, 'r') as f:\n", + " config = yaml.safe_load(f)\n", + "\n", + "print(\"Current Configuration:\")\n", + "print(\"=\" * 50)\n", + "print(yaml.dump(config, default_flow_style=False))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Initialize Router" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Router initialized successfully!\n", + "Number of training samples: 50544\n", + "Number of LLM candidates: 7\n", + "LLM candidates: ['qwen2.5-7b-instruct', 'llama-3.1-8b-instruct', 'mistral-7b-instruct-v0.3', 'llama-3.3-nemotron-super-49b-v1', 'llama3-70b-instruct', 'mixtral-8x7b-instruct-v0.1', 'mixtral-8x22b-instruct-v0.1']\n", + "Backbone model: microsoft/mdeberta-v3-base\n" + ] + } + ], + "source": [ + "# Initialize DCRouter with configuration\n", + "router = DCRouter(yaml_path=CONFIG_PATH)\n", + "\n", + "print(\"Router initialized successfully!\")\n", + "print(f\"Number of training samples: {len(router.routing_data_train)}\")\n", + "print(f\"Number of LLM candidates: {len(router.llm_data)}\")\n", + "print(f\"LLM candidates: {list(router.llm_data.keys())}\")\n", + "print(f\"Backbone model: {config['model_path'].get('backbone_model', 'microsoft/mdeberta-v3-base')}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Training" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Trainer initialized!\n", + "Device: cuda\n", + "Save dir: /home/zhongjie/LLMRouter/saved_models/dcrouter\n", + "Final model path: /home/zhongjie/LLMRouter/saved_models/dcrouter/dcrouter_model.pth\n" + ] + } + ], + "source": [ + "# Initialize trainer\n", + "trainer = DCTrainer(router=router, device=device)\n", + "\n", + "print(\"Trainer initialized!\")\n", + "print(f\"Device: {device}\")\n", + "print(f\"Save dir: {trainer.save_dir}\")\n", + "print(f\"Final model path: {trainer.final_model_path}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting training...\n", + "==================================================\n", + "Note: DCRouter training uses dual contrastive learning.\n", + "This may take some time depending on your hardware.\n", + "==================================================\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Training: 100%|██████████| 500/500 [17:54<00:00, 2.15s/it, step=499, loss=3.4797] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "==================================================\n", + "Training completed!\n" + ] + } + ], + "source": [ + "# Train the model\n", + "print(\"Starting training...\")\n", + "print(\"=\" * 50)\n", + "print(\"Note: DCRouter training uses dual contrastive learning.\")\n", + "print(\"This may take some time depending on your hardware.\")\n", + "print(\"=\" * 50)\n", + "\n", + "trainer.train()\n", + "\n", + "print(\"=\" * 50)\n", + "print(\"Training completed!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Model Verification" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Model loaded from: /home/zhongjie/LLMRouter/saved_models/dcrouter/dcrouter_model.pth\n", + "Checkpoint keys: odict_keys(['backbone.embeddings.word_embeddings.weight', 'backbone.embeddings.LayerNorm.weight', 'backbone.embeddings.LayerNorm.bias', 'backbone.encoder.layer.0.attention.self.query_proj.weight', 'backbone.encoder.layer.0.attention.self.query_proj.bias', 'backbone.encoder.layer.0.attention.self.key_proj.weight', 'backbone.encoder.layer.0.attention.self.key_proj.bias', 'backbone.encoder.layer.0.attention.self.value_proj.weight', 'backbone.encoder.layer.0.attention.self.value_proj.bias', 'backbone.encoder.layer.0.attention.output.dense.weight', 'backbone.encoder.layer.0.attention.output.dense.bias', 'backbone.encoder.layer.0.attention.output.LayerNorm.weight', 'backbone.encoder.layer.0.attention.output.LayerNorm.bias', 'backbone.encoder.layer.0.intermediate.dense.weight', 'backbone.encoder.layer.0.intermediate.dense.bias', 'backbone.encoder.layer.0.output.dense.weight', 'backbone.encoder.layer.0.output.dense.bias', 'backbone.encoder.layer.0.output.LayerNorm.weight', 'backbone.encoder.layer.0.output.LayerNorm.bias', 'backbone.encoder.layer.1.attention.self.query_proj.weight', 'backbone.encoder.layer.1.attention.self.query_proj.bias', 'backbone.encoder.layer.1.attention.self.key_proj.weight', 'backbone.encoder.layer.1.attention.self.key_proj.bias', 'backbone.encoder.layer.1.attention.self.value_proj.weight', 'backbone.encoder.layer.1.attention.self.value_proj.bias', 'backbone.encoder.layer.1.attention.output.dense.weight', 'backbone.encoder.layer.1.attention.output.dense.bias', 'backbone.encoder.layer.1.attention.output.LayerNorm.weight', 'backbone.encoder.layer.1.attention.output.LayerNorm.bias', 'backbone.encoder.layer.1.intermediate.dense.weight', 'backbone.encoder.layer.1.intermediate.dense.bias', 'backbone.encoder.layer.1.output.dense.weight', 'backbone.encoder.layer.1.output.dense.bias', 'backbone.encoder.layer.1.output.LayerNorm.weight', 'backbone.encoder.layer.1.output.LayerNorm.bias', 'backbone.encoder.layer.2.attention.self.query_proj.weight', 'backbone.encoder.layer.2.attention.self.query_proj.bias', 'backbone.encoder.layer.2.attention.self.key_proj.weight', 'backbone.encoder.layer.2.attention.self.key_proj.bias', 'backbone.encoder.layer.2.attention.self.value_proj.weight', 'backbone.encoder.layer.2.attention.self.value_proj.bias', 'backbone.encoder.layer.2.attention.output.dense.weight', 'backbone.encoder.layer.2.attention.output.dense.bias', 'backbone.encoder.layer.2.attention.output.LayerNorm.weight', 'backbone.encoder.layer.2.attention.output.LayerNorm.bias', 'backbone.encoder.layer.2.intermediate.dense.weight', 'backbone.encoder.layer.2.intermediate.dense.bias', 'backbone.encoder.layer.2.output.dense.weight', 'backbone.encoder.layer.2.output.dense.bias', 'backbone.encoder.layer.2.output.LayerNorm.weight', 'backbone.encoder.layer.2.output.LayerNorm.bias', 'backbone.encoder.layer.3.attention.self.query_proj.weight', 'backbone.encoder.layer.3.attention.self.query_proj.bias', 'backbone.encoder.layer.3.attention.self.key_proj.weight', 'backbone.encoder.layer.3.attention.self.key_proj.bias', 'backbone.encoder.layer.3.attention.self.value_proj.weight', 'backbone.encoder.layer.3.attention.self.value_proj.bias', 'backbone.encoder.layer.3.attention.output.dense.weight', 'backbone.encoder.layer.3.attention.output.dense.bias', 'backbone.encoder.layer.3.attention.output.LayerNorm.weight', 'backbone.encoder.layer.3.attention.output.LayerNorm.bias', 'backbone.encoder.layer.3.intermediate.dense.weight', 'backbone.encoder.layer.3.intermediate.dense.bias', 'backbone.encoder.layer.3.output.dense.weight', 'backbone.encoder.layer.3.output.dense.bias', 'backbone.encoder.layer.3.output.LayerNorm.weight', 'backbone.encoder.layer.3.output.LayerNorm.bias', 'backbone.encoder.layer.4.attention.self.query_proj.weight', 'backbone.encoder.layer.4.attention.self.query_proj.bias', 'backbone.encoder.layer.4.attention.self.key_proj.weight', 'backbone.encoder.layer.4.attention.self.key_proj.bias', 'backbone.encoder.layer.4.attention.self.value_proj.weight', 'backbone.encoder.layer.4.attention.self.value_proj.bias', 'backbone.encoder.layer.4.attention.output.dense.weight', 'backbone.encoder.layer.4.attention.output.dense.bias', 'backbone.encoder.layer.4.attention.output.LayerNorm.weight', 'backbone.encoder.layer.4.attention.output.LayerNorm.bias', 'backbone.encoder.layer.4.intermediate.dense.weight', 'backbone.encoder.layer.4.intermediate.dense.bias', 'backbone.encoder.layer.4.output.dense.weight', 'backbone.encoder.layer.4.output.dense.bias', 'backbone.encoder.layer.4.output.LayerNorm.weight', 'backbone.encoder.layer.4.output.LayerNorm.bias', 'backbone.encoder.layer.5.attention.self.query_proj.weight', 'backbone.encoder.layer.5.attention.self.query_proj.bias', 'backbone.encoder.layer.5.attention.self.key_proj.weight', 'backbone.encoder.layer.5.attention.self.key_proj.bias', 'backbone.encoder.layer.5.attention.self.value_proj.weight', 'backbone.encoder.layer.5.attention.self.value_proj.bias', 'backbone.encoder.layer.5.attention.output.dense.weight', 'backbone.encoder.layer.5.attention.output.dense.bias', 'backbone.encoder.layer.5.attention.output.LayerNorm.weight', 'backbone.encoder.layer.5.attention.output.LayerNorm.bias', 'backbone.encoder.layer.5.intermediate.dense.weight', 'backbone.encoder.layer.5.intermediate.dense.bias', 'backbone.encoder.layer.5.output.dense.weight', 'backbone.encoder.layer.5.output.dense.bias', 'backbone.encoder.layer.5.output.LayerNorm.weight', 'backbone.encoder.layer.5.output.LayerNorm.bias', 'backbone.encoder.layer.6.attention.self.query_proj.weight', 'backbone.encoder.layer.6.attention.self.query_proj.bias', 'backbone.encoder.layer.6.attention.self.key_proj.weight', 'backbone.encoder.layer.6.attention.self.key_proj.bias', 'backbone.encoder.layer.6.attention.self.value_proj.weight', 'backbone.encoder.layer.6.attention.self.value_proj.bias', 'backbone.encoder.layer.6.attention.output.dense.weight', 'backbone.encoder.layer.6.attention.output.dense.bias', 'backbone.encoder.layer.6.attention.output.LayerNorm.weight', 'backbone.encoder.layer.6.attention.output.LayerNorm.bias', 'backbone.encoder.layer.6.intermediate.dense.weight', 'backbone.encoder.layer.6.intermediate.dense.bias', 'backbone.encoder.layer.6.output.dense.weight', 'backbone.encoder.layer.6.output.dense.bias', 'backbone.encoder.layer.6.output.LayerNorm.weight', 'backbone.encoder.layer.6.output.LayerNorm.bias', 'backbone.encoder.layer.7.attention.self.query_proj.weight', 'backbone.encoder.layer.7.attention.self.query_proj.bias', 'backbone.encoder.layer.7.attention.self.key_proj.weight', 'backbone.encoder.layer.7.attention.self.key_proj.bias', 'backbone.encoder.layer.7.attention.self.value_proj.weight', 'backbone.encoder.layer.7.attention.self.value_proj.bias', 'backbone.encoder.layer.7.attention.output.dense.weight', 'backbone.encoder.layer.7.attention.output.dense.bias', 'backbone.encoder.layer.7.attention.output.LayerNorm.weight', 'backbone.encoder.layer.7.attention.output.LayerNorm.bias', 'backbone.encoder.layer.7.intermediate.dense.weight', 'backbone.encoder.layer.7.intermediate.dense.bias', 'backbone.encoder.layer.7.output.dense.weight', 'backbone.encoder.layer.7.output.dense.bias', 'backbone.encoder.layer.7.output.LayerNorm.weight', 'backbone.encoder.layer.7.output.LayerNorm.bias', 'backbone.encoder.layer.8.attention.self.query_proj.weight', 'backbone.encoder.layer.8.attention.self.query_proj.bias', 'backbone.encoder.layer.8.attention.self.key_proj.weight', 'backbone.encoder.layer.8.attention.self.key_proj.bias', 'backbone.encoder.layer.8.attention.self.value_proj.weight', 'backbone.encoder.layer.8.attention.self.value_proj.bias', 'backbone.encoder.layer.8.attention.output.dense.weight', 'backbone.encoder.layer.8.attention.output.dense.bias', 'backbone.encoder.layer.8.attention.output.LayerNorm.weight', 'backbone.encoder.layer.8.attention.output.LayerNorm.bias', 'backbone.encoder.layer.8.intermediate.dense.weight', 'backbone.encoder.layer.8.intermediate.dense.bias', 'backbone.encoder.layer.8.output.dense.weight', 'backbone.encoder.layer.8.output.dense.bias', 'backbone.encoder.layer.8.output.LayerNorm.weight', 'backbone.encoder.layer.8.output.LayerNorm.bias', 'backbone.encoder.layer.9.attention.self.query_proj.weight', 'backbone.encoder.layer.9.attention.self.query_proj.bias', 'backbone.encoder.layer.9.attention.self.key_proj.weight', 'backbone.encoder.layer.9.attention.self.key_proj.bias', 'backbone.encoder.layer.9.attention.self.value_proj.weight', 'backbone.encoder.layer.9.attention.self.value_proj.bias', 'backbone.encoder.layer.9.attention.output.dense.weight', 'backbone.encoder.layer.9.attention.output.dense.bias', 'backbone.encoder.layer.9.attention.output.LayerNorm.weight', 'backbone.encoder.layer.9.attention.output.LayerNorm.bias', 'backbone.encoder.layer.9.intermediate.dense.weight', 'backbone.encoder.layer.9.intermediate.dense.bias', 'backbone.encoder.layer.9.output.dense.weight', 'backbone.encoder.layer.9.output.dense.bias', 'backbone.encoder.layer.9.output.LayerNorm.weight', 'backbone.encoder.layer.9.output.LayerNorm.bias', 'backbone.encoder.layer.10.attention.self.query_proj.weight', 'backbone.encoder.layer.10.attention.self.query_proj.bias', 'backbone.encoder.layer.10.attention.self.key_proj.weight', 'backbone.encoder.layer.10.attention.self.key_proj.bias', 'backbone.encoder.layer.10.attention.self.value_proj.weight', 'backbone.encoder.layer.10.attention.self.value_proj.bias', 'backbone.encoder.layer.10.attention.output.dense.weight', 'backbone.encoder.layer.10.attention.output.dense.bias', 'backbone.encoder.layer.10.attention.output.LayerNorm.weight', 'backbone.encoder.layer.10.attention.output.LayerNorm.bias', 'backbone.encoder.layer.10.intermediate.dense.weight', 'backbone.encoder.layer.10.intermediate.dense.bias', 'backbone.encoder.layer.10.output.dense.weight', 'backbone.encoder.layer.10.output.dense.bias', 'backbone.encoder.layer.10.output.LayerNorm.weight', 'backbone.encoder.layer.10.output.LayerNorm.bias', 'backbone.encoder.layer.11.attention.self.query_proj.weight', 'backbone.encoder.layer.11.attention.self.query_proj.bias', 'backbone.encoder.layer.11.attention.self.key_proj.weight', 'backbone.encoder.layer.11.attention.self.key_proj.bias', 'backbone.encoder.layer.11.attention.self.value_proj.weight', 'backbone.encoder.layer.11.attention.self.value_proj.bias', 'backbone.encoder.layer.11.attention.output.dense.weight', 'backbone.encoder.layer.11.attention.output.dense.bias', 'backbone.encoder.layer.11.attention.output.LayerNorm.weight', 'backbone.encoder.layer.11.attention.output.LayerNorm.bias', 'backbone.encoder.layer.11.intermediate.dense.weight', 'backbone.encoder.layer.11.intermediate.dense.bias', 'backbone.encoder.layer.11.output.dense.weight', 'backbone.encoder.layer.11.output.dense.bias', 'backbone.encoder.layer.11.output.LayerNorm.weight', 'backbone.encoder.layer.11.output.LayerNorm.bias', 'backbone.encoder.rel_embeddings.weight', 'backbone.encoder.LayerNorm.weight', 'backbone.encoder.LayerNorm.bias', 'embeddings.weight'])\n" + ] + } + ], + "source": [ + "# Verify the trained model\n", + "import torch\n", + "\n", + "# Load saved model\n", + "model_path = trainer.final_model_path\n", + "if os.path.exists(model_path):\n", + " checkpoint = torch.load(model_path, map_location='cpu')\n", + " print(f\"Model loaded from: {model_path}\")\n", + " print(f\"Checkpoint keys: {checkpoint.keys() if isinstance(checkpoint, dict) else 'state_dict'}\")\n", + "else:\n", + " print(f\"Model not found at: {model_path}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Test query: What is the capital of France?\n", + "Routed to: llama-3.1-8b-instruct\n" + ] + } + ], + "source": [ + "# Test prediction\n", + "test_query = {\"query\": \"What is the capital of France?\"}\n", + "result = router.route_single(test_query)\n", + "\n", + "print(f\"Test query: {test_query['query']}\")\n", + "print(f\"Routed to: {result['model_name']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. Training Curve Analysis" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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DJCI3ywVC2Fu3rx58Kc//Qlmz57te97TTz8thLkHH3wQXn31VfjMZz4DRx11FLz++uvKeSjorVixQvmrqqqComHmDIe7ewfzXyEA6B0YhO6+ARguDA3laNKlDF+Eglo6eob1eRnR5sk4sXRBRs8jRZgb0Q4QMRpPDEYuUfDZp7OzE0488UT485//DBdffLHvuVdddZXy/Ve/+hXcd9998J///Ad23HFH5YWdNGkSFK3NnOF4yTDNQbtc/Di09wzAuxd9FqrKS4eVmctGHaIzfGHmbBTkdr3kfzCmohTevOAQiCtG2/JTrOttTWX+35fhwEhjsljNysg14jhFFZyZO+uss+CII46Agw46KPK1Q0ND0NHRAWPHjvUIiDNmzICpU6fCkUce6WHuijFo8HDsKJHRQkEO8eHqLhgOKEJYjhagsJP3Kx+vF/929w0P68kocgSpWavKoVgQx8UoXxhhsimDET9m7o477oDXXntNqFkzwRVXXAFdXV1w3HHHOcdmzpwp7Oa22247aG9vh6uvvhr22msvWLBgAWyxxRbGcnp7e8WfBF4nhUX8ywew3H5LoDkURvB3KtQlIHUsn6CMYDKZv2enGCT3GBgchKGhkqzLwc9hmEz6fPgZhdnheOaooI4hcaxfPkG7sdDPHlSXmgqXmRsYGISS4aLTM4S9PZNF826EAc4rEsX8HCOlP0YCkoQwGBwczGt/hC23YMLc0qVL4eyzz4ZHH300I3u222+/HS644AKhZm1ubnaO77777uJPAgW5nXbaCX7/+9/DNddcY3W+uPDCCz3HV69enTfHCeyg1rYO429d3RugpaUFevrdTmxra4OWlsSwqX3Xrl0HLaXqs//2ySWCxfrZIRvnjClcu9ZlAFtaVkNPVWZDsrU1FeMLsWpVC1SUBQuFbW3t5N4too3xpSwpKThhrWBD9walniMdgwMDxoms8M+e9K1LObhCwyfLVsZa7drf329tz/7+AeU37IO4vhth0JHWNojPHR0xGEfZodj7YySgu9tdt1auaoHOjva89QeO2VgLc+jAgC/VnDlzFAkXnRyuvfZawZSVlponwzvvvBNOP/10uOuuuwLVs9i4u+yyCyxevNh6znnnnQfnnnuuwsxNmzbN8ZrN1wtZvdysyiyvqBQCajtJ39TU1KgIrfkATZuEquvm5nrlt38teFV8/tER28G0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/CK5zTbbJFesWOH8tbS0xLo/WJgrAHbddVfR0RQzZ85M/vjHPy5EdUYFdGFuaGgoOWnSJPFSSvT09CQbGhqSf/zjH8X31tZWscCh8C2xbNmyZElJSfLhhx8e5icYWcCJEftk7ty54jv3R+HR1NSUvPHGG7kvCoSOjo7kFltsIRb//fbbzxHm+N0ojDC3/fbbG3+La3+wmnWY0dfXB6+++ioccsghynH8/vzzzw93dUYtPvroI1i5cqXSDxgnaL/99nP6Afupv79fOQdp9W233Zb7Kku0tbWJf8eOHcv9UWAMDg7CHXfcAV1dXULdyu9GYXDWWWfBEUccAQcddJBynPujMFi8eLGY79Ec6stf/jJ8+OGHse4Pzs06zFizZo2YPCdOnKgcx+84QBjDA9nWpn745JNPnHMqKiqgqamJ+yqHQKL03HPPhb333ltMbtwfhcHChQuF8NbT0wO1tbVw7733wqxZs5zFht+N4QMK06+99hrMmzfP8xvPVcOP3XbbDW677TbYcsstYdWqVXDxxRfDnnvuKezi4tofLMwVCGhQqS9w+jFGPPuB+yo7fPvb34Y33ngDnn32We6PAmKrrbaC+fPnQ2trK9x9991wyimnwNy5c53f+d0YHixduhTOPvtsePTRR4VDnA3cH8OHww47zPm83XbbiU3PZpttBrfeeivsvvvusewPVrMOM9CzpbS01COdt7S0eCR9Rv4gPZP8+gHPQbX4+vXrua9yBPTuuv/+++HJJ5+EqVOncn8UEMgcbL755rDzzjsLD8rtt98err76an43hhmoksN5B6MalJWViT8Uqq+55hrxWc5HPFcVDjU1NUKoQ9VrXNcOFuYKMIHiS/vYY48px/E70riM4QHaQeALR/sBXz6cRGU/YD+Vl5cr56xYsQLefPNN7quIwB0pMnL33HMPPPHEE6L9uT/i10cYToHfjeHFgQceKFTeyJLKPxSwTzzxRPF500035bmqwOjt7YV33nkHJk+eHN/3Iy9uFYxQoUluuukmEZrknHPOEaFJPv74Y265HHuHvf766+IPh/rvfvc78VmGgEFvJPRAuueee4R7+QknnGB0L586dWry8ccfF+7lBxxwAIcmyQD/93//J9r6qaeeUtz9u7u7nXO4P4YP5513XvLpp59OfvTRR8k33nhDhCZBT7tHH32U+yIGoN6sCH43hhff//73xVz14YcfJl988cXkkUceKUKRyDU6jv3BwlyBcN111yVnzJiRrKioSO60005OiAZG7vDkk08KIU7/O+WUUxwXc3RBRzfzysrK5L777iteTIoNGzYkv/3tbyfHjh2brK6uFi/1kiVLuJsiwtQP+Iex5yS4P4YPp512mjP/TJgwIXnggQc6ghz3RfyEOX43hhfHp+PGIekyZcqU5DHHHJN86623Yt0fCfxffjg/BoPBYDAYDEa+wTZzDAaDwWAwGEUMFuYYDAaDwWAwihgszDEYDAaDwWAUMViYYzAYDAaDwShisDDHYDAYDAaDUcRgYY7BYDAYDAajiMHCHIPBYDAYDEYRg4U5BoPBYDAYjCIGC3MMBoPBYDAYRQwW5hgMBiMALS0t8M1vfhOmT58OlZWVItH2oYceCi+88IL4PZFIwL///W9uRwaDURCUFea2DAaDUTz44he/CP39/XDrrbfCpptuCqtWrYL//e9/sG7dukJXjcFgMIBzszIYDIYPWltboampCZ566inYb7/9PL9vvPHG8MknnzjfZ8yYAR9//LH4/J///AcuuOACeOutt2DKlClwyimnwE9+8hMoKytzGL0//OEPcP/994vykfH79a9/DV/60pe4TxgMRmiwmpXBYDB8UFtbK/5Qjdrb2+v5fd68eeLfm2++GVasWOF8f+SRR+CrX/0qfPe734W3334bbrjhBrjlllvgkksuUa7/2c9+Jpi/BQsWiPNPOOEEeOedd7hPGAxGaDAzx2AwGAG4++674Rvf+AZs2LABdtppJ8HQffnLX4bZs2enJtJEAu699174/Oc/71yz7777wmGHHQbnnXeec+xvf/sb/PCHP4Tly5c715155plw/fXXO+fsvvvu4h7I2DEYDEYYMDPHYDAYAUDmDAUwVIei4wOqRFHgQqbNhldffRV++ctfOswe/qFAiOxdd3e3c94ee+yhXIffmZljMBhRwA4QDAaDEQJVVVVw8MEHi7+f//zn8PWvfx1+8YtfwNe+9jXj+UNDQ3DhhRfCMcccYyzLD8jYMRgMRlgwM8dgMBgZYNasWdDV1SU+l5eXw+DgoPI7MneLFi2CzTff3PNXUuJOvS+++KJyHX6fOXMm9wmDwQgNZuYYDAbDB2vXrhXepaeddpqwkaurq4NXXnlFeJ0effTRjkcrhirZa6+9RBw69H5F9u7II4+EadOmietRgHvjjTdg4cKFcPHFFzvl33XXXbDzzjvD3nvvDX//+9/h5Zdfhptuuon7hMFghAY7QDAYDIYP0IMVw4s8+uij8MEHH4h4c1JAO//886G6ulqEIDn33HNFSJKNNtrICU2CHq1oN/f6668L9g4ZN1TPou2cmIATCbjuuuuEp+zTTz8tQpNcdtllwrmCwWAwwoKFOQaDwSgQTF6wDAaDERVsM8dgMBgMBoNRxGBhjsFgMBgMBqOIwQ4QDAaDUSAkk0luewaDkTWYmWMwGAwGg8EoYrAwx2AwGAwGg1HEYGGOwWAwGAwGo4jBwhyDwWAwGAxGEYOFOQaDwWAwGIwiBgtzDAaDwWAwGEUMFuYYDAaDwWAwihgszDEYDAaDwWAUMViYYzAYDAaDwYDixf8Ha3hY6igXMpgAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "# If training logs are available, plot them\n", + "if hasattr(trainer, 'loss_history') and trainer.loss_history:\n", + " plt.figure(figsize=(12, 4))\n", + " \n", + " plt.subplot(1, 2, 1)\n", + " plt.plot(trainer.loss_history)\n", + " plt.xlabel('Step')\n", + " plt.ylabel('Loss')\n", + " plt.title('Training Loss')\n", + " plt.grid(True, alpha=0.3)\n", + " \n", + " plt.tight_layout()\n", + " plt.show()\n", + "else:\n", + " print(\"Training history not available for plotting.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "In this notebook, we:\n", + "\n", + "1. **Loaded Configuration**: Set up DCRouter with YAML configuration\n", + "2. **Initialized Router**: Created DCRouter with mDEBERTa backbone\n", + "3. **Trained Model**: Used dual contrastive learning\n", + "4. **Verified Model**: Tested routing with sample queries\n", + "\n", + "**Key Takeaways**:\n", + "- DCRouter uses transformer-based embeddings\n", + "- Contrastive learning helps distinguish good/bad LLM matches\n", + "- GPU training recommended for faster convergence\n", + "\n", + "**Next Steps**:\n", + "- Use next part of notebook for inference\n", + "- Experiment with different temperature values" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# DCRouter - Inference\n", + "\n", + "This part of notebook demonstrates how to use a trained **DCRouter** for inference." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Environment Setup" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Load Trained Router" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Router loaded with 7 LLM candidates\n" + ] + } + ], + "source": [ + "CONFIG_PATH = \"configs/model_config_train/dcrouter.yaml\"\n", + "\n", + "with open(CONFIG_PATH, 'r') as f:\n", + " config = yaml.safe_load(f)\n", + "\n", + "config['model_path']['load_model_path'] = config['model_path']['save_model_path']\n", + "\n", + "INFERENCE_CONFIG_PATH = \"configs/model_config_test/dcrouter_inference.yaml\"\n", + "os.makedirs(os.path.dirname(INFERENCE_CONFIG_PATH), exist_ok=True)\n", + "\n", + "with open(INFERENCE_CONFIG_PATH, 'w') as f:\n", + " yaml.dump(config, f)\n", + "\n", + "router = DCRouter(yaml_path=INFERENCE_CONFIG_PATH)\n", + "print(f\"Router loaded with {len(router.llm_data)} LLM candidates\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Query Routing" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Routing Results:\n", + "======================================================================\n", + "1. What is the capital of France?...\n", + " Routed to: llama-3.1-8b-instruct\n", + "2. Solve the equation: 2x + 5 = 15...\n", + " Routed to: llama-3.1-8b-instruct\n", + "3. Write a Python function to check if a number is prime....\n", + " Routed to: llama-3.1-8b-instruct\n", + "4. Explain quantum computing in simple terms....\n", + " Routed to: llama-3.1-8b-instruct\n", + "5. What are the implications of climate change on agricult...\n", + " Routed to: qwen2.5-7b-instruct\n" + ] + } + ], + "source": [ + "EXAMPLE_QUERIES = [\n", + " {\"query\": \"What is the capital of France?\"},\n", + " {\"query\": \"Solve the equation: 2x + 5 = 15\"},\n", + " {\"query\": \"Write a Python function to check if a number is prime.\"},\n", + " {\"query\": \"Explain quantum computing in simple terms.\"},\n", + " {\"query\": \"What are the implications of climate change on agriculture?\"},\n", + "]\n", + "\n", + "print(\"Routing Results:\")\n", + "print(\"=\" * 70)\n", + "\n", + "for i, query in enumerate(EXAMPLE_QUERIES, 1):\n", + " result = router.route_single(query)\n", + " print(f\"{i}. {query['query'][:55]}...\")\n", + " print(f\" Routed to: {result['model_name']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Batch Routing" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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Dbt06ZL1EtOhW3/99Zfs3r2bebgBpAsUUgOSyKmwqlfmY9NwPXjwYPnpp5/k8ccf964DAMCfOIH7jTfesGJp/fr1s9omoaGhcvz4ce82OXPmtAvVTZo0sSk1ddFWb20Bj95KDgBpEf8XA5LAmQbs4MGDNmbbqVSuNGhrd7jx48fLokWL5Pbbb+c1BgD4Fe3d5dACo2vWrJFly5bJb7/9JsOGDZOBAwdaDRSdCcS5MH348GHJkyeP/P77797ArdXLASCt4/9kwHWI3jVcu8E5gbtu3bpWUbV69ere4jB79+61yub6BYNK5QAAf+ScMz/66CP53//+J/fcc4+35slrr71mrddDhgyx2127dpX8+fPL6NGjbRpOPccSuAGkJ1QvB64jbJ8+fdqmAFPZsmWzLuWNGzeWihUrysSJE2N0Hf/nn38smDuVWQEA8Ffdu3e386RepP72229jzLU9YsQIeffdd6VLly7yzjvveM+zTm8yAEgvaOkGrhG4tSL58OHDvWF66tSpNiXY7NmzpVixYnHGamfNmpXXFADgd+KbQ1uHWmkr9tChQ2XOnDnSoUMHu3itevfuLefPn5cffvhBMmXK5N2HwA0gvaGlG0jEN998I0899ZT07dtXatWqJRMmTLAx25MnT5YWLVrw2gEAECtwb9682XqE6bp69erZupdfftkCuM69refV6BeonYvczPIBIL0idAMJ0DHbTz/9tDz22GP2ZUHHpNWvX9/uO3r0qMydO5fgDQDwe9HDslYn127k586dk4IFC0revHmtgJrq06ePjBkzxsJ327ZtvS3eisANID2jejmQAC3iopXJtahLeHi4jeFu1KiR7Ny508am6Ti1BQsW8PoBAPxa9KJpn3zyiS1aVLR58+ayYsUKWb16td0fEhIiPXv2tPOqsy72YwBAekToBhKgU321b9/e5g7VLwrly5e3eUX1ynzp0qXl5MmT8sILL9h4NAAA/P1C9fbt2+XDDz+UOnXqyPLly61QmhZRa9iwobV8O8F85MiR0rRpU18fMgCkGEI3EG0+0b/++kt2795tgVqVKlXKvkjoFfty5cp5K5JrhVUd771t2zbJkSMHryEAwK/oeO3YtzV067zaGrh1eJYWIdXpwPQ8qnNyayE19eKLL9p2uh4A/AGhG/j/u7XNmzdPGjRoYPOIdurUST777DN7bfSLwW23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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from collections import Counter\n", + "import matplotlib.pyplot as plt\n", + "\n", + "# Route multiple queries\n", + "results = [router.route_single(q) for q in EXAMPLE_QUERIES]\n", + "\n", + "# Show distribution\n", + "model_counts = Counter(r['model_name'] for r in results)\n", + "\n", + "plt.figure(figsize=(10, 5))\n", + "plt.bar(model_counts.keys(), model_counts.values())\n", + "plt.xlabel('LLM Model')\n", + "plt.ylabel('Count')\n", + "plt.title('DCRouter Routing Distribution')\n", + "plt.xticks(rotation=45, ha='right')\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. File-Based Inference\n", + "\n", + "Load queries from a file and save results." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loaded 706 queries from: data/example_data/query_data/default_query_test.jsonl\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Routed 10 queries\n", + "Results saved to: outputs/dcrouter_results.jsonl\n", + "\n", + "Sample results:\n", + " 1. Q: There are 4 houses in a row, numbered... -> llama-3.1-8b-instruct\n", + " 2. Q: There are 3 houses in a row, numbered... -> llama-3.1-8b-instruct\n", + " 3. Q: There are 3 houses in a row, numbered... -> llama-3.1-8b-instruct\n" + ] + } + ], + "source": [ + "import json\n", + "\n", + "# Load queries from a JSONL file\n", + "def load_queries_from_file(file_path):\n", + " \"\"\"Load queries from a JSONL file.\"\"\"\n", + " queries = []\n", + " with open(file_path, 'r', encoding='utf-8') as f:\n", + " for line in f:\n", + " if line.strip():\n", + " queries.append(json.loads(line))\n", + " return queries\n", + "\n", + "# Save results to a JSONL file\n", + "def save_results_to_file(results, output_path):\n", + " \"\"\"Save routing results to a JSONL file.\"\"\"\n", + " os.makedirs(os.path.dirname(output_path), exist_ok=True)\n", + " with open(output_path, 'w', encoding='utf-8') as f:\n", + " for result in results:\n", + " f.write(json.dumps(result, ensure_ascii=False) + '\\n')\n", + " print(f\"Results saved to: {output_path}\")\n", + "\n", + "# Example: Load from default query file\n", + "QUERY_FILE = \"data/example_data/query_data/default_query_test.jsonl\"\n", + "OUTPUT_FILE = \"outputs/dcrouter_results.jsonl\"\n", + "\n", + "if os.path.exists(QUERY_FILE):\n", + " # Load queries\n", + " file_queries = load_queries_from_file(QUERY_FILE)\n", + " print(f\"Loaded {len(file_queries)} queries from: {QUERY_FILE}\")\n", + " \n", + " # Route queries\n", + " file_results = router.route_batch(batch=file_queries[:10])\n", + " print(f\"Routed {len(file_results)} queries\")\n", + " \n", + " # Save results\n", + " save_results_to_file(file_results, OUTPUT_FILE)\n", + " \n", + " # Show sample results\n", + " print(f\"\\nSample results:\")\n", + " for i, result in enumerate(file_results[:3], 1):\n", + " print(f\" {i}. {result.get('query', '')[:40]}... -> {result['model_name']}\")\n", + "else:\n", + " print(f\"Query file not found: {QUERY_FILE}\")\n", + " print(\"Create a JSONL file with format: {\\\"query\\\": \\\"Your question\\\"}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "This notebook demonstrated:\n", + "1. Loading a trained DCRouter\n", + "2. Routing queries using transformer-based embeddings\n", + "3. Batch routing and visualization\n", + "\n", + "DCRouter is effective for:\n", + "- High-accuracy routing with transformer embeddings\n", + "- Distinguishing subtle differences between queries" + ] + } + ], + "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.13.11" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/colab_notebooks/elorouter/01_elorouter_training_and_inference.ipynb b/colab_notebooks/elorouter/01_elorouter_training_and_inference.ipynb new file mode 100644 index 0000000..c3eb166 --- /dev/null +++ b/colab_notebooks/elorouter/01_elorouter_training_and_inference.ipynb @@ -0,0 +1,737 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# EloRouter - Training and Inference\n", + "\n", + "This notebook demonstrates how to use **EloRouter** for training and inference.\n", + "\n", + "## Overview\n", + "\n", + "EloRouter uses Elo ratings to select the best LLM. It's an inference-only router\n", + "that doesn't require training - it uses pre-computed Elo ratings from benchmarks.\n", + "\n", + "**Key Features**:\n", + "- No training required\n", + "- Uses established Elo rating system\n", + "- Simple and interpretable\n", + "- Based on pairwise comparisons\n", + "\n", + "**Note**: EloRouter always selects the highest-rated LLM based on Elo scores." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Environment Setup" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cloning into 'LLMRouter'...\n", + "remote: Enumerating objects: 6017, done.\u001b[K\n", + "remote: Counting objects: 100% (180/180), done.\u001b[K\n", + "remote: Compressing objects: 100% (90/90), done.\u001b[K\n", + "remote: Total 6017 (delta 105), reused 96 (delta 90), pack-reused 5837 (from 2)\u001b[K\n", + "Receiving objects: 100% (6017/6017), 89.41 MiB | 56.31 MiB/s, done.\n", + "Resolving deltas: 100% (2945/2945), done.\n", + "Updating files: 100% (288/288), done.\n", + "/home/zhongjie/LLMRouter\n", + "Obtaining file:///home/zhongjie/LLMRouter\n", + " Installing build dependencies ... \u001b[?25ldone\n", + "\u001b[?25h Checking if build backend supports build_editable ... \u001b[?25ldone\n", + "\u001b[?25h Getting requirements to build editable ... \u001b[?25ldone\n", + "\u001b[?25h Preparing editable metadata (pyproject.toml) ... \u001b[?25ldone\n", + "\u001b[?25hRequirement already satisfied: torch>=2.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.9.1)\n", + "Requirement already satisfied: transformers>=4.40 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (4.57.6)\n", + "Requirement already satisfied: sentencepiece>=0.1.99 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (0.2.1)\n", + "Requirement already satisfied: numpy>=1.21 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.3.5)\n", + "Requirement already satisfied: pandas>=1.5 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.3.3)\n", + "Requirement already satisfied: scikit-learn>=1.2 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (1.8.0)\n", + "Requirement already satisfied: pyyaml>=6.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (6.0.3)\n", + "Requirement already satisfied: tqdm>=4.65 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (4.67.1)\n", + "Requirement already satisfied: datasets>=2.14 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (4.5.0)\n", + "Requirement already satisfied: pydantic>=2.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.12.5)\n", + "Requirement already satisfied: gradio>=4.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (6.3.0)\n", + "Requirement already satisfied: litellm>=1.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (1.81.0)\n", + "Requirement already satisfied: peft>=0.7 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (0.18.1)\n", + "Requirement already satisfied: torch-geometric>=2.3 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.7.0)\n", + "Requirement already satisfied: scipy>=1.10 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (1.16.3)\n", + "Requirement already satisfied: protobuf>=3.20 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (6.31.1)\n", + "Requirement already satisfied: filelock in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (3.20.2)\n", + "Requirement already satisfied: pyarrow>=21.0.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (22.0.0)\n", + "Requirement already satisfied: dill<0.4.1,>=0.3.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (0.4.0)\n", + "Requirement already satisfied: requests>=2.32.2 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (2.32.5)\n", + "Requirement already satisfied: httpx<1.0.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (0.28.1)\n", + "Requirement already satisfied: xxhash in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (3.6.0)\n", + "Requirement already satisfied: multiprocess<0.70.19 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (0.70.18)\n", + "Requirement already satisfied: fsspec<=2025.10.0,>=2023.1.0 in /opt/conda/lib/python3.13/site-packages (from fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (2025.10.0)\n", + "Requirement already satisfied: huggingface-hub<2.0,>=0.25.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (0.36.0)\n", + "Requirement already satisfied: packaging in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (25.0)\n", + "Requirement already satisfied: aiohttp!=4.0.0a0,!=4.0.0a1 in /opt/conda/lib/python3.13/site-packages (from fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (3.13.3)\n", + "Requirement already satisfied: anyio in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (4.12.0)\n", + "Requirement already satisfied: certifi in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (2026.1.4)\n", + "Requirement already satisfied: httpcore==1.* in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.0.9)\n", + "Requirement already satisfied: idna in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (3.11)\n", + "Requirement already satisfied: h11>=0.16 in /opt/conda/lib/python3.13/site-packages (from httpcore==1.*->httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (0.16.0)\n", + "Requirement already satisfied: typing-extensions>=3.7.4.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<2.0,>=0.25.0->datasets>=2.14->llmrouter-lib==0.2.0) (4.15.0)\n", + "Requirement already satisfied: hf-xet<2.0.0,>=1.1.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<2.0,>=0.25.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.2.0)\n", + "Requirement already satisfied: aiohappyeyeballs>=2.5.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (2.6.1)\n", + "Requirement already satisfied: aiosignal>=1.4.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.4.0)\n", + "Requirement already satisfied: attrs>=17.3.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (25.4.0)\n", + "Requirement already satisfied: frozenlist>=1.1.1 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.8.0)\n", + "Requirement already satisfied: multidict<7.0,>=4.5 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (6.7.0)\n", + "Requirement already satisfied: propcache>=0.2.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (0.4.1)\n", + "Requirement already satisfied: yarl<2.0,>=1.17.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.22.0)\n", + "Requirement already satisfied: aiofiles<25.0,>=22.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (24.1.0)\n", + "Requirement already satisfied: audioop-lts<1.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.2.2)\n", + "Requirement already satisfied: brotli>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (1.2.0)\n", + "Requirement already satisfied: fastapi<1.0,>=0.115.2 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.128.0)\n", + "Requirement already satisfied: ffmpy in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (1.0.0)\n", + "Requirement already satisfied: gradio-client==2.0.3 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (2.0.3)\n", + "Requirement already satisfied: groovy~=0.1 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.1.2)\n", + "Requirement already satisfied: jinja2<4.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (3.1.6)\n", + "Requirement already satisfied: markupsafe<4.0,>=2.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (3.0.3)\n", + "Requirement already satisfied: orjson~=3.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (3.11.5)\n", + "Requirement already satisfied: pillow<13.0,>=8.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (12.1.0)\n", + "Requirement already satisfied: pydub in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.25.1)\n", + "Requirement already satisfied: python-multipart>=0.0.18 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.0.21)\n", + "Requirement already satisfied: safehttpx<0.2.0,>=0.1.7 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.1.7)\n", + "Requirement already satisfied: semantic-version~=2.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (2.10.0)\n", + "Requirement already satisfied: starlette<1.0,>=0.40.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.50.0)\n", + "Requirement already satisfied: tomlkit<0.14.0,>=0.12.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.13.3)\n", + "Requirement already satisfied: typer<1.0,>=0.12 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.21.1)\n", + "Requirement already satisfied: uvicorn>=0.14.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.40.0)\n", + "Requirement already satisfied: annotated-doc>=0.0.2 in /opt/conda/lib/python3.13/site-packages (from fastapi<1.0,>=0.115.2->gradio>=4.0->llmrouter-lib==0.2.0) (0.0.4)\n", + "Requirement already satisfied: python-dateutil>=2.8.2 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.2.0) (2.9.0.post0)\n", + "Requirement already satisfied: pytz>=2020.1 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.2.0) (2025.2)\n", + "Requirement already satisfied: tzdata>=2022.7 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.2.0) (2025.3)\n", + "Requirement already satisfied: annotated-types>=0.6.0 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.2.0) (0.7.0)\n", + "Requirement already satisfied: pydantic-core==2.41.5 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.2.0) (2.41.5)\n", + "Requirement already satisfied: typing-inspection>=0.4.2 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.2.0) (0.4.2)\n", + "Requirement already satisfied: click>=8.0.0 in /opt/conda/lib/python3.13/site-packages (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (8.3.1)\n", + "Requirement already satisfied: shellingham>=1.3.0 in /opt/conda/lib/python3.13/site-packages (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (1.5.4)\n", + "Requirement already satisfied: rich>=10.11.0 in /opt/conda/lib/python3.13/site-packages (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (14.2.0)\n", + "Requirement already satisfied: fastuuid>=0.13.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (0.14.0)\n", + "Requirement already satisfied: grpcio!=1.68.*,!=1.69.*,!=1.70.*,!=1.71.0,!=1.71.1,!=1.72.0,!=1.72.1,!=1.73.0,>=1.62.3 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (1.76.0)\n", + "Requirement already satisfied: importlib-metadata>=6.8.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (8.7.0)\n", + "Requirement already satisfied: jsonschema<5.0.0,>=4.23.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (4.25.1)\n", + "Requirement already satisfied: openai>=2.8.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (2.15.0)\n", + "Requirement already satisfied: python-dotenv>=0.2.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (1.2.1)\n", + "Requirement already satisfied: tiktoken>=0.7.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (0.12.0)\n", + "Requirement already satisfied: tokenizers in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (0.22.2)\n", + "Requirement already satisfied: jsonschema-specifications>=2023.03.6 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.2.0) (2025.9.1)\n", + "Requirement already satisfied: referencing>=0.28.4 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.2.0) (0.37.0)\n", + "Requirement already satisfied: rpds-py>=0.7.1 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.2.0) (0.30.0)\n", + "Requirement already satisfied: zipp>=3.20 in /opt/conda/lib/python3.13/site-packages (from importlib-metadata>=6.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (3.23.0)\n", + "Requirement already satisfied: distro<2,>=1.7.0 in /opt/conda/lib/python3.13/site-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (1.9.0)\n", + "Requirement already satisfied: jiter<1,>=0.10.0 in /opt/conda/lib/python3.13/site-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (0.12.0)\n", + "Requirement already satisfied: sniffio in /opt/conda/lib/python3.13/site-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (1.3.1)\n", + "Requirement already satisfied: psutil in /opt/conda/lib/python3.13/site-packages (from peft>=0.7->llmrouter-lib==0.2.0) (7.2.1)\n", + "Requirement already satisfied: accelerate>=0.21.0 in /opt/conda/lib/python3.13/site-packages (from peft>=0.7->llmrouter-lib==0.2.0) (1.12.0)\n", + "Requirement already satisfied: safetensors in /opt/conda/lib/python3.13/site-packages (from peft>=0.7->llmrouter-lib==0.2.0) (0.7.0)\n", + "Requirement already satisfied: six>=1.5 in /opt/conda/lib/python3.13/site-packages (from python-dateutil>=2.8.2->pandas>=1.5->llmrouter-lib==0.2.0) (1.17.0)\n", + "Requirement already satisfied: charset_normalizer<4,>=2 in /opt/conda/lib/python3.13/site-packages (from requests>=2.32.2->datasets>=2.14->llmrouter-lib==0.2.0) (3.4.4)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/conda/lib/python3.13/site-packages (from requests>=2.32.2->datasets>=2.14->llmrouter-lib==0.2.0) (2.6.2)\n", + "Requirement already satisfied: markdown-it-py>=2.2.0 in /opt/conda/lib/python3.13/site-packages (from rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (4.0.0)\n", + "Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /opt/conda/lib/python3.13/site-packages (from rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (2.19.2)\n", + "Requirement already satisfied: mdurl~=0.1 in /opt/conda/lib/python3.13/site-packages (from markdown-it-py>=2.2.0->rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (0.1.2)\n", + "Requirement already satisfied: joblib>=1.3.0 in /opt/conda/lib/python3.13/site-packages (from scikit-learn>=1.2->llmrouter-lib==0.2.0) (1.5.3)\n", + "Requirement already satisfied: threadpoolctl>=3.2.0 in /opt/conda/lib/python3.13/site-packages (from scikit-learn>=1.2->llmrouter-lib==0.2.0) (3.6.0)\n", + "Requirement already satisfied: regex>=2022.1.18 in /opt/conda/lib/python3.13/site-packages (from tiktoken>=0.7.0->litellm>=1.0->llmrouter-lib==0.2.0) (2026.1.15)\n", + "Requirement already satisfied: setuptools in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (80.9.0)\n", + "Requirement already satisfied: sympy>=1.13.3 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (1.14.0)\n", + "Requirement already satisfied: networkx>=2.5.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (3.6.1)\n", + "Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.93)\n", + "Requirement already satisfied: nvidia-cuda-runtime-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.90)\n", + "Requirement already satisfied: nvidia-cuda-cupti-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.90)\n", + "Requirement already satisfied: nvidia-cudnn-cu12==9.10.2.21 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (9.10.2.21)\n", + "Requirement already satisfied: nvidia-cublas-cu12==12.8.4.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.4.1)\n", + "Requirement already satisfied: nvidia-cufft-cu12==11.3.3.83 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (11.3.3.83)\n", + "Requirement already satisfied: nvidia-curand-cu12==10.3.9.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (10.3.9.90)\n", + "Requirement already satisfied: nvidia-cusolver-cu12==11.7.3.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (11.7.3.90)\n", + "Requirement already satisfied: nvidia-cusparse-cu12==12.5.8.93 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.5.8.93)\n", + "Requirement already satisfied: nvidia-cusparselt-cu12==0.7.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (0.7.1)\n", + "Requirement already satisfied: nvidia-nccl-cu12==2.27.5 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (2.27.5)\n", + "Requirement already satisfied: nvidia-nvshmem-cu12==3.3.20 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (3.3.20)\n", + "Requirement already satisfied: nvidia-nvtx-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.90)\n", + "Requirement already satisfied: nvidia-nvjitlink-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.93)\n", + "Requirement already satisfied: nvidia-cufile-cu12==1.13.1.3 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (1.13.1.3)\n", + "Requirement already satisfied: triton==3.5.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (3.5.1)\n", + "Requirement already satisfied: mpmath<1.4,>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from sympy>=1.13.3->torch>=2.0->llmrouter-lib==0.2.0) (1.3.0)\n", + "Requirement already satisfied: pyparsing in /opt/conda/lib/python3.13/site-packages (from torch-geometric>=2.3->llmrouter-lib==0.2.0) (3.3.1)\n", + "Building wheels for collected packages: llmrouter-lib\n", + " Building editable for llmrouter-lib (pyproject.toml) ... \u001b[?25ldone\n", + "\u001b[?25h Created wheel for llmrouter-lib: filename=llmrouter_lib-0.2.0-0.editable-py3-none-any.whl size=15709 sha256=c7c85cdb9449fdbfc511504249da8139073438920eebae9193ededb8cecfcdc3\n", + " Stored in directory: /tmp/pip-ephem-wheel-cache-5croafgh/wheels/82/4a/fd/59c4aec93c356c380d86a88ab74bece164c0aa855d68b155e4\n", + "Successfully built llmrouter-lib\n", + "Installing collected packages: llmrouter-lib\n", + " Attempting uninstall: llmrouter-lib\n", + " Found existing installation: llmrouter-lib 0.2.0\n", + " Uninstalling llmrouter-lib-0.2.0:\n", + " Successfully uninstalled llmrouter-lib-0.2.0\n", + "Successfully installed llmrouter-lib-0.2.0\n", + "Requirement already satisfied: transformers in /opt/conda/lib/python3.13/site-packages (4.57.6)\n", + "Requirement already satisfied: torch in /opt/conda/lib/python3.13/site-packages (2.9.1)\n", + "Requirement already satisfied: filelock in /opt/conda/lib/python3.13/site-packages (from transformers) (3.20.2)\n", + "Requirement already satisfied: huggingface-hub<1.0,>=0.34.0 in /opt/conda/lib/python3.13/site-packages (from transformers) (0.36.0)\n", + "Requirement already satisfied: numpy>=1.17 in /opt/conda/lib/python3.13/site-packages (from transformers) (2.3.5)\n", + "Requirement already satisfied: packaging>=20.0 in /opt/conda/lib/python3.13/site-packages (from transformers) (25.0)\n", + "Requirement already satisfied: pyyaml>=5.1 in /opt/conda/lib/python3.13/site-packages (from transformers) (6.0.3)\n", + "Requirement already satisfied: regex!=2019.12.17 in /opt/conda/lib/python3.13/site-packages (from transformers) (2026.1.15)\n", + "Requirement already satisfied: requests in /opt/conda/lib/python3.13/site-packages (from transformers) (2.32.5)\n", + "Requirement already satisfied: tokenizers<=0.23.0,>=0.22.0 in /opt/conda/lib/python3.13/site-packages (from transformers) (0.22.2)\n", + "Requirement already satisfied: safetensors>=0.4.3 in /opt/conda/lib/python3.13/site-packages (from transformers) (0.7.0)\n", + "Requirement already satisfied: tqdm>=4.27 in /opt/conda/lib/python3.13/site-packages (from transformers) (4.67.1)\n", + "Requirement already satisfied: fsspec>=2023.5.0 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (2025.10.0)\n", + "Requirement already satisfied: typing-extensions>=3.7.4.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (4.15.0)\n", + "Requirement already satisfied: hf-xet<2.0.0,>=1.1.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (1.2.0)\n", + "Requirement already satisfied: setuptools in /opt/conda/lib/python3.13/site-packages (from torch) (80.9.0)\n", + "Requirement already satisfied: sympy>=1.13.3 in /opt/conda/lib/python3.13/site-packages (from torch) (1.14.0)\n", + "Requirement already satisfied: networkx>=2.5.1 in /opt/conda/lib/python3.13/site-packages (from torch) (3.6.1)\n", + "Requirement already satisfied: jinja2 in /opt/conda/lib/python3.13/site-packages (from torch) (3.1.6)\n", + "Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.93)\n", + "Requirement already satisfied: nvidia-cuda-runtime-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-cuda-cupti-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-cudnn-cu12==9.10.2.21 in /opt/conda/lib/python3.13/site-packages (from torch) (9.10.2.21)\n", + "Requirement already satisfied: nvidia-cublas-cu12==12.8.4.1 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.4.1)\n", + "Requirement already satisfied: nvidia-cufft-cu12==11.3.3.83 in /opt/conda/lib/python3.13/site-packages (from torch) (11.3.3.83)\n", + "Requirement already satisfied: nvidia-curand-cu12==10.3.9.90 in /opt/conda/lib/python3.13/site-packages (from torch) (10.3.9.90)\n", + "Requirement already satisfied: nvidia-cusolver-cu12==11.7.3.90 in /opt/conda/lib/python3.13/site-packages (from torch) (11.7.3.90)\n", + "Requirement already satisfied: nvidia-cusparse-cu12==12.5.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.5.8.93)\n", + "Requirement already satisfied: nvidia-cusparselt-cu12==0.7.1 in /opt/conda/lib/python3.13/site-packages (from torch) (0.7.1)\n", + "Requirement already satisfied: nvidia-nccl-cu12==2.27.5 in /opt/conda/lib/python3.13/site-packages (from torch) (2.27.5)\n", + "Requirement already satisfied: nvidia-nvshmem-cu12==3.3.20 in /opt/conda/lib/python3.13/site-packages (from torch) (3.3.20)\n", + "Requirement already satisfied: nvidia-nvtx-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-nvjitlink-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.93)\n", + "Requirement already satisfied: nvidia-cufile-cu12==1.13.1.3 in /opt/conda/lib/python3.13/site-packages (from torch) (1.13.1.3)\n", + "Requirement already satisfied: triton==3.5.1 in /opt/conda/lib/python3.13/site-packages (from torch) (3.5.1)\n", + "Requirement already satisfied: mpmath<1.4,>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from sympy>=1.13.3->torch) (1.3.0)\n", + "Requirement already satisfied: MarkupSafe>=2.0 in /opt/conda/lib/python3.13/site-packages (from jinja2->torch) (3.0.3)\n", + "Requirement already satisfied: charset_normalizer<4,>=2 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (3.4.4)\n", + "Requirement already satisfied: idna<4,>=2.5 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (3.11)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (2.6.2)\n", + "Requirement already satisfied: certifi>=2017.4.17 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (2026.1.4)\n" + ] + } + ], + "source": [ + "# Install required packages (for Colab)\n", + "!git clone https://github.com/ulab-uiuc/LLMRouter.git\n", + "%cd LLMRouter\n", + "!pip install -e .\n", + "!pip install transformers torch\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "os.environ['OPENAI_API_KEY'] = 'your-key'\n", + "os.environ['ANTHROPIC_API_KEY'] = 'your-key'\n", + "# Or for multiple keys:\n", + "os.environ['API_KEYS'] = '[\"key1\", \"key2\"]'" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Environment setup complete!\n" + ] + } + ], + "source": [ + "from llmrouter.models.elorouter import EloRouter, EloRouterTrainer\n", + "from llmrouter.utils import setup_environment\n", + "\n", + "setup_environment()\n", + "print(\"Environment setup complete!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Configuration" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Current Configuration:\n", + "==================================================\n", + "data_path:\n", + " llm_data: data/example_data/llm_candidates/default_llm.json\n", + " llm_embedding_data: data/example_data/llm_candidates/default_llm_embeddings.json\n", + " query_data_test: data/example_data/query_data/default_query_test.jsonl\n", + " query_data_train: data/example_data/query_data/default_query_train.jsonl\n", + " query_embedding_data: data/example_data/routing_data/query_embeddings_longformer.pt\n", + " routing_data_test: data/example_data/routing_data/default_routing_test_data.jsonl\n", + " routing_data_train: data/example_data/routing_data/default_routing_train_data.jsonl\n", + "metric:\n", + " weights:\n", + " cost: 0\n", + " llm_judge: 0\n", + " performance: 1\n", + "model_path:\n", + " ini_model_path: ''\n", + " save_model_path: saved_models/elorouter/elorouter.pkl\n", + "\n" + ] + } + ], + "source": [ + "import yaml\n", + "\n", + "CONFIG_PATH = \"configs/model_config_train/elorouter.yaml\"\n", + "\n", + "with open(CONFIG_PATH, 'r') as f:\n", + " config = yaml.safe_load(f)\n", + "\n", + "print(\"Current Configuration:\")\n", + "print(\"=\" * 50)\n", + "print(yaml.dump(config, default_flow_style=False))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Load Router" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "✅ MetaRouter initialized successfully (YAML + data loaded).\n", + "[EloRouter] Initialized with 9 models.\n", + "Router initialized successfully!\n", + "Number of LLM candidates: 7\n", + "LLM candidates: ['qwen2.5-7b-instruct', 'llama-3.1-8b-instruct', 'mistral-7b-instruct-v0.3', 'llama-3.3-nemotron-super-49b-v1', 'llama3-70b-instruct', 'mixtral-8x7b-instruct-v0.1', 'mixtral-8x22b-instruct-v0.1']\n" + ] + } + ], + "source": [ + "router = EloRouter(yaml_path=CONFIG_PATH)\n", + "\n", + "print(\"Router initialized successfully!\")\n", + "print(f\"Number of LLM candidates: {len(router.llm_data)}\")\n", + "print(f\"LLM candidates: {list(router.llm_data.keys())}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Training" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[EloRouterTrainer] Initialized.\n", + "Trainer initialized!\n", + "Number of training samples: 50544\n", + "Save path: /home/zhongjie/LLMRouter/llmrouter/saved_models/elorouter/elorouter.pkl\n" + ] + } + ], + "source": [ + "# Initialize trainer\n", + "trainer = EloRouterTrainer(router=router, device=\"cpu\")\n", + "\n", + "print(\"Trainer initialized!\")\n", + "print(f\"Number of training samples: {len(router.routing_data_train)}\")\n", + "print(f\"Save path: {trainer.save_model_path}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting EloRouter training...\n", + "==================================================\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/conda/lib/python3.13/site-packages/sklearn/linear_model/_logistic.py:1135: FutureWarning: 'penalty' was deprecated in version 1.8 and will be removed in 1.10. To avoid this warning, leave 'penalty' set to its default value and use 'l1_ratio' or 'C' instead. Use l1_ratio=0 instead of penalty='l2', l1_ratio=1 instead of penalty='l1', and C=np.inf instead of penalty=None.\n", + " warnings.warn(\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Created directory: /home/zhongjie/LLMRouter/llmrouter/saved_models/elorouter\n", + "Successfully saved pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/elorouter/elorouter.pkl\n", + "[EloRouterTrainer] Saved Elo scores to: /home/zhongjie/LLMRouter/llmrouter/saved_models/elorouter/elorouter.pkl\n", + "==================================================\n", + "Training completed successfully!\n" + ] + } + ], + "source": [ + "# Train the model\n", + "print(\"Starting EloRouter training...\")\n", + "print(\"=\" * 50)\n", + "\n", + "trainer.train()\n", + "\n", + "print(\"=\" * 50)\n", + "print(\"Training completed successfully!\")" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/elorouter/elorouter.pkl\n", + "LLM Elo Ratings:\n", + "==================================================\n", + " qwen2.5-7b-instruct 1346.09\n", + " gemma-2-9b-it 1213.53\n", + " llama-3.1-8b-instruct 1148.56\n", + " codegemma-7b 1014.94\n", + " llama-3.1-nemotron-51b-instruct 953.59\n", + " llama-3.3-nemotron-super-49b-v1 881.35\n", + " llama3-chatqa-1.5-8b 875.05\n", + " mistral-7b-instruct-v0.3 867.38\n", + " llama3-chatqa-1.5-70b 699.50\n" + ] + } + ], + "source": [ + "from llmrouter.utils import load_model\n", + "\n", + "# 训练完成后加载 Elo scores\n", + "elo_scores = load_model(trainer.save_model_path)\n", + "\n", + "print(\"LLM Elo Ratings:\")\n", + "print(\"=\" * 50)\n", + "for model, rating in sorted(elo_scores.items(), key=lambda x: x[1], reverse=True):\n", + " print(f\" {model:30} {rating:.2f}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Understanding Elo Ratings\n", + "\n", + "Elo rating system:\n", + "- Higher rating = better model\n", + "- Ratings updated based on pairwise comparisons\n", + "- Used in Chatbot Arena leaderboard" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "LLM Elo Ratings:\n", + "==================================================\n", + "Elo ratings are computed from benchmark data.\n", + "The router will select the model with highest average performance.\n" + ] + } + ], + "source": [ + "# Display Elo ratings for each LLM\n", + "print(\"LLM Elo Ratings:\")\n", + "print(\"=\" * 50)\n", + "\n", + "# Note: Elo ratings should be available in the router or LLM data\n", + "if hasattr(router, 'elo_ratings'):\n", + " for model, rating in sorted(router.elo_ratings.items(), key=lambda x: x[1], reverse=True):\n", + " print(f\" {model:30} {rating}\")\n", + "else:\n", + " print(\"Elo ratings are computed from benchmark data.\")\n", + " print(\"The router will select the model with highest average performance.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. Query Routing" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Routing Results:\n", + "============================================================\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/elorouter/elorouter.pkl\n", + "[EloRouter] Loaded Elo scores from /home/zhongjie/LLMRouter/llmrouter/saved_models/elorouter/elorouter.pkl\n", + "1. What is the capital of France?...\n", + " Routed to: qwen2.5-7b-instruct\n", + "2. Solve the equation: 2x + 5 = 15...\n", + " Routed to: qwen2.5-7b-instruct\n", + "3. Write a Python function to check if a number is pr...\n", + " Routed to: qwen2.5-7b-instruct\n", + "4. Explain quantum computing in simple terms....\n", + " Routed to: qwen2.5-7b-instruct\n" + ] + } + ], + "source": [ + "EXAMPLE_QUERIES = [\n", + " {\"query\": \"What is the capital of France?\"},\n", + " {\"query\": \"Solve the equation: 2x + 5 = 15\"},\n", + " {\"query\": \"Write a Python function to check if a number is prime.\"},\n", + " {\"query\": \"Explain quantum computing in simple terms.\"},\n", + "]\n", + "\n", + "print(\"Routing Results:\")\n", + "print(\"=\" * 60)\n", + "\n", + "for i, query in enumerate(EXAMPLE_QUERIES, 1):\n", + " result = router.route_single(query)\n", + " print(f\"{i}. {query['query'][:50]}...\")\n", + " print(f\" Routed to: {result['model_name']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7. Note on EloRouter Behavior\n", + "\n", + "EloRouter is a **static** router - it always selects the same LLM (the highest-rated one)\n", + "regardless of the query content. This is useful as:\n", + "- A baseline for comparison\n", + "- When you want the overall best model\n", + "- When query-specific routing isn't necessary" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "All queries routed to same model: True\n", + "Selected model: qwen2.5-7b-instruct\n" + ] + } + ], + "source": [ + "# Verify static behavior\n", + "results = [router.route_single(q)['model_name'] for q in EXAMPLE_QUERIES]\n", + "all_same = len(set(results)) == 1\n", + "\n", + "print(f\"All queries routed to same model: {all_same}\")\n", + "if all_same:\n", + " print(f\"Selected model: {results[0]}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 8. File-Based Inference\n", + "\n", + "Load queries from a file and save results." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loaded 706 queries from: data/example_data/query_data/default_query_test.jsonl\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Routed 10 queries\n", + "Results saved to: outputs/elorouter_results.jsonl\n", + "\n", + "Sample results:\n", + " 1. Q: There are 4 houses in a row, numbered... -> qwen2.5-7b-instruct\n", + " 2. Q: There are 3 houses in a row, numbered... -> qwen2.5-7b-instruct\n", + " 3. Q: There are 3 houses in a row, numbered... -> qwen2.5-7b-instruct\n" + ] + } + ], + "source": [ + "import json\n", + "\n", + "# Load queries from a JSONL file\n", + "def load_queries_from_file(file_path):\n", + " \"\"\"Load queries from a JSONL file.\"\"\"\n", + " queries = []\n", + " with open(file_path, 'r', encoding='utf-8') as f:\n", + " for line in f:\n", + " if line.strip():\n", + " queries.append(json.loads(line))\n", + " return queries\n", + "\n", + "# Save results to a JSONL file\n", + "def save_results_to_file(results, output_path):\n", + " \"\"\"Save routing results to a JSONL file.\"\"\"\n", + " os.makedirs(os.path.dirname(output_path), exist_ok=True)\n", + " with open(output_path, 'w', encoding='utf-8') as f:\n", + " for result in results:\n", + " f.write(json.dumps(result, ensure_ascii=False) + '\\n')\n", + " print(f\"Results saved to: {output_path}\")\n", + "\n", + "# Example: Load from default query file\n", + "QUERY_FILE = \"data/example_data/query_data/default_query_test.jsonl\"\n", + "OUTPUT_FILE = \"outputs/elorouter_results.jsonl\"\n", + "\n", + "if os.path.exists(QUERY_FILE):\n", + " # Load queries\n", + " file_queries = load_queries_from_file(QUERY_FILE)\n", + " print(f\"Loaded {len(file_queries)} queries from: {QUERY_FILE}\")\n", + " \n", + " # Route queries\n", + " file_results = router.route_batch(batch=file_queries[:10])\n", + " print(f\"Routed {len(file_results)} queries\")\n", + " \n", + " # Save results\n", + " save_results_to_file(file_results, OUTPUT_FILE)\n", + " \n", + " # Show sample results\n", + " print(f\"\\nSample results:\")\n", + " for i, result in enumerate(file_results[:3], 1):\n", + " print(f\" {i}. {result.get('query', '')[:40]}... -> {result['model_name']}\")\n", + "else:\n", + " print(f\"Query file not found: {QUERY_FILE}\")\n", + " print(\"Create a JSONL file with format: {\\\"query\\\": \\\"Your question\\\"}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "In this notebook, we:\n", + "\n", + "1. **Loaded EloRouter**: No training required\n", + "2. **Understood Elo System**: Rating-based model selection\n", + "3. **Performed Routing**: Static selection of best-rated model\n", + "\n", + "**Key Takeaways**:\n", + "- EloRouter is the simplest router (no training)\n", + "- Always selects the highest-rated model\n", + "- Useful as a baseline or when simplicity is preferred\n", + "\n", + "**When to use EloRouter**:\n", + "- As a baseline for comparison\n", + "- When you always want the \"best\" model\n", + "- When computational resources are limited\n", + "\n", + "**When NOT to use EloRouter**:\n", + "- When query-specific routing is important\n", + "- When cost optimization is needed\n", + "- When different queries need different model capabilities" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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.13.11" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/colab_notebooks/gmtrouter/01_gmtrouter_training_and_inference.ipynb b/colab_notebooks/gmtrouter/01_gmtrouter_training_and_inference.ipynb new file mode 100644 index 0000000..9f3d69a --- /dev/null +++ b/colab_notebooks/gmtrouter/01_gmtrouter_training_and_inference.ipynb @@ -0,0 +1,983 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# GMTRouter - Training\n", + "\n", + "This notebook demonstrates how to train the **GMTRouter** (Graph-based Multi-Turn Router).\n", + "\n", + "## Overview\n", + "\n", + "GMTRouter uses a Heterogeneous Graph Neural Network (HeteroGNN) to model complex relationships\n", + "in multi-turn conversations for personalized LLM routing.\n", + "\n", + "**Key Features**:\n", + "- 5 Node types: User, Session, Query, LLM, Response\n", + "- 21 Edge types capturing various relationships\n", + "- Personalized routing based on user preferences\n", + "- Multi-turn conversation support\n", + "\n", + "**Requirements**:\n", + "- PyTorch 2.0+\n", + "- PyTorch Geometric (optional but recommended)\n", + "- GMTRouter dataset" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Environment Setup" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cloning into 'LLMRouter'...\n", + "remote: Enumerating objects: 6017, done.\u001b[K\n", + "remote: Counting objects: 100% (172/172), done.\u001b[K\n", + "remote: Compressing objects: 100% (91/91), done.\u001b[K\n", + "remote: Total 6017 (delta 98), reused 87 (delta 81), pack-reused 5845 (from 2)\u001b[K\n", + "Receiving objects: 100% (6017/6017), 89.41 MiB | 53.86 MiB/s, done.\n", + "Resolving deltas: 100% (2946/2946), done.\n", + "Updating files: 100% (288/288), done.\n", + "/home/zhongjie/LLMRouter\n", + "Obtaining file:///home/zhongjie/LLMRouter\n", + " Installing build dependencies ... \u001b[?25ldone\n", + "\u001b[?25h Checking if build backend supports build_editable ... \u001b[?25ldone\n", + "\u001b[?25h Getting requirements to build editable ... \u001b[?25ldone\n", + "\u001b[?25h Preparing editable metadata (pyproject.toml) ... \u001b[?25ldone\n", + "\u001b[?25hRequirement already satisfied: torch>=2.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.9.1)\n", + "Requirement already satisfied: transformers>=4.40 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (4.57.6)\n", + "Requirement already satisfied: sentencepiece>=0.1.99 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (0.2.1)\n", + "Requirement already satisfied: numpy>=1.21 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.3.5)\n", + "Requirement already satisfied: pandas>=1.5 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.3.3)\n", + "Requirement already satisfied: scikit-learn>=1.2 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (1.8.0)\n", + "Requirement already satisfied: pyyaml>=6.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (6.0.3)\n", + "Requirement already satisfied: tqdm>=4.65 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (4.67.1)\n", + "Requirement already satisfied: datasets>=2.14 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (4.5.0)\n", + "Requirement already satisfied: pydantic>=2.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.12.5)\n", + "Requirement already satisfied: gradio>=4.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (6.3.0)\n", + "Requirement already satisfied: litellm>=1.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (1.81.0)\n", + "Requirement already satisfied: peft>=0.7 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (0.18.1)\n", + "Requirement already satisfied: torch-geometric>=2.3 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.7.0)\n", + "Requirement already satisfied: scipy>=1.10 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (1.16.3)\n", + "Requirement already satisfied: protobuf>=3.20 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (6.31.1)\n", + "Requirement already satisfied: filelock in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (3.20.2)\n", + "Requirement already satisfied: pyarrow>=21.0.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (22.0.0)\n", + "Requirement already satisfied: dill<0.4.1,>=0.3.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (0.4.0)\n", + "Requirement already satisfied: requests>=2.32.2 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (2.32.5)\n", + "Requirement already satisfied: httpx<1.0.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (0.28.1)\n", + "Requirement already satisfied: xxhash in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (3.6.0)\n", + "Requirement already satisfied: multiprocess<0.70.19 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (0.70.18)\n", + "Requirement already satisfied: fsspec<=2025.10.0,>=2023.1.0 in /opt/conda/lib/python3.13/site-packages (from fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (2025.10.0)\n", + "Requirement already satisfied: huggingface-hub<2.0,>=0.25.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (0.36.0)\n", + "Requirement already satisfied: packaging in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (25.0)\n", + "Requirement already satisfied: aiohttp!=4.0.0a0,!=4.0.0a1 in /opt/conda/lib/python3.13/site-packages (from fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (3.13.3)\n", + "Requirement already satisfied: anyio in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (4.12.0)\n", + "Requirement already satisfied: certifi in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (2026.1.4)\n", + "Requirement already satisfied: httpcore==1.* in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.0.9)\n", + "Requirement already satisfied: idna in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (3.11)\n", + "Requirement already satisfied: h11>=0.16 in /opt/conda/lib/python3.13/site-packages (from httpcore==1.*->httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (0.16.0)\n", + "Requirement already satisfied: typing-extensions>=3.7.4.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<2.0,>=0.25.0->datasets>=2.14->llmrouter-lib==0.2.0) (4.15.0)\n", + "Requirement already satisfied: hf-xet<2.0.0,>=1.1.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<2.0,>=0.25.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.2.0)\n", + "Requirement already satisfied: aiohappyeyeballs>=2.5.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (2.6.1)\n", + "Requirement already satisfied: aiosignal>=1.4.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.4.0)\n", + "Requirement already satisfied: attrs>=17.3.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (25.4.0)\n", + "Requirement already satisfied: frozenlist>=1.1.1 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.8.0)\n", + "Requirement already satisfied: multidict<7.0,>=4.5 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (6.7.0)\n", + "Requirement already satisfied: propcache>=0.2.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (0.4.1)\n", + "Requirement already satisfied: yarl<2.0,>=1.17.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.22.0)\n", + "Requirement already satisfied: aiofiles<25.0,>=22.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (24.1.0)\n", + "Requirement already satisfied: audioop-lts<1.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.2.2)\n", + "Requirement already satisfied: brotli>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (1.2.0)\n", + "Requirement already satisfied: fastapi<1.0,>=0.115.2 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.128.0)\n", + "Requirement already satisfied: ffmpy in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (1.0.0)\n", + "Requirement already satisfied: gradio-client==2.0.3 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (2.0.3)\n", + "Requirement already satisfied: groovy~=0.1 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.1.2)\n", + "Requirement already satisfied: jinja2<4.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (3.1.6)\n", + "Requirement already satisfied: markupsafe<4.0,>=2.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (3.0.3)\n", + "Requirement already satisfied: orjson~=3.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (3.11.5)\n", + "Requirement already satisfied: pillow<13.0,>=8.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (12.1.0)\n", + "Requirement already satisfied: pydub in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.25.1)\n", + "Requirement already satisfied: python-multipart>=0.0.18 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.0.21)\n", + "Requirement already satisfied: safehttpx<0.2.0,>=0.1.7 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.1.7)\n", + "Requirement already satisfied: semantic-version~=2.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (2.10.0)\n", + "Requirement already satisfied: starlette<1.0,>=0.40.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.50.0)\n", + "Requirement already satisfied: tomlkit<0.14.0,>=0.12.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.13.3)\n", + "Requirement already satisfied: typer<1.0,>=0.12 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.21.1)\n", + "Requirement already satisfied: uvicorn>=0.14.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.40.0)\n", + "Requirement already satisfied: annotated-doc>=0.0.2 in /opt/conda/lib/python3.13/site-packages (from fastapi<1.0,>=0.115.2->gradio>=4.0->llmrouter-lib==0.2.0) (0.0.4)\n", + "Requirement already satisfied: python-dateutil>=2.8.2 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.2.0) (2.9.0.post0)\n", + "Requirement already satisfied: pytz>=2020.1 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.2.0) (2025.2)\n", + "Requirement already satisfied: tzdata>=2022.7 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.2.0) (2025.3)\n", + "Requirement already satisfied: annotated-types>=0.6.0 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.2.0) (0.7.0)\n", + "Requirement already satisfied: pydantic-core==2.41.5 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.2.0) (2.41.5)\n", + "Requirement already satisfied: typing-inspection>=0.4.2 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.2.0) (0.4.2)\n", + "Requirement already satisfied: click>=8.0.0 in /opt/conda/lib/python3.13/site-packages (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (8.3.1)\n", + "Requirement already satisfied: shellingham>=1.3.0 in /opt/conda/lib/python3.13/site-packages (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (1.5.4)\n", + "Requirement already satisfied: rich>=10.11.0 in /opt/conda/lib/python3.13/site-packages (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (14.2.0)\n", + "Requirement already satisfied: fastuuid>=0.13.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (0.14.0)\n", + "Requirement already satisfied: grpcio!=1.68.*,!=1.69.*,!=1.70.*,!=1.71.0,!=1.71.1,!=1.72.0,!=1.72.1,!=1.73.0,>=1.62.3 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (1.76.0)\n", + "Requirement already satisfied: importlib-metadata>=6.8.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (8.7.0)\n", + "Requirement already satisfied: jsonschema<5.0.0,>=4.23.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (4.25.1)\n", + "Requirement already satisfied: openai>=2.8.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (2.15.0)\n", + "Requirement already satisfied: python-dotenv>=0.2.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (1.2.1)\n", + "Requirement already satisfied: tiktoken>=0.7.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (0.12.0)\n", + "Requirement already satisfied: tokenizers in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (0.22.2)\n", + "Requirement already satisfied: jsonschema-specifications>=2023.03.6 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.2.0) (2025.9.1)\n", + "Requirement already satisfied: referencing>=0.28.4 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.2.0) (0.37.0)\n", + "Requirement already satisfied: rpds-py>=0.7.1 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.2.0) (0.30.0)\n", + "Requirement already satisfied: zipp>=3.20 in /opt/conda/lib/python3.13/site-packages (from importlib-metadata>=6.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (3.23.0)\n", + "Requirement already satisfied: distro<2,>=1.7.0 in /opt/conda/lib/python3.13/site-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (1.9.0)\n", + "Requirement already satisfied: jiter<1,>=0.10.0 in /opt/conda/lib/python3.13/site-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (0.12.0)\n", + "Requirement already satisfied: sniffio in /opt/conda/lib/python3.13/site-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (1.3.1)\n", + "Requirement already satisfied: psutil in /opt/conda/lib/python3.13/site-packages (from peft>=0.7->llmrouter-lib==0.2.0) (7.2.1)\n", + "Requirement already satisfied: accelerate>=0.21.0 in /opt/conda/lib/python3.13/site-packages (from peft>=0.7->llmrouter-lib==0.2.0) (1.12.0)\n", + "Requirement already satisfied: safetensors in /opt/conda/lib/python3.13/site-packages (from peft>=0.7->llmrouter-lib==0.2.0) (0.7.0)\n", + "Requirement already satisfied: six>=1.5 in /opt/conda/lib/python3.13/site-packages (from python-dateutil>=2.8.2->pandas>=1.5->llmrouter-lib==0.2.0) (1.17.0)\n", + "Requirement already satisfied: charset_normalizer<4,>=2 in /opt/conda/lib/python3.13/site-packages (from requests>=2.32.2->datasets>=2.14->llmrouter-lib==0.2.0) (3.4.4)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/conda/lib/python3.13/site-packages (from requests>=2.32.2->datasets>=2.14->llmrouter-lib==0.2.0) (2.6.2)\n", + "Requirement already satisfied: markdown-it-py>=2.2.0 in /opt/conda/lib/python3.13/site-packages (from rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (4.0.0)\n", + "Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /opt/conda/lib/python3.13/site-packages (from rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (2.19.2)\n", + "Requirement already satisfied: mdurl~=0.1 in /opt/conda/lib/python3.13/site-packages (from markdown-it-py>=2.2.0->rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (0.1.2)\n", + "Requirement already satisfied: joblib>=1.3.0 in /opt/conda/lib/python3.13/site-packages (from scikit-learn>=1.2->llmrouter-lib==0.2.0) (1.5.3)\n", + "Requirement already satisfied: threadpoolctl>=3.2.0 in /opt/conda/lib/python3.13/site-packages (from scikit-learn>=1.2->llmrouter-lib==0.2.0) (3.6.0)\n", + "Requirement already satisfied: regex>=2022.1.18 in /opt/conda/lib/python3.13/site-packages (from tiktoken>=0.7.0->litellm>=1.0->llmrouter-lib==0.2.0) (2026.1.15)\n", + "Requirement already satisfied: setuptools in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (80.9.0)\n", + "Requirement already satisfied: sympy>=1.13.3 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (1.14.0)\n", + "Requirement already satisfied: networkx>=2.5.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (3.6.1)\n", + "Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.93)\n", + "Requirement already satisfied: nvidia-cuda-runtime-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.90)\n", + "Requirement already satisfied: nvidia-cuda-cupti-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.90)\n", + "Requirement already satisfied: nvidia-cudnn-cu12==9.10.2.21 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (9.10.2.21)\n", + "Requirement already satisfied: nvidia-cublas-cu12==12.8.4.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.4.1)\n", + "Requirement already satisfied: nvidia-cufft-cu12==11.3.3.83 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (11.3.3.83)\n", + "Requirement already satisfied: nvidia-curand-cu12==10.3.9.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (10.3.9.90)\n", + "Requirement already satisfied: nvidia-cusolver-cu12==11.7.3.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (11.7.3.90)\n", + "Requirement already satisfied: nvidia-cusparse-cu12==12.5.8.93 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.5.8.93)\n", + "Requirement already satisfied: nvidia-cusparselt-cu12==0.7.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (0.7.1)\n", + "Requirement already satisfied: nvidia-nccl-cu12==2.27.5 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (2.27.5)\n", + "Requirement already satisfied: nvidia-nvshmem-cu12==3.3.20 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (3.3.20)\n", + "Requirement already satisfied: nvidia-nvtx-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.90)\n", + "Requirement already satisfied: nvidia-nvjitlink-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.93)\n", + "Requirement already satisfied: nvidia-cufile-cu12==1.13.1.3 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (1.13.1.3)\n", + "Requirement already satisfied: triton==3.5.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (3.5.1)\n", + "Requirement already satisfied: mpmath<1.4,>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from sympy>=1.13.3->torch>=2.0->llmrouter-lib==0.2.0) (1.3.0)\n", + "Requirement already satisfied: pyparsing in /opt/conda/lib/python3.13/site-packages (from torch-geometric>=2.3->llmrouter-lib==0.2.0) (3.3.1)\n", + "Building wheels for collected packages: llmrouter-lib\n", + " Building editable for llmrouter-lib (pyproject.toml) ... \u001b[?25ldone\n", + "\u001b[?25h Created wheel for llmrouter-lib: filename=llmrouter_lib-0.2.0-0.editable-py3-none-any.whl size=15709 sha256=f1a7e8ab14b77420868477c073f2bc3a6fa03a5b0211b6853ae72a3c7f013d8e\n", + " Stored in directory: /tmp/pip-ephem-wheel-cache-ln6sfduv/wheels/82/4a/fd/59c4aec93c356c380d86a88ab74bece164c0aa855d68b155e4\n", + "Successfully built llmrouter-lib\n", + "Installing collected packages: llmrouter-lib\n", + " Attempting uninstall: llmrouter-lib\n", + " Found existing installation: llmrouter-lib 0.2.0\n", + " Uninstalling llmrouter-lib-0.2.0:\n", + " Successfully uninstalled llmrouter-lib-0.2.0\n", + "Successfully installed llmrouter-lib-0.2.0\n", + "Requirement already satisfied: torch in /opt/conda/lib/python3.13/site-packages (2.9.1)\n", + "Requirement already satisfied: torch-geometric in /opt/conda/lib/python3.13/site-packages (2.7.0)\n", + "Requirement already satisfied: filelock in /opt/conda/lib/python3.13/site-packages (from torch) (3.20.2)\n", + "Requirement already satisfied: typing-extensions>=4.10.0 in /opt/conda/lib/python3.13/site-packages (from torch) (4.15.0)\n", + "Requirement already satisfied: setuptools in /opt/conda/lib/python3.13/site-packages (from torch) (80.9.0)\n", + "Requirement already satisfied: sympy>=1.13.3 in /opt/conda/lib/python3.13/site-packages (from torch) (1.14.0)\n", + "Requirement already satisfied: networkx>=2.5.1 in /opt/conda/lib/python3.13/site-packages (from torch) (3.6.1)\n", + "Requirement already satisfied: jinja2 in /opt/conda/lib/python3.13/site-packages (from torch) (3.1.6)\n", + "Requirement already satisfied: fsspec>=0.8.5 in /opt/conda/lib/python3.13/site-packages (from torch) (2025.10.0)\n", + "Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.93)\n", + "Requirement already satisfied: nvidia-cuda-runtime-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-cuda-cupti-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-cudnn-cu12==9.10.2.21 in /opt/conda/lib/python3.13/site-packages (from torch) (9.10.2.21)\n", + "Requirement already satisfied: nvidia-cublas-cu12==12.8.4.1 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.4.1)\n", + "Requirement already satisfied: nvidia-cufft-cu12==11.3.3.83 in /opt/conda/lib/python3.13/site-packages (from torch) (11.3.3.83)\n", + "Requirement already satisfied: nvidia-curand-cu12==10.3.9.90 in /opt/conda/lib/python3.13/site-packages (from torch) (10.3.9.90)\n", + "Requirement already satisfied: nvidia-cusolver-cu12==11.7.3.90 in /opt/conda/lib/python3.13/site-packages (from torch) (11.7.3.90)\n", + "Requirement already satisfied: nvidia-cusparse-cu12==12.5.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.5.8.93)\n", + "Requirement already satisfied: nvidia-cusparselt-cu12==0.7.1 in /opt/conda/lib/python3.13/site-packages (from torch) (0.7.1)\n", + "Requirement already satisfied: nvidia-nccl-cu12==2.27.5 in /opt/conda/lib/python3.13/site-packages (from torch) (2.27.5)\n", + "Requirement already satisfied: nvidia-nvshmem-cu12==3.3.20 in /opt/conda/lib/python3.13/site-packages (from torch) (3.3.20)\n", + "Requirement already satisfied: nvidia-nvtx-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-nvjitlink-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.93)\n", + "Requirement already satisfied: nvidia-cufile-cu12==1.13.1.3 in /opt/conda/lib/python3.13/site-packages (from torch) (1.13.1.3)\n", + "Requirement already satisfied: triton==3.5.1 in /opt/conda/lib/python3.13/site-packages (from torch) (3.5.1)\n", + "Requirement already satisfied: aiohttp in /opt/conda/lib/python3.13/site-packages (from torch-geometric) (3.13.3)\n", + "Requirement already satisfied: numpy in /opt/conda/lib/python3.13/site-packages (from torch-geometric) (2.3.5)\n", + "Requirement already satisfied: psutil>=5.8.0 in /opt/conda/lib/python3.13/site-packages (from torch-geometric) (7.2.1)\n", + "Requirement already satisfied: pyparsing in /opt/conda/lib/python3.13/site-packages (from torch-geometric) (3.3.1)\n", + "Requirement already satisfied: requests in /opt/conda/lib/python3.13/site-packages (from torch-geometric) (2.32.5)\n", + "Requirement already satisfied: tqdm in /opt/conda/lib/python3.13/site-packages (from torch-geometric) (4.67.1)\n", + "Requirement already satisfied: xxhash in /opt/conda/lib/python3.13/site-packages (from torch-geometric) (3.6.0)\n", + "Requirement already satisfied: mpmath<1.4,>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from sympy>=1.13.3->torch) (1.3.0)\n", + "Requirement already satisfied: aiohappyeyeballs>=2.5.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp->torch-geometric) (2.6.1)\n", + "Requirement already satisfied: aiosignal>=1.4.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp->torch-geometric) (1.4.0)\n", + "Requirement already satisfied: attrs>=17.3.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp->torch-geometric) (25.4.0)\n", + "Requirement already satisfied: frozenlist>=1.1.1 in /opt/conda/lib/python3.13/site-packages (from aiohttp->torch-geometric) (1.8.0)\n", + "Requirement already satisfied: multidict<7.0,>=4.5 in /opt/conda/lib/python3.13/site-packages (from aiohttp->torch-geometric) (6.7.0)\n", + "Requirement already satisfied: propcache>=0.2.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp->torch-geometric) (0.4.1)\n", + "Requirement already satisfied: yarl<2.0,>=1.17.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp->torch-geometric) (1.22.0)\n", + "Requirement already satisfied: idna>=2.0 in /opt/conda/lib/python3.13/site-packages (from yarl<2.0,>=1.17.0->aiohttp->torch-geometric) (3.11)\n", + "Requirement already satisfied: MarkupSafe>=2.0 in /opt/conda/lib/python3.13/site-packages (from jinja2->torch) (3.0.3)\n", + "Requirement already satisfied: charset_normalizer<4,>=2 in /opt/conda/lib/python3.13/site-packages (from requests->torch-geometric) (3.4.4)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/conda/lib/python3.13/site-packages (from requests->torch-geometric) (2.6.2)\n", + "Requirement already satisfied: certifi>=2017.4.17 in /opt/conda/lib/python3.13/site-packages (from requests->torch-geometric) (2026.1.4)\n" + ] + } + ], + "source": [ + "# Install required packages (for Colab)\n", + "!git clone https://github.com/ulab-uiuc/LLMRouter.git\n", + "%cd LLMRouter\n", + "!pip install -e .\n", + "!pip install torch torch-geometric\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "os.environ['OPENAI_API_KEY'] = 'your-key'\n", + "os.environ['ANTHROPIC_API_KEY'] = 'your-key'\n", + "# Or for multiple keys:\n", + "os.environ['API_KEYS'] = '[\"key1\", \"key2\"]'" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using device: cuda\n" + ] + } + ], + "source": [ + "import torch\n", + "from llmrouter.models.gmtrouter import GMTRouter, GMTRouterTrainer\n", + "from llmrouter.utils import setup_environment\n", + "\n", + "setup_environment()\n", + "\n", + "device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n", + "print(f\"Using device: {device}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Configuration\n", + "\n", + "GMTRouter uses the following configuration parameters:\n", + "\n", + "| Parameter | Description | Default |\n", + "|-----------|-------------|--------|\n", + "| `num_gnn_layers` | Number of HGT layers | 2 |\n", + "| `hidden_dim` | Hidden dimension | 128 |\n", + "| `dropout` | Dropout rate | 0.1 |\n", + "| `epochs` | Training epochs | 350 |\n", + "| `lr` | Learning rate | 5e-4 |\n", + "| `objective` | Training objective | \"auc\" |" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Current Configuration:\n", + "==================================================\n", + "checkpoint:\n", + " root: ./models\n", + " save_every: 25\n", + "config:\n", + " preprocess: true\n", + "data_path:\n", + " data_root: ./data\n", + " test_set: ./data/mt_bench/test_set.jsonl\n", + " training_set: ./data/mt_bench/training_set.jsonl\n", + " valid_set: ./data/mt_bench/valid_set.jsonl\n", + "dataset:\n", + " name: mt_bench\n", + " path: ./data\n", + "gmt_config:\n", + " dropout: 0.1\n", + " hidden_dim: 128\n", + " num_gnn_layers: 2\n", + " personalization: true\n", + "model_path:\n", + " checkpoint_root: ./saved_models/gmtrouter\n", + " load_model_path: ./saved_models/gmtrouter/gmtrouter.pt\n", + " save_model_path: ./saved_models/gmtrouter/gmtrouter.pt\n", + "train:\n", + " binary: true\n", + " epochs: 350\n", + " eval_every: 5\n", + " id: llmrouter_gmt\n", + " lr: 5e-4\n", + " objective: auc\n", + " prediction_count: 256\n", + " seed: 136\n", + "\n" + ] + } + ], + "source": [ + "import yaml\n", + "\n", + "CONFIG_PATH = \"configs/model_config_train/gmtrouter.yaml\"\n", + "\n", + "with open(CONFIG_PATH, 'r') as f:\n", + " config = yaml.safe_load(f)\n", + "\n", + "print(\"Current Configuration:\")\n", + "print(\"=\" * 50)\n", + "print(yaml.dump(config, default_flow_style=False))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Data Preparation\n", + "\n", + "GMTRouter requires specific data format. \n", + "\n", + "First, download the dataset to `./data/mt_bench`.\n", + "\n", + "Then, extract the dataset in place:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/home/zhongjie/LLMRouter\n" + ] + } + ], + "source": [ + "!pwd" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "rm -rf /home/zhongjie/LLMRouter/data/mt_bench/GMTRouter_Dataset " + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "GMTRouter Dataset/\n", + "GMTRouter Dataset/data/\n", + "GMTRouter Dataset/data/chatbot_arena/\n", + "GMTRouter Dataset/data/chatbot_arena/test_set.jsonl\n", + "GMTRouter Dataset/data/chatbot_arena/training_set.jsonl\n", + "GMTRouter Dataset/data/chatbot_arena/valid_set.jsonl\n", + "GMTRouter Dataset/data/mt_bench/\n", + "GMTRouter Dataset/data/mt_bench/test_set.jsonl\n", + "GMTRouter Dataset/data/mt_bench/training_set.jsonl\n", + "GMTRouter Dataset/data/mt_bench/valid_set.jsonl\n", + "GMTRouter Dataset/data/mmlu/\n", + "GMTRouter Dataset/data/mmlu/test_set.jsonl\n", + "GMTRouter Dataset/data/mmlu/training_set.jsonl\n", + "GMTRouter Dataset/data/mmlu/valid_set.jsonl\n", + "GMTRouter Dataset/data/gsm8k/\n", + "GMTRouter Dataset/data/gsm8k/test_set.jsonl\n", + "GMTRouter Dataset/data/gsm8k/training_set.jsonl\n", + "GMTRouter Dataset/data/gsm8k/valid_set.jsonl\n", + "GMTRouter Dataset/LICENSE\n", + "GMTRouter Dataset/README.md\n", + "mv: cannot stat './data/mt_bench/GMTRouter_dataset/*': No such file or directory\n", + "rmdir: failed to remove './data/mt_bench/GMTRouter_dataset': No such file or directory\n" + ] + } + ], + "source": [ + "!tar -xzvf ./data/mt_bench/GMTRouter_dataset.tar.gz -C ./data/mt_bench\n", + "\n", + "!mv \"./data/mt_bench/GMTRouter Dataset/data/mt_bench/\"* \"./data/mt_bench/\"\n", + "\n", + "!rm -rf \"./data/mt_bench/GMTRouter Dataset\"" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Dataset found at: ./data/mt_bench\n", + "Files: ['.ipynb_checkpoints', 'GMTRouter_dataset.tar.gz', 'mt_bench_to_jsonl.py', 'test_set.jsonl', 'training_set.jsonl', 'valid_set.jsonl']\n" + ] + } + ], + "source": [ + "# Check if data exists\n", + "data_path = config.get('data_path', {}).get('data_root', './data')\n", + "dataset_name = config.get('dataset', {}).get('name', 'mt_bench')\n", + "\n", + "expected_path = os.path.join(data_path, dataset_name)\n", + "\n", + "if os.path.exists(expected_path):\n", + " print(f\"Dataset found at: {expected_path}\")\n", + " files = os.listdir(expected_path)\n", + " print(f\"Files: {files}\")\n", + "else:\n", + " print(f\"Dataset not found at: {expected_path}\")\n", + " print(\"\\nPlease download the GMTRouter dataset:\")\n", + " print(\"1. Download from Google Drive\")\n", + " print(\"2. Extract to ./data/\")\n", + " print(\"3. Expected structure:\")\n", + " print(\" ./data/mt_bench/training_set.jsonl\")\n", + " print(\" ./data/mt_bench/valid_set.jsonl\")\n", + " print(\" ./data/mt_bench/test_set.jsonl\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Initialize Router" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "✅ MetaRouter initialized successfully (YAML + data loaded).\n", + "No pretrained model found at ./saved_models/gmtrouter/gmtrouter.pt\n", + "GMTRouter will need to be trained first.\n", + "Router initialized successfully!\n", + "Number of GNN layers: 2\n", + "Hidden dimension: 128\n", + "Personalization: True\n" + ] + } + ], + "source": [ + "router = GMTRouter(yaml_path=CONFIG_PATH)\n", + "\n", + "print(\"Router initialized successfully!\")\n", + "print(f\"Number of GNN layers: {config['gmt_config']['num_gnn_layers']}\")\n", + "print(f\"Hidden dimension: {config['gmt_config']['hidden_dim']}\")\n", + "print(f\"Personalization: {config['gmt_config']['personalization']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Graph Structure\n", + "\n", + "GMTRouter builds a heterogeneous graph with:\n", + "- **User nodes**: User embeddings and preferences\n", + "- **Session nodes**: Conversation session representations\n", + "- **Query nodes**: Query embeddings\n", + "- **LLM nodes**: LLM model embeddings\n", + "- **Response nodes**: Response quality scores" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(\"GMTRouter Graph Structure:\")\n", + "print(\"=\" * 50)\n", + "print(\"\\nNode Types:\")\n", + "print(\" 1. User - User preferences and history\")\n", + "print(\" 2. Session - Conversation sessions\")\n", + "print(\" 3. Query - User queries\")\n", + "print(\" 4. LLM - Language models\")\n", + "print(\" 5. Response - Model responses\")\n", + "print(\"\\nEdge Types (21 total):\")\n", + "print(\" - own/owned_by (User-Session)\")\n", + "print(\" - contains/contained_by (Session-Query)\")\n", + "print(\" - answered_by/answered_to (Query-Response)\")\n", + "print(\" - generated_by/generated (LLM-Response)\")\n", + "print(\" - next/prev (Query-Query temporal)\")\n", + "print(\" - ... and more\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. Training" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Trainer initialized!\n", + "Device: cuda\n" + ] + } + ], + "source": [ + "trainer = GMTRouterTrainer(router=router, device=device)\n", + "\n", + "print(\"Trainer initialized!\")\n", + "print(f\"Device: {device}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting training...\n", + "==================================================\n", + "Note: GMTRouter training uses pairwise learning on the graph.\n", + "This may take significant time for large datasets.\n", + "==================================================\n", + "======================================================================\n", + "GMTRouter Training\n", + "======================================================================\n", + "Loading training data from ./data/mt_bench/training_set.jsonl...\n", + "Loading GMTRouter data from ./data/mt_bench/training_set.jsonl...\n", + "Warning: Validation error at line 5:\n", + " 2 validation errors for GMTRouterInteraction\n", + "conversation.0.rating\n", + " Value error, Rating must be between 0 and 5 [type=value_error, input_value=-1.3121, input_type=float]\n", + " For further information visit https://errors.pydantic.dev/2.12/v/value_error\n", + "conversation.1.rating\n", + " Value error, Rating must be between 0 and 5 [type=value_error, input_value=-1.7922, input_type=float]\n", + " For further information visit https://errors.pydantic.dev/2.12/v/value_error\n", + "Warning: Validation error at line 6:\n", + " 2 validation errors for GMTRouterInteraction\n", + "conversation.0.rating\n", + " Value error, Rating must be between 0 and 5 [type=value_error, input_value=-0.842, input_type=float]\n", + " For further information visit https://errors.pydantic.dev/2.12/v/value_error\n", + "conversation.1.rating\n", + " Value error, Rating must be between 0 and 5 [type=value_error, input_value=-2.0202, input_type=float]\n", + " For further information visit https://errors.pydantic.dev/2.12/v/value_error\n", + "Warning: Validation error at line 7:\n", + " 1 validation error for GMTRouterInteraction\n", + "conversation.0.rating\n", + " Value error, Rating must be between 0 and 5 [type=value_error, input_value=-1.3121, input_type=float]\n", + " For further information visit https://errors.pydantic.dev/2.12/v/value_error\n", + "Warning: Validation error at line 8:\n", + " 1 validation error for GMTRouterInteraction\n", + "conversation.0.rating\n", + " Value error, Rating must be between 0 and 5 [type=value_error, input_value=-0.842, input_type=float]\n", + " For further information visit https://errors.pydantic.dev/2.12/v/value_error\n", + "Warning: Validation error at line 13:\n", + " 1 validation error for GMTRouterInteraction\n", + "conversation.0.rating\n", + " Value error, Rating must be between 0 and 5 [type=value_error, input_value=-0.6455, input_type=float]\n", + " For further information visit https://errors.pydantic.dev/2.12/v/value_error\n", + "Warning: Validation error at line 15:\n", + " 1 validation error for GMTRouterInteraction\n", + "conversation.0.rating\n", + " Value error, Rating must be between 0 and 5 [type=value_error, input_value=-0.6455, input_type=float]\n", + " For further information visit https://errors.pydantic.dev/2.12/v/value_error\n", + "Warning: Validation error at line 33:\n", + " 1 validation error for GMTRouterInteraction\n", + "conversation.0.rating\n", + " Value error, Rating must be between 0 and 5 [type=value_error, input_value=-0.4917, input_type=float]\n", + " For further information visit https://errors.pydantic.dev/2.12/v/value_error\n", + "Warning: Validation error at line 34:\n", + " 1 validation error for GMTRouterInteraction\n", + "conversation.0.rating\n", + " Value error, Rating must be between 0 and 5 [type=value_error, input_value=-0.2387, input_type=float]\n", + " For further information visit https://errors.pydantic.dev/2.12/v/value_error\n", + "Warning: Validation error at line 35:\n", + " 1 validation error for GMTRouterInteraction\n", + "conversation.0.rating\n", + " Value error, Rating must be between 0 and 5 [type=value_error, input_value=-0.4917, input_type=float]\n", + " For further information visit https://errors.pydantic.dev/2.12/v/value_error\n", + "Warning: Validation error at line 36:\n", + " 1 validation error for GMTRouterInteraction\n", + "conversation.0.rating\n", + " Value error, Rating must be between 0 and 5 [type=value_error, input_value=-0.2387, input_type=float]\n", + " For further information visit https://errors.pydantic.dev/2.12/v/value_error\n", + "Warning: Validation error at line 41:\n", + " 1 validation error for GMTRouterInteraction\n", + "conversation.0.rating\n", + " Value error, Rating must be between 0 and 5 [type=value_error, input_value=-0.0189, input_type=float]\n", + " For further information visit https://errors.pydantic.dev/2.12/v/value_error\n", + "Too many validation errors (>11). Please check data format.\n", + "See GMTRouter README for correct JSONL format.\n" + ] + }, + { + "ename": "ValueError", + "evalue": "Data validation failed", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mValidationError\u001b[39m Traceback (most recent call last)", + "\u001b[36mFile \u001b[39m\u001b[32m~/LLMRouter/llmrouter/models/gmtrouter/data_loader.py:188\u001b[39m, in \u001b[36mGMTRouterDataLoader.load_data\u001b[39m\u001b[34m(self, file_path)\u001b[39m\n\u001b[32m 187\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m188\u001b[39m validated_interaction = \u001b[43mGMTRouterInteraction\u001b[49m\u001b[43m(\u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mdata\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 189\u001b[39m \u001b[38;5;66;03m# Convert back to dict for processing\u001b[39;00m\n", + "\u001b[36mFile \u001b[39m\u001b[32m/opt/conda/lib/python3.13/site-packages/pydantic/main.py:250\u001b[39m, in \u001b[36mBaseModel.__init__\u001b[39m\u001b[34m(self, **data)\u001b[39m\n\u001b[32m 249\u001b[39m __tracebackhide__ = \u001b[38;5;28;01mTrue\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m250\u001b[39m validated_self = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m__pydantic_validator__\u001b[49m\u001b[43m.\u001b[49m\u001b[43mvalidate_python\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdata\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mself_instance\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[32m 251\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m validated_self:\n", + "\u001b[31mValidationError\u001b[39m: 1 validation error for GMTRouterInteraction\nconversation.0.rating\n Value error, Rating must be between 0 and 5 [type=value_error, input_value=-0.0189, input_type=float]\n For further information visit https://errors.pydantic.dev/2.12/v/value_error", + "\nDuring handling of the above exception, another exception occurred:\n", + "\u001b[31mValueError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[20]\u001b[39m\u001b[32m, line 7\u001b[39m\n\u001b[32m 4\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33m\"\u001b[39m\u001b[33mThis may take significant time for large datasets.\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m 5\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33m\"\u001b[39m\u001b[33m=\u001b[39m\u001b[33m\"\u001b[39m * \u001b[32m50\u001b[39m)\n\u001b[32m----> \u001b[39m\u001b[32m7\u001b[39m \u001b[43mtrainer\u001b[49m\u001b[43m.\u001b[49m\u001b[43mtrain\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 9\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33m\"\u001b[39m\u001b[33m=\u001b[39m\u001b[33m\"\u001b[39m * \u001b[32m50\u001b[39m)\n\u001b[32m 10\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33m\"\u001b[39m\u001b[33mTraining completed!\u001b[39m\u001b[33m\"\u001b[39m)\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/LLMRouter/llmrouter/models/gmtrouter/trainer.py:108\u001b[39m, in \u001b[36mGMTRouterTrainer.train\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 105\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33m\"\u001b[39m\u001b[33m=\u001b[39m\u001b[33m\"\u001b[39m * \u001b[32m70\u001b[39m)\n\u001b[32m 107\u001b[39m \u001b[38;5;66;03m# Load data\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m108\u001b[39m train_data, val_data = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mrouter\u001b[49m\u001b[43m.\u001b[49m\u001b[43mget_training_data\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 110\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m train_data \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m 111\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33m\"\u001b[39m\u001b[33mError: No training data loaded. Check data paths in config.\u001b[39m\u001b[33m\"\u001b[39m)\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/LLMRouter/llmrouter/models/gmtrouter/router.py:155\u001b[39m, in \u001b[36mGMTRouter.get_training_data\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 152\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mLoading training data from \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mtrain_path\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m...\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m 154\u001b[39m \u001b[38;5;66;03m# Load training data\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m155\u001b[39m train_graph, train_metadata = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mdata_loader\u001b[49m\u001b[43m.\u001b[49m\u001b[43mload_data\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtrain_path\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 157\u001b[39m \u001b[38;5;66;03m# Prepare training data structure\u001b[39;00m\n\u001b[32m 158\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mhasattr\u001b[39m(train_graph, \u001b[33m'\u001b[39m\u001b[33mx_dict\u001b[39m\u001b[33m'\u001b[39m):\n\u001b[32m 159\u001b[39m \u001b[38;5;66;03m# PyTorch Geometric HeteroData\u001b[39;00m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/LLMRouter/llmrouter/models/gmtrouter/data_loader.py:198\u001b[39m, in \u001b[36mGMTRouterDataLoader.load_data\u001b[39m\u001b[34m(self, file_path)\u001b[39m\n\u001b[32m 196\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mToo many validation errors (>\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mvalidation_errors\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m). Please check data format.\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m 197\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mSee GMTRouter README for correct JSONL format.\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m--> \u001b[39m\u001b[32m198\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[33m\"\u001b[39m\u001b[33mData validation failed\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m 199\u001b[39m \u001b[38;5;28;01mcontinue\u001b[39;00m\n\u001b[32m 201\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m json.JSONDecodeError \u001b[38;5;28;01mas\u001b[39;00m e:\n", + "\u001b[31mValueError\u001b[39m: Data validation failed" + ] + } + ], + "source": [ + "print(\"Starting training...\")\n", + "print(\"=\" * 50)\n", + "print(\"Note: GMTRouter training uses pairwise learning on the graph.\")\n", + "print(\"This may take significant time for large datasets.\")\n", + "print(\"=\" * 50)\n", + "\n", + "trainer.train()\n", + "\n", + "print(\"=\" * 50)\n", + "print(\"Training completed!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7. Model Verification" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Check saved model\n", + "save_path = config['model_path'].get('save_model_path', './saved_models/gmtrouter/gmtrouter.pt')\n", + "\n", + "if os.path.exists(save_path):\n", + " print(f\"Model saved at: {save_path}\")\n", + " checkpoint = torch.load(save_path, map_location='cpu')\n", + " print(f\"Checkpoint keys: {checkpoint.keys() if isinstance(checkpoint, dict) else 'state_dict'}\")\n", + "else:\n", + " print(f\"Model not found at: {save_path}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Test prediction\n", + "test_query = {\n", + " \"query\": \"What is machine learning?\",\n", + " \"user_id\": \"test_user\",\n", + " \"session_id\": \"test_session\"\n", + "}\n", + "result = router.route_single(test_query)\n", + "\n", + "print(f\"Test query: {test_query['query']}\")\n", + "print(f\"User: {test_query['user_id']}\")\n", + "print(f\"Routed to: {result['model_name']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "In this notebook, we:\n", + "\n", + "1. **Loaded Configuration**: Set up GMTRouter with YAML configuration\n", + "2. **Understood Graph Structure**: 5 node types, 21 edge types\n", + "3. **Trained Model**: Pairwise learning on heterogeneous graph\n", + "4. **Verified Model**: Tested personalized routing\n", + "\n", + "**Key Takeaways**:\n", + "- GMTRouter captures complex multi-turn conversation patterns\n", + "- User personalization improves routing quality\n", + "- HeteroGNN learns from relational structure\n", + "\n", + "**Next Steps**:\n", + "- Use the next part of notebook for inference\n", + "- Experiment with different datasets" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# GMTRouter - Inference\n", + "\n", + "This part of notebook demonstrates how to use a trained **GMTRouter** for inference." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Environment Setup (optional)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import torch\n", + "from llmrouter.models.gmtrouter import GMTRouter\n", + "from llmrouter.utils import setup_environment\n", + "import yaml\n", + "\n", + "setup_environment()\n", + "\n", + "device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n", + "print(f\"Using device: {device}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Load Trained Router" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "CONFIG_PATH = \"configs/model_config_train/gmtrouter.yaml\"\n", + "\n", + "router = GMTRouter(yaml_path=CONFIG_PATH)\n", + "print(\"Router loaded!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Personalized Query Routing" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Multi-turn conversation example\n", + "CONVERSATION = [\n", + " {\n", + " \"query\": \"What is machine learning?\",\n", + " \"user_id\": \"user_001\",\n", + " \"session_id\": \"session_001\",\n", + " \"turn\": 1\n", + " },\n", + " {\n", + " \"query\": \"Can you give me a practical example?\",\n", + " \"user_id\": \"user_001\",\n", + " \"session_id\": \"session_001\",\n", + " \"turn\": 2\n", + " },\n", + " {\n", + " \"query\": \"How do I implement this in Python?\",\n", + " \"user_id\": \"user_001\",\n", + " \"session_id\": \"session_001\",\n", + " \"turn\": 3\n", + " },\n", + "]\n", + "\n", + "print(\"Multi-turn Routing Results:\")\n", + "print(\"=\" * 60)\n", + "\n", + "for query in CONVERSATION:\n", + " result = router.route_single(query)\n", + " print(f\"Turn {query['turn']}: {query['query'][:40]}...\")\n", + " print(f\" Routed to: {result['model_name']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Different User Preferences" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Same query, different users\n", + "query_text = \"Explain neural networks.\"\n", + "\n", + "users = [\"user_001\", \"user_002\", \"user_003\"]\n", + "\n", + "print(f\"Query: {query_text}\")\n", + "print(\"=\" * 60)\n", + "\n", + "for user in users:\n", + " query = {\n", + " \"query\": query_text,\n", + " \"user_id\": user,\n", + " \"session_id\": f\"{user}_session\"\n", + " }\n", + " result = router.route_single(query)\n", + " print(f\"{user}: Routed to {result['model_name']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. File-Based Inference\n", + "\n", + "Load queries from a file and save results." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "\n", + "# Load queries from a JSONL file\n", + "def load_queries_from_file(file_path):\n", + " \"\"\"Load queries from a JSONL file.\"\"\"\n", + " queries = []\n", + " with open(file_path, 'r', encoding='utf-8') as f:\n", + " for line in f:\n", + " if line.strip():\n", + " queries.append(json.loads(line))\n", + " return queries\n", + "\n", + "# Save results to a JSONL file\n", + "def save_results_to_file(results, output_path):\n", + " \"\"\"Save routing results to a JSONL file.\"\"\"\n", + " os.makedirs(os.path.dirname(output_path), exist_ok=True)\n", + " with open(output_path, 'w', encoding='utf-8') as f:\n", + " for result in results:\n", + " f.write(json.dumps(result, ensure_ascii=False) + '\\n')\n", + " print(f\"Results saved to: {output_path}\")\n", + "\n", + "# Example: Load from default query file\n", + "QUERY_FILE = \"data/example_data/query_data/default_query_test.jsonl\"\n", + "OUTPUT_FILE = \"outputs/gmtrouter_results.jsonl\"\n", + "\n", + "if os.path.exists(QUERY_FILE):\n", + " # Load queries\n", + " file_queries = load_queries_from_file(QUERY_FILE)\n", + " print(f\"Loaded {len(file_queries)} queries from: {QUERY_FILE}\")\n", + " \n", + " # Route queries\n", + " file_results = router.route_batch(batch=file_queries[:10])\n", + " print(f\"Routed {len(file_results)} queries\")\n", + " \n", + " # Save results\n", + " save_results_to_file(file_results, OUTPUT_FILE)\n", + " \n", + " # Show sample results\n", + " print(f\"\\nSample results:\")\n", + " for i, result in enumerate(file_results[:3], 1):\n", + " print(f\" {i}. {result.get('query', '')[:40]}... -> {result['model_name']}\")\n", + "else:\n", + " print(f\"Query file not found: {QUERY_FILE}\")\n", + " print(\"Create a JSONL file with format: {\\\"query\\\": \\\"Your question\\\"}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "GMTRouter provides:\n", + "- Personalized routing based on user history\n", + "- Multi-turn context awareness\n", + "- Graph-based relationship modeling" + ] + } + ], + "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.13.11" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/colab_notebooks/graphrouter/01_graphrouter_training_and_inference.ipynb b/colab_notebooks/graphrouter/01_graphrouter_training_and_inference.ipynb new file mode 100644 index 0000000..bfb18ee --- /dev/null +++ b/colab_notebooks/graphrouter/01_graphrouter_training_and_inference.ipynb @@ -0,0 +1,846 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# GraphRouter - Training\n", + "\n", + "This notebook demonstrates how to train the **GraphRouter** (Graph Neural Network Router).\n", + "\n", + "## Overview\n", + "\n", + "GraphRouter uses a Graph Neural Network (GNN) to model the relationships between queries and LLMs.\n", + "It constructs a heterogeneous graph where queries and LLMs are nodes, and performance scores are edge weights.\n", + "\n", + "**Key Features**:\n", + "- Graph-based representation of query-LLM interactions\n", + "- Message passing for learning representations\n", + "- Can capture complex relational patterns" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Environment Setup" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cloning into 'LLMRouter'...\n", + "remote: Enumerating objects: 5897, done.\u001b[K\n", + "remote: Counting objects: 100% (220/220), done.\u001b[K\n", + "remote: Compressing objects: 100% (136/136), done.\u001b[K\n", + "remote: Total 5897 (delta 109), reused 119 (delta 84), pack-reused 5677 (from 1)\u001b[K\n", + "Receiving objects: 100% (5897/5897), 88.90 MiB | 50.58 MiB/s, done.\n", + "Resolving deltas: 100% (2880/2880), done.\n", + "Updating files: 100% (280/280), done.\n", + "/home/zhongjie/LLMRouter\n", + "Obtaining file:///home/zhongjie/LLMRouter\n", + " Installing build dependencies ... \u001b[?25ldone\n", + "\u001b[?25h Checking if build backend supports build_editable ... \u001b[?25ldone\n", + "\u001b[?25h Getting requirements to build editable ... \u001b[?25ldone\n", + "\u001b[?25h Preparing editable metadata (pyproject.toml) ... \u001b[?25ldone\n", + "\u001b[?25hRequirement already satisfied: torch>=2.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (2.9.1)\n", + "Requirement already satisfied: transformers>=4.40 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (4.57.3)\n", + "Requirement already satisfied: sentencepiece>=0.1.99 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (0.2.1)\n", + "Requirement already satisfied: numpy>=1.21 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (2.4.1)\n", + "Requirement already satisfied: pandas>=1.5 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (2.3.3)\n", + "Requirement already satisfied: scikit-learn>=1.2 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (1.8.0)\n", + "Requirement already satisfied: pyyaml>=6.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (6.0.3)\n", + "Requirement already satisfied: tqdm>=4.65 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (4.67.1)\n", + "Requirement already satisfied: datasets>=2.14 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (4.4.2)\n", + "Requirement already satisfied: pydantic>=2.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (2.12.5)\n", + "Requirement already satisfied: gradio>=4.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (6.3.0)\n", + "Requirement already satisfied: litellm>=1.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (1.80.13)\n", + "Requirement already satisfied: peft>=0.7 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (0.18.1)\n", + "Requirement already satisfied: torch-geometric>=2.3 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (2.7.0)\n", + "Requirement already satisfied: scipy>=1.10 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (1.17.0)\n", + "Requirement already satisfied: protobuf>=3.20 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (6.33.3)\n", + "Requirement already satisfied: filelock in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.1.1) (3.20.3)\n", + "Requirement already satisfied: pyarrow>=21.0.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.1.1) (22.0.0)\n", + "Requirement already satisfied: dill<0.4.1,>=0.3.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.1.1) (0.4.0)\n", + "Requirement already satisfied: requests>=2.32.2 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.1.1) (2.32.5)\n", + "Requirement already satisfied: httpx<1.0.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.1.1) (0.28.1)\n", + "Requirement already satisfied: xxhash in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.1.1) (3.6.0)\n", + "Requirement already satisfied: multiprocess<0.70.19 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.1.1) (0.70.18)\n", + "Requirement already satisfied: fsspec<=2025.10.0,>=2023.1.0 in /opt/conda/lib/python3.13/site-packages (from fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.1.1) (2025.10.0)\n", + "Requirement already satisfied: huggingface-hub<2.0,>=0.25.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.1.1) (0.36.0)\n", + "Requirement already satisfied: packaging in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.1.1) (25.0)\n", + "Requirement already satisfied: aiohttp!=4.0.0a0,!=4.0.0a1 in /opt/conda/lib/python3.13/site-packages (from fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.1.1) (3.13.3)\n", + "Requirement already satisfied: anyio in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.1.1) (4.12.0)\n", + "Requirement already satisfied: certifi in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.1.1) (2026.1.4)\n", + "Requirement already satisfied: httpcore==1.* in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.1.1) (1.0.9)\n", + "Requirement already satisfied: idna in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.1.1) (3.11)\n", + "Requirement already satisfied: h11>=0.16 in /opt/conda/lib/python3.13/site-packages (from httpcore==1.*->httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.1.1) (0.16.0)\n", + "Requirement already satisfied: typing-extensions>=3.7.4.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<2.0,>=0.25.0->datasets>=2.14->llmrouter-lib==0.1.1) (4.15.0)\n", + "Requirement already satisfied: hf-xet<2.0.0,>=1.1.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<2.0,>=0.25.0->datasets>=2.14->llmrouter-lib==0.1.1) (1.2.0)\n", + "Requirement already satisfied: aiohappyeyeballs>=2.5.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.1.1) (2.6.1)\n", + "Requirement already satisfied: aiosignal>=1.4.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.1.1) (1.4.0)\n", + "Requirement already satisfied: attrs>=17.3.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.1.1) (25.4.0)\n", + "Requirement already satisfied: frozenlist>=1.1.1 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.1.1) (1.8.0)\n", + "Requirement already satisfied: multidict<7.0,>=4.5 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.1.1) (6.7.0)\n", + "Requirement already satisfied: propcache>=0.2.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.1.1) (0.4.1)\n", + "Requirement already satisfied: yarl<2.0,>=1.17.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.1.1) (1.22.0)\n", + "Requirement already satisfied: aiofiles<25.0,>=22.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (24.1.0)\n", + "Requirement already satisfied: audioop-lts<1.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (0.2.2)\n", + "Requirement already satisfied: brotli>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (1.2.0)\n", + "Requirement already satisfied: fastapi<1.0,>=0.115.2 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (0.128.0)\n", + "Requirement already satisfied: ffmpy in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (1.0.0)\n", + "Requirement already satisfied: gradio-client==2.0.3 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (2.0.3)\n", + "Requirement already satisfied: groovy~=0.1 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (0.1.2)\n", + "Requirement already satisfied: jinja2<4.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (3.1.6)\n", + "Requirement already satisfied: markupsafe<4.0,>=2.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (3.0.3)\n", + "Requirement already satisfied: orjson~=3.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (3.11.5)\n", + "Requirement already satisfied: pillow<13.0,>=8.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (12.1.0)\n", + "Requirement already satisfied: pydub in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (0.25.1)\n", + "Requirement already satisfied: python-multipart>=0.0.18 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (0.0.21)\n", + "Requirement already satisfied: safehttpx<0.2.0,>=0.1.7 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (0.1.7)\n", + "Requirement already satisfied: semantic-version~=2.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (2.10.0)\n", + "Requirement already satisfied: starlette<1.0,>=0.40.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (0.50.0)\n", + "Requirement already satisfied: tomlkit<0.14.0,>=0.12.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (0.13.3)\n", + "Requirement already satisfied: typer<1.0,>=0.12 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (0.21.1)\n", + "Requirement already satisfied: uvicorn>=0.14.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (0.40.0)\n", + "Requirement already satisfied: annotated-doc>=0.0.2 in /opt/conda/lib/python3.13/site-packages (from fastapi<1.0,>=0.115.2->gradio>=4.0->llmrouter-lib==0.1.1) (0.0.4)\n", + "Requirement already satisfied: python-dateutil>=2.8.2 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.1.1) (2.9.0.post0)\n", + "Requirement already satisfied: pytz>=2020.1 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.1.1) (2025.2)\n", + "Requirement already satisfied: tzdata>=2022.7 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.1.1) (2025.3)\n", + "Requirement already satisfied: annotated-types>=0.6.0 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.1.1) (0.7.0)\n", + "Requirement already satisfied: pydantic-core==2.41.5 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.1.1) (2.41.5)\n", + "Requirement already satisfied: typing-inspection>=0.4.2 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.1.1) (0.4.2)\n", + "Requirement already satisfied: click>=8.0.0 in /opt/conda/lib/python3.13/site-packages (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.1.1) (8.3.1)\n", + "Requirement already satisfied: shellingham>=1.3.0 in /opt/conda/lib/python3.13/site-packages (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.1.1) (1.5.4)\n", + "Requirement already satisfied: rich>=10.11.0 in /opt/conda/lib/python3.13/site-packages (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.1.1) (14.2.0)\n", + "Requirement already satisfied: fastuuid>=0.13.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.1.1) (0.14.0)\n", + "Requirement already satisfied: grpcio!=1.68.*,!=1.69.*,!=1.70.*,!=1.71.0,!=1.71.1,!=1.72.0,!=1.72.1,!=1.73.0,>=1.62.3 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.1.1) (1.76.0)\n", + "Requirement already satisfied: importlib-metadata>=6.8.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.1.1) (8.7.0)\n", + "Requirement already satisfied: jsonschema<5.0.0,>=4.23.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.1.1) (4.25.1)\n", + "Requirement already satisfied: openai>=2.8.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.1.1) (2.15.0)\n", + "Requirement already satisfied: python-dotenv>=0.2.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.1.1) (1.2.1)\n", + "Requirement already satisfied: tiktoken>=0.7.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.1.1) (0.12.0)\n", + "Requirement already satisfied: tokenizers in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.1.1) (0.22.2)\n", + "Requirement already satisfied: jsonschema-specifications>=2023.03.6 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.1.1) (2025.9.1)\n", + "Requirement already satisfied: referencing>=0.28.4 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.1.1) (0.37.0)\n", + "Requirement already satisfied: rpds-py>=0.7.1 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.1.1) (0.30.0)\n", + "Requirement already satisfied: zipp>=3.20 in /opt/conda/lib/python3.13/site-packages (from importlib-metadata>=6.8.0->litellm>=1.0->llmrouter-lib==0.1.1) (3.23.0)\n", + "Requirement already satisfied: distro<2,>=1.7.0 in /opt/conda/lib/python3.13/site-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.1.1) (1.9.0)\n", + "Requirement already satisfied: jiter<1,>=0.10.0 in /opt/conda/lib/python3.13/site-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.1.1) (0.12.0)\n", + "Requirement already satisfied: sniffio in /opt/conda/lib/python3.13/site-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.1.1) (1.3.1)\n", + "Requirement already satisfied: psutil in /opt/conda/lib/python3.13/site-packages (from peft>=0.7->llmrouter-lib==0.1.1) (7.2.1)\n", + "Requirement already satisfied: accelerate>=0.21.0 in /opt/conda/lib/python3.13/site-packages (from peft>=0.7->llmrouter-lib==0.1.1) (1.12.0)\n", + "Requirement already satisfied: safetensors in /opt/conda/lib/python3.13/site-packages (from peft>=0.7->llmrouter-lib==0.1.1) (0.7.0)\n", + "Requirement already satisfied: six>=1.5 in /opt/conda/lib/python3.13/site-packages (from python-dateutil>=2.8.2->pandas>=1.5->llmrouter-lib==0.1.1) (1.17.0)\n", + "Requirement already satisfied: charset_normalizer<4,>=2 in /opt/conda/lib/python3.13/site-packages (from requests>=2.32.2->datasets>=2.14->llmrouter-lib==0.1.1) (3.4.4)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/conda/lib/python3.13/site-packages (from requests>=2.32.2->datasets>=2.14->llmrouter-lib==0.1.1) (2.6.2)\n", + "Requirement already satisfied: markdown-it-py>=2.2.0 in /opt/conda/lib/python3.13/site-packages (from rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.1.1) (4.0.0)\n", + "Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /opt/conda/lib/python3.13/site-packages (from rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.1.1) (2.19.2)\n", + "Requirement already satisfied: mdurl~=0.1 in /opt/conda/lib/python3.13/site-packages (from markdown-it-py>=2.2.0->rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.1.1) (0.1.2)\n", + "Requirement already satisfied: joblib>=1.3.0 in /opt/conda/lib/python3.13/site-packages (from scikit-learn>=1.2->llmrouter-lib==0.1.1) (1.5.3)\n", + "Requirement already satisfied: threadpoolctl>=3.2.0 in /opt/conda/lib/python3.13/site-packages (from scikit-learn>=1.2->llmrouter-lib==0.1.1) (3.6.0)\n", + "Requirement already satisfied: regex>=2022.1.18 in /opt/conda/lib/python3.13/site-packages (from tiktoken>=0.7.0->litellm>=1.0->llmrouter-lib==0.1.1) (2025.11.3)\n", + "Requirement already satisfied: setuptools in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (80.9.0)\n", + "Requirement already satisfied: sympy>=1.13.3 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (1.14.0)\n", + "Requirement already satisfied: networkx>=2.5.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (3.6.1)\n", + "Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (12.8.93)\n", + "Requirement already satisfied: nvidia-cuda-runtime-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (12.8.90)\n", + "Requirement already satisfied: nvidia-cuda-cupti-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (12.8.90)\n", + "Requirement already satisfied: nvidia-cudnn-cu12==9.10.2.21 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (9.10.2.21)\n", + "Requirement already satisfied: nvidia-cublas-cu12==12.8.4.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (12.8.4.1)\n", + "Requirement already satisfied: nvidia-cufft-cu12==11.3.3.83 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (11.3.3.83)\n", + "Requirement already satisfied: nvidia-curand-cu12==10.3.9.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (10.3.9.90)\n", + "Requirement already satisfied: nvidia-cusolver-cu12==11.7.3.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (11.7.3.90)\n", + "Requirement already satisfied: nvidia-cusparse-cu12==12.5.8.93 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (12.5.8.93)\n", + "Requirement already satisfied: nvidia-cusparselt-cu12==0.7.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (0.7.1)\n", + "Requirement already satisfied: nvidia-nccl-cu12==2.27.5 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (2.27.5)\n", + "Requirement already satisfied: nvidia-nvshmem-cu12==3.3.20 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (3.3.20)\n", + "Requirement already satisfied: nvidia-nvtx-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (12.8.90)\n", + "Requirement already satisfied: nvidia-nvjitlink-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (12.8.93)\n", + "Requirement already satisfied: nvidia-cufile-cu12==1.13.1.3 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (1.13.1.3)\n", + "Requirement already satisfied: triton==3.5.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (3.5.1)\n", + "Requirement already satisfied: mpmath<1.4,>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from sympy>=1.13.3->torch>=2.0->llmrouter-lib==0.1.1) (1.3.0)\n", + "Requirement already satisfied: pyparsing in /opt/conda/lib/python3.13/site-packages (from torch-geometric>=2.3->llmrouter-lib==0.1.1) (3.3.1)\n", + "Building wheels for collected packages: llmrouter-lib\n", + " Building editable for llmrouter-lib (pyproject.toml) ... \u001b[?25ldone\n", + "\u001b[?25h Created wheel for llmrouter-lib: filename=llmrouter_lib-0.1.1-0.editable-py3-none-any.whl size=14451 sha256=56b69718b77669f1ae7af89e2f02ce48c162fc47ec91589e7d06a5d9039127d8\n", + " Stored in directory: /tmp/pip-ephem-wheel-cache-dw036n5u/wheels/82/4a/fd/59c4aec93c356c380d86a88ab74bece164c0aa855d68b155e4\n", + "Successfully built llmrouter-lib\n", + "Installing collected packages: llmrouter-lib\n", + " Attempting uninstall: llmrouter-lib\n", + " Found existing installation: llmrouter-lib 0.1.1\n", + " Uninstalling llmrouter-lib-0.1.1:\n", + " Successfully uninstalled llmrouter-lib-0.1.1\n", + "Successfully installed llmrouter-lib-0.1.1\n", + "Requirement already satisfied: torch in /opt/conda/lib/python3.13/site-packages (2.9.1)\n", + "Requirement already satisfied: torch-geometric in /opt/conda/lib/python3.13/site-packages (2.7.0)\n", + "Requirement already satisfied: filelock in /opt/conda/lib/python3.13/site-packages (from torch) (3.20.3)\n", + "Requirement already satisfied: typing-extensions>=4.10.0 in /opt/conda/lib/python3.13/site-packages (from torch) (4.15.0)\n", + "Requirement already satisfied: setuptools in /opt/conda/lib/python3.13/site-packages (from torch) (80.9.0)\n", + "Requirement already satisfied: sympy>=1.13.3 in /opt/conda/lib/python3.13/site-packages (from torch) (1.14.0)\n", + "Requirement already satisfied: networkx>=2.5.1 in /opt/conda/lib/python3.13/site-packages (from torch) (3.6.1)\n", + "Requirement already satisfied: jinja2 in /opt/conda/lib/python3.13/site-packages (from torch) (3.1.6)\n", + "Requirement already satisfied: fsspec>=0.8.5 in /opt/conda/lib/python3.13/site-packages (from torch) (2025.10.0)\n", + "Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.93)\n", + "Requirement already satisfied: nvidia-cuda-runtime-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-cuda-cupti-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-cudnn-cu12==9.10.2.21 in /opt/conda/lib/python3.13/site-packages (from torch) (9.10.2.21)\n", + "Requirement already satisfied: nvidia-cublas-cu12==12.8.4.1 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.4.1)\n", + "Requirement already satisfied: nvidia-cufft-cu12==11.3.3.83 in /opt/conda/lib/python3.13/site-packages (from torch) (11.3.3.83)\n", + "Requirement already satisfied: nvidia-curand-cu12==10.3.9.90 in /opt/conda/lib/python3.13/site-packages (from torch) (10.3.9.90)\n", + "Requirement already satisfied: nvidia-cusolver-cu12==11.7.3.90 in /opt/conda/lib/python3.13/site-packages (from torch) (11.7.3.90)\n", + "Requirement already satisfied: nvidia-cusparse-cu12==12.5.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.5.8.93)\n", + "Requirement already satisfied: nvidia-cusparselt-cu12==0.7.1 in /opt/conda/lib/python3.13/site-packages (from torch) (0.7.1)\n", + "Requirement already satisfied: nvidia-nccl-cu12==2.27.5 in /opt/conda/lib/python3.13/site-packages (from torch) (2.27.5)\n", + "Requirement already satisfied: nvidia-nvshmem-cu12==3.3.20 in /opt/conda/lib/python3.13/site-packages (from torch) (3.3.20)\n", + "Requirement already satisfied: nvidia-nvtx-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-nvjitlink-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.93)\n", + "Requirement already satisfied: nvidia-cufile-cu12==1.13.1.3 in /opt/conda/lib/python3.13/site-packages (from torch) (1.13.1.3)\n", + "Requirement already satisfied: triton==3.5.1 in /opt/conda/lib/python3.13/site-packages (from torch) (3.5.1)\n", + "Requirement already satisfied: aiohttp in /opt/conda/lib/python3.13/site-packages (from torch-geometric) (3.13.3)\n", + "Requirement already satisfied: numpy in /opt/conda/lib/python3.13/site-packages (from torch-geometric) (2.4.1)\n", + "Requirement already satisfied: psutil>=5.8.0 in /opt/conda/lib/python3.13/site-packages (from torch-geometric) (7.2.1)\n", + "Requirement already satisfied: pyparsing in /opt/conda/lib/python3.13/site-packages (from torch-geometric) (3.3.1)\n", + "Requirement already satisfied: requests in /opt/conda/lib/python3.13/site-packages (from torch-geometric) (2.32.5)\n", + "Requirement already satisfied: tqdm in /opt/conda/lib/python3.13/site-packages (from torch-geometric) (4.67.1)\n", + "Requirement already satisfied: xxhash in /opt/conda/lib/python3.13/site-packages (from torch-geometric) (3.6.0)\n", + "Requirement already satisfied: mpmath<1.4,>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from sympy>=1.13.3->torch) (1.3.0)\n", + "Requirement already satisfied: aiohappyeyeballs>=2.5.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp->torch-geometric) (2.6.1)\n", + "Requirement already satisfied: aiosignal>=1.4.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp->torch-geometric) (1.4.0)\n", + "Requirement already satisfied: attrs>=17.3.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp->torch-geometric) (25.4.0)\n", + "Requirement already satisfied: frozenlist>=1.1.1 in /opt/conda/lib/python3.13/site-packages (from aiohttp->torch-geometric) (1.8.0)\n", + "Requirement already satisfied: multidict<7.0,>=4.5 in /opt/conda/lib/python3.13/site-packages (from aiohttp->torch-geometric) (6.7.0)\n", + "Requirement already satisfied: propcache>=0.2.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp->torch-geometric) (0.4.1)\n", + "Requirement already satisfied: yarl<2.0,>=1.17.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp->torch-geometric) (1.22.0)\n", + "Requirement already satisfied: idna>=2.0 in /opt/conda/lib/python3.13/site-packages (from yarl<2.0,>=1.17.0->aiohttp->torch-geometric) (3.11)\n", + "Requirement already satisfied: MarkupSafe>=2.0 in /opt/conda/lib/python3.13/site-packages (from jinja2->torch) (3.0.3)\n", + "Requirement already satisfied: charset_normalizer<4,>=2 in /opt/conda/lib/python3.13/site-packages (from requests->torch-geometric) (3.4.4)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/conda/lib/python3.13/site-packages (from requests->torch-geometric) (2.6.2)\n", + "Requirement already satisfied: certifi>=2017.4.17 in /opt/conda/lib/python3.13/site-packages (from requests->torch-geometric) (2026.1.4)\n" + ] + } + ], + "source": [ + "# Install required packages (for Colab)\n", + "!git clone https://github.com/ulab-uiuc/LLMRouter.git\n", + "%cd LLMRouter\n", + "!pip install -e .\n", + "!pip install torch torch-geometric\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "os.environ['OPENAI_API_KEY'] = 'your-key'\n", + "os.environ['ANTHROPIC_API_KEY'] = 'your-key'\n", + "# Or for multiple keys:\n", + "os.environ['API_KEYS'] = '[\"key1\", \"key2\"]'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import torch\n", + "from llmrouter.models.graphrouter import GraphRouter, GraphTrainer\n", + "from llmrouter.utils import setup_environment\n", + "\n", + "setup_environment()\n", + "\n", + "device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n", + "print(f\"Using device: {device}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Configuration\n", + "\n", + "GraphRouter uses the following configuration parameters:\n", + "\n", + "| Parameter | Description | Default |\n", + "|-----------|-------------|--------|\n", + "| `hidden_dim` | GNN hidden layer dimension | 64 |\n", + "| `learning_rate` | Learning rate | 0.001 |\n", + "| `weight_decay` | L2 regularization | 0.0001 |\n", + "| `train_epoch` | Training epochs | 100 |\n", + "| `batch_size` | Batch size | 4 |\n", + "| `train_mask_rate` | Edge masking rate | 0.3 |\n", + "| `val_split_ratio` | Validation split | 0.2 |" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Current Configuration:\n", + "==================================================\n", + "data_path:\n", + " llm_data: data/example_data/llm_candidates/default_llm.json\n", + " llm_embedding_data: data/example_data/llm_candidates/default_llm_embeddings.json\n", + " query_data_test: data/example_data/query_data/default_query_test.jsonl\n", + " query_data_train: data/example_data/query_data/default_query_train.jsonl\n", + " query_embedding_data: data/example_data/routing_data/query_embeddings_longformer.pt\n", + " routing_data_test: data/example_data/routing_data/default_routing_test_data.jsonl\n", + " routing_data_train: data/example_data/routing_data/default_routing_train_data.jsonl\n", + "hparam:\n", + " batch_size: 4\n", + " hidden_dim: 64\n", + " learning_rate: 0.001\n", + " random_state: 42\n", + " train_epoch: 100\n", + " train_mask_rate: 0.3\n", + " val_split_ratio: 0.2\n", + " weight_decay: 0.0001\n", + "metric:\n", + " weights:\n", + " cost: 0\n", + " llm_judge: 0\n", + " performance: 1\n", + "model_path:\n", + " ini_model_path: ''\n", + " load_model_path: saved_models/graphrouter/graphrouter.pt\n", + " save_model_path: saved_models/graphrouter/graphrouter.pt\n", + "\n" + ] + } + ], + "source": [ + "import yaml\n", + "\n", + "CONFIG_PATH = \"configs/model_config_train/graphrouter.yaml\"\n", + "\n", + "with open(CONFIG_PATH, 'r') as f:\n", + " config = yaml.safe_load(f)\n", + "\n", + "print(\"Current Configuration:\")\n", + "print(\"=\" * 50)\n", + "print(yaml.dump(config, default_flow_style=False))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Initialize Router" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "✅ MetaRouter initialized successfully (YAML + data loaded).\n", + "Router initialized successfully!\n", + "Number of training samples: 50544\n", + "Number of LLM candidates: 14\n", + "LLM candidates: ['qwen2.5-7b-instruct', 'codegemma-7b', 'gemma-2-9b-it', 'llama-3.1-8b-instruct', 'llama3-chatqa-1.5-8b', 'mistral-7b-instruct-v0.3', 'llama-3.3-nemotron-super-49b-v1', 'llama-3.1-nemotron-51b-instruct', 'llama3-chatqa-1.5-70b', 'llama3-70b-instruct', 'mixtral-8x7b-instruct-v0.1', 'mixtral-8x22b-instruct-v0.1', 'palmyra-creative-122b', 'mistral-nemo-12b-instruct']\n" + ] + } + ], + "source": [ + "router = GraphRouter(yaml_path=CONFIG_PATH)\n", + "\n", + "print(\"Router initialized successfully!\")\n", + "print(f\"Number of training samples: {len(router.routing_data_train)}\")\n", + "print(f\"Number of LLM candidates: {len(router.llm_data)}\")\n", + "print(f\"LLM candidates: {list(router.llm_data.keys())}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Graph Structure Visualization" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Graph Structure Information:\n", + "==================================================\n", + "\n", + "Node types:\n", + " - Query nodes: Based on training queries\n", + " - LLM nodes: 14 models\n", + "\n", + "Edge types:\n", + " - Query -> LLM edges (performance scores)\n", + "\n", + "The GNN learns to predict missing edges for new queries.\n" + ] + } + ], + "source": [ + "# Understand the graph structure\n", + "print(\"Graph Structure Information:\")\n", + "print(\"=\" * 50)\n", + "print(f\"\\nNode types:\")\n", + "print(f\" - Query nodes: Based on training queries\")\n", + "print(f\" - LLM nodes: {len(router.llm_data)} models\")\n", + "print(f\"\\nEdge types:\")\n", + "print(f\" - Query -> LLM edges (performance scores)\")\n", + "print(f\"\\nThe GNN learns to predict missing edges for new queries.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Training" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Trainer initialized!\n", + "Device: cuda\n", + "Save path: /home/zhongjie/LLMRouter/llmrouter/saved_models/graphrouter/graphrouter.pt\n" + ] + } + ], + "source": [ + "trainer = GraphTrainer(router=router, device=device)\n", + "\n", + "print(\"Trainer initialized!\")\n", + "print(f\"Device: {device}\")\n", + "print(f\"Save path: {trainer.save_model_path}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting training...\n", + "==================================================\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/zhongjie/LLMRouter/llmrouter/models/graphrouter/graph_nn.py:136: UserWarning: To copy construct from a tensor, it is recommended to use sourceTensor.detach().clone() or sourceTensor.detach().clone().requires_grad_(True), rather than torch.tensor(sourceTensor).\n", + " self.train_mask = torch.tensor(data.train_mask, dtype=torch.bool)\n", + "/home/zhongjie/LLMRouter/llmrouter/models/graphrouter/graph_nn.py:137: UserWarning: To copy construct from a tensor, it is recommended to use sourceTensor.detach().clone() or sourceTensor.detach().clone().requires_grad_(True), rather than torch.tensor(sourceTensor).\n", + " self.valide_mask = torch.tensor(data.valide_mask, dtype=torch.bool)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 0: train_loss=1.2877, val_result=0.1774\n", + "Epoch 10: train_loss=0.4122, val_result=0.6208\n", + "Epoch 20: train_loss=0.4345, val_result=0.1774\n", + "Epoch 30: train_loss=0.3860, val_result=0.6208\n", + "Epoch 40: train_loss=0.3645, val_result=0.1800\n", + "Epoch 50: train_loss=0.3542, val_result=0.1782\n", + "Epoch 60: train_loss=0.3482, val_result=0.3299\n", + "Epoch 70: train_loss=0.3493, val_result=0.1827\n", + "Epoch 80: train_loss=0.3452, val_result=0.2612\n", + "Epoch 90: train_loss=0.3459, val_result=0.2193\n", + "Training completed. Best validation result: 0.6208\n", + "==================================================\n", + "Training completed!\n", + "Best validation result: 0.6208158731460571\n" + ] + } + ], + "source": [ + "print(\"Starting training...\")\n", + "print(\"=\" * 50)\n", + "\n", + "best_result = trainer.train()\n", + "\n", + "print(\"=\" * 50)\n", + "print(\"Training completed!\")\n", + "if best_result:\n", + " print(f\"Best validation result: {best_result}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. Model Verification" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Model loaded from: /home/zhongjie/LLMRouter/llmrouter/saved_models/graphrouter/graphrouter.pt\n" + ] + } + ], + "source": [ + "# Verify the trained model\n", + "import os\n", + "model_path = trainer.save_model_path\n", + "if os.path.exists(model_path):\n", + " checkpoint = torch.load(model_path, map_location='cpu')\n", + " print(f\"Model loaded from: {model_path}\")\n", + "else:\n", + " print(f\"Model not found at: {model_path}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Input ids are automatically padded to be a multiple of `config.attention_window`: 512\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Test query: What is the capital of France?\n", + "Routed to: llama3-chatqa-1.5-8b\n" + ] + } + ], + "source": [ + "# Test prediction\n", + "test_query = {\"query\": \"What is the capital of France?\"}\n", + "result = router.route_single(test_query)\n", + "\n", + "print(f\"Test query: {test_query['query']}\")\n", + "print(f\"Routed to: {result['model_name']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "In this notebook, we:\n", + "\n", + "1. **Loaded Configuration**: Set up GraphRouter with YAML configuration\n", + "2. **Understood Graph Structure**: Query-LLM bipartite graph\n", + "3. **Trained GNN Model**: Used message passing to learn representations\n", + "4. **Verified Model**: Tested routing with sample queries\n", + "\n", + "**Key Takeaways**:\n", + "- GraphRouter models query-LLM relationships as a graph\n", + "- GNN can capture complex interaction patterns\n", + "- Edge masking during training improves generalization\n", + "\n", + "**Next Steps**:\n", + "- Use the next part of notebook for inference\n", + "- Experiment with different GNN architectures" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# GraphRouter - Inference\n", + "\n", + "This part of notebook demonstrates how to use a trained **GraphRouter** for inference." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Environment Setup" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Load Trained Router" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "✅ MetaRouter initialized successfully (YAML + data loaded).\n", + "Router loaded with 14 LLM candidates\n" + ] + } + ], + "source": [ + "CONFIG_PATH = \"configs/model_config_test/graphrouter.yaml\"\n", + "\n", + "with open(CONFIG_PATH, 'r') as f:\n", + " config = yaml.safe_load(f)\n", + "\n", + "router = GraphRouter(yaml_path=CONFIG_PATH)\n", + "print(f\"Router loaded with {len(router.llm_data)} LLM candidates\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Query Routing" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Routing Results:\n", + "============================================================\n", + "1. What is the capital of France?...\n", + " Routed to: llama3-chatqa-1.5-8b\n", + "2. Solve the equation: 2x + 5 = 15...\n", + " Routed to: llama-3.1-nemotron-51b-instruct\n", + "3. Write a Python function to check if a number is pr...\n", + " Routed to: llama-3.1-nemotron-51b-instruct\n", + "4. Explain quantum computing in simple terms....\n", + " Routed to: llama-3.1-nemotron-51b-instruct\n" + ] + } + ], + "source": [ + "EXAMPLE_QUERIES = [\n", + " {\"query\": \"What is the capital of France?\"},\n", + " {\"query\": \"Solve the equation: 2x + 5 = 15\"},\n", + " {\"query\": \"Write a Python function to check if a number is prime.\"},\n", + " {\"query\": \"Explain quantum computing in simple terms.\"},\n", + "]\n", + "\n", + "print(\"Routing Results:\")\n", + "print(\"=\" * 60)\n", + "\n", + "for i, query in enumerate(EXAMPLE_QUERIES, 1):\n", + " result = router.route_single(query)\n", + " print(f\"{i}. {query['query'][:50]}...\")\n", + " print(f\" Routed to: {result['model_name']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. File-Based Inference\n", + "\n", + "Load queries from a file and save results." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loaded 706 queries from: data/example_data/query_data/default_query_test.jsonl\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "\n", + "\u001b[1;31mGive Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new\u001b[0m\n", + "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.\n", + "\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "\n", + "\u001b[1;31mGive Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new\u001b[0m\n", + "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.\n", + "\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "\n", + "\u001b[1;31mGive Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new\u001b[0m\n", + "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.\n", + "\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "\n", + "\u001b[1;31mGive Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new\u001b[0m\n", + "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.\n", + "\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "\n", + "\u001b[1;31mGive Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new\u001b[0m\n", + "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.\n", + "\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "\n", + "\u001b[1;31mGive Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new\u001b[0m\n", + "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.\n", + "\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "\n", + "\u001b[1;31mGive Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new\u001b[0m\n", + "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.\n", + "\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "\n", + "\u001b[1;31mGive Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new\u001b[0m\n", + "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.\n", + "\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "\n", + "\u001b[1;31mGive Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new\u001b[0m\n", + "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.\n", + "\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "\n", + "\u001b[1;31mGive Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new\u001b[0m\n", + "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.\n", + "\n", + "Routed 10 queries\n", + "Results saved to: outputs/graphrouter_results.jsonl\n", + "\n", + "Sample results:\n", + " 1. Q: There are 4 houses in a row, numbered... -> llama-3.1-nemotron-51b-instruct\n", + " 2. Q: There are 3 houses in a row, numbered... -> llama-3.1-nemotron-51b-instruct\n", + " 3. Q: There are 3 houses in a row, numbered... -> llama-3.1-nemotron-51b-instruct\n" + ] + } + ], + "source": [ + "import json\n", + "\n", + "# Load queries from a JSONL file\n", + "def load_queries_from_file(file_path):\n", + " \"\"\"Load queries from a JSONL file.\"\"\"\n", + " queries = []\n", + " with open(file_path, 'r', encoding='utf-8') as f:\n", + " for line in f:\n", + " if line.strip():\n", + " queries.append(json.loads(line))\n", + " return queries\n", + "\n", + "# Save results to a JSONL file\n", + "def save_results_to_file(results, output_path):\n", + " \"\"\"Save routing results to a JSONL file.\"\"\"\n", + " os.makedirs(os.path.dirname(output_path), exist_ok=True)\n", + " with open(output_path, 'w', encoding='utf-8') as f:\n", + " for result in results:\n", + " f.write(json.dumps(result, ensure_ascii=False) + '\\n')\n", + " print(f\"Results saved to: {output_path}\")\n", + "\n", + "# Example: Load from default query file\n", + "QUERY_FILE = \"data/example_data/query_data/default_query_test.jsonl\"\n", + "OUTPUT_FILE = \"outputs/graphrouter_results.jsonl\"\n", + "\n", + "if os.path.exists(QUERY_FILE):\n", + " # Load queries\n", + " file_queries = load_queries_from_file(QUERY_FILE)\n", + " print(f\"Loaded {len(file_queries)} queries from: {QUERY_FILE}\")\n", + " \n", + " # Route queries\n", + " file_results = router.route_batch(batch=file_queries[:10])\n", + " print(f\"Routed {len(file_results)} queries\")\n", + " \n", + " # Save results\n", + " save_results_to_file(file_results, OUTPUT_FILE)\n", + " \n", + " # Show sample results\n", + " print(f\"\\nSample results:\")\n", + " for i, result in enumerate(file_results[:3], 1):\n", + " print(f\" {i}. {result.get('query', '')[:40]}... -> {result['model_name']}\")\n", + "else:\n", + " print(f\"Query file not found: {QUERY_FILE}\")\n", + " print(\"Create a JSONL file with format: {\\\"query\\\": \\\"Your question\\\"}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "This notebook demonstrated:\n", + "1. Loading a trained GraphRouter\n", + "2. Routing queries using GNN-based inference\n", + "\n", + "GraphRouter is effective for:\n", + "- Capturing relational patterns between queries and LLMs\n", + "- Leveraging graph structure for better routing" + ] + } + ], + "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.13.11" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/colab_notebooks/hybrid_llm_router/01_hybrid_llm_router_training_and_inference.ipynb b/colab_notebooks/hybrid_llm_router/01_hybrid_llm_router_training_and_inference.ipynb new file mode 100644 index 0000000..14c4be8 --- /dev/null +++ b/colab_notebooks/hybrid_llm_router/01_hybrid_llm_router_training_and_inference.ipynb @@ -0,0 +1,726 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# HybridLLMRouter - Training\n", + "\n", + "This notebook demonstrates how to train the **HybridLLMRouter**.\n", + "\n", + "## Overview\n", + "\n", + "HybridLLMRouter routes between small and large models using an MLP regressor.\n", + "It supports different routing modes: deterministic, probabilistic, and transformed.\n", + "\n", + "**Key Features**:\n", + "- Binary routing (small vs large model)\n", + "- MLP-based decision making\n", + "- Configurable routing threshold" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Environment Setup" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cloning into 'LLMRouter'...\n", + "remote: Enumerating objects: 6017, done.\u001b[K\n", + "remote: Counting objects: 100% (172/172), done.\u001b[K\n", + "remote: Compressing objects: 100% (91/91), done.\u001b[K\n", + "remote: Total 6017 (delta 98), reused 87 (delta 81), pack-reused 5845 (from 2)\u001b[K\n", + "Receiving objects: 100% (6017/6017), 89.41 MiB | 53.05 MiB/s, done.\n", + "Resolving deltas: 100% (2946/2946), done.\n", + "Updating files: 100% (288/288), done.\n", + "/home/zhongjie/LLMRouter\n", + "Obtaining file:///home/zhongjie/LLMRouter\n", + " Installing build dependencies ... \u001b[?25ldone\n", + "\u001b[?25h Checking if build backend supports build_editable ... \u001b[?25ldone\n", + "\u001b[?25h Getting requirements to build editable ... \u001b[?25ldone\n", + "\u001b[?25h Preparing editable metadata (pyproject.toml) ... \u001b[?25ldone\n", + "\u001b[?25hRequirement already satisfied: torch>=2.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.9.1)\n", + "Requirement already satisfied: transformers>=4.40 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (4.57.6)\n", + "Requirement already satisfied: sentencepiece>=0.1.99 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (0.2.1)\n", + "Requirement already satisfied: numpy>=1.21 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.3.5)\n", + "Requirement already satisfied: pandas>=1.5 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.3.3)\n", + "Requirement already satisfied: scikit-learn>=1.2 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (1.8.0)\n", + "Requirement already satisfied: pyyaml>=6.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (6.0.3)\n", + "Requirement already satisfied: tqdm>=4.65 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (4.67.1)\n", + "Requirement already satisfied: datasets>=2.14 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (4.5.0)\n", + "Requirement already satisfied: pydantic>=2.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.12.5)\n", + "Requirement already satisfied: gradio>=4.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (6.3.0)\n", + "Requirement already satisfied: litellm>=1.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (1.81.0)\n", + "Requirement already satisfied: peft>=0.7 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (0.18.1)\n", + "Requirement already satisfied: torch-geometric>=2.3 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.7.0)\n", + "Requirement already satisfied: scipy>=1.10 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (1.16.3)\n", + "Requirement already satisfied: protobuf>=3.20 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (6.31.1)\n", + "Requirement already satisfied: filelock in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (3.20.2)\n", + "Requirement already satisfied: pyarrow>=21.0.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (22.0.0)\n", + "Requirement already satisfied: dill<0.4.1,>=0.3.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (0.4.0)\n", + "Requirement already satisfied: requests>=2.32.2 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (2.32.5)\n", + "Requirement already satisfied: httpx<1.0.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (0.28.1)\n", + "Requirement already satisfied: xxhash in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (3.6.0)\n", + "Requirement already satisfied: multiprocess<0.70.19 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (0.70.18)\n", + "Requirement already satisfied: fsspec<=2025.10.0,>=2023.1.0 in /opt/conda/lib/python3.13/site-packages (from fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (2025.10.0)\n", + "Requirement already satisfied: huggingface-hub<2.0,>=0.25.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (0.36.0)\n", + "Requirement already satisfied: packaging in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (25.0)\n", + "Requirement already satisfied: aiohttp!=4.0.0a0,!=4.0.0a1 in /opt/conda/lib/python3.13/site-packages (from fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (3.13.3)\n", + "Requirement already satisfied: anyio in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (4.12.0)\n", + "Requirement already satisfied: certifi in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (2026.1.4)\n", + "Requirement already satisfied: httpcore==1.* in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.0.9)\n", + "Requirement already satisfied: idna in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (3.11)\n", + "Requirement already satisfied: h11>=0.16 in /opt/conda/lib/python3.13/site-packages (from httpcore==1.*->httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (0.16.0)\n", + "Requirement already satisfied: typing-extensions>=3.7.4.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<2.0,>=0.25.0->datasets>=2.14->llmrouter-lib==0.2.0) (4.15.0)\n", + "Requirement already satisfied: hf-xet<2.0.0,>=1.1.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<2.0,>=0.25.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.2.0)\n", + "Requirement already satisfied: aiohappyeyeballs>=2.5.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (2.6.1)\n", + "Requirement already satisfied: aiosignal>=1.4.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.4.0)\n", + "Requirement already satisfied: attrs>=17.3.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (25.4.0)\n", + "Requirement already satisfied: frozenlist>=1.1.1 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.8.0)\n", + "Requirement already satisfied: multidict<7.0,>=4.5 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (6.7.0)\n", + "Requirement already satisfied: propcache>=0.2.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (0.4.1)\n", + "Requirement already satisfied: yarl<2.0,>=1.17.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.22.0)\n", + "Requirement already satisfied: aiofiles<25.0,>=22.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (24.1.0)\n", + "Requirement already satisfied: audioop-lts<1.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.2.2)\n", + "Requirement already satisfied: brotli>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (1.2.0)\n", + "Requirement already satisfied: fastapi<1.0,>=0.115.2 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.128.0)\n", + "Requirement already satisfied: ffmpy in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (1.0.0)\n", + "Requirement already satisfied: gradio-client==2.0.3 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (2.0.3)\n", + "Requirement already satisfied: groovy~=0.1 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.1.2)\n", + "Requirement already satisfied: jinja2<4.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (3.1.6)\n", + "Requirement already satisfied: markupsafe<4.0,>=2.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (3.0.3)\n", + "Requirement already satisfied: orjson~=3.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (3.11.5)\n", + "Requirement already satisfied: pillow<13.0,>=8.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (12.1.0)\n", + "Requirement already satisfied: pydub in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.25.1)\n", + "Requirement already satisfied: python-multipart>=0.0.18 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.0.21)\n", + "Requirement already satisfied: safehttpx<0.2.0,>=0.1.7 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.1.7)\n", + "Requirement already satisfied: semantic-version~=2.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (2.10.0)\n", + "Requirement already satisfied: starlette<1.0,>=0.40.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.50.0)\n", + "Requirement already satisfied: tomlkit<0.14.0,>=0.12.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.13.3)\n", + "Requirement already satisfied: typer<1.0,>=0.12 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.21.1)\n", + "Requirement already satisfied: uvicorn>=0.14.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.40.0)\n", + "Requirement already satisfied: annotated-doc>=0.0.2 in /opt/conda/lib/python3.13/site-packages (from fastapi<1.0,>=0.115.2->gradio>=4.0->llmrouter-lib==0.2.0) (0.0.4)\n", + "Requirement already satisfied: python-dateutil>=2.8.2 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.2.0) (2.9.0.post0)\n", + "Requirement already satisfied: pytz>=2020.1 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.2.0) (2025.2)\n", + "Requirement already satisfied: tzdata>=2022.7 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.2.0) (2025.3)\n", + "Requirement already satisfied: annotated-types>=0.6.0 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.2.0) (0.7.0)\n", + "Requirement already satisfied: pydantic-core==2.41.5 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.2.0) (2.41.5)\n", + "Requirement already satisfied: typing-inspection>=0.4.2 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.2.0) (0.4.2)\n", + "Requirement already satisfied: click>=8.0.0 in /opt/conda/lib/python3.13/site-packages (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (8.3.1)\n", + "Requirement already satisfied: shellingham>=1.3.0 in /opt/conda/lib/python3.13/site-packages (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (1.5.4)\n", + "Requirement already satisfied: rich>=10.11.0 in /opt/conda/lib/python3.13/site-packages (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (14.2.0)\n", + "Requirement already satisfied: fastuuid>=0.13.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (0.14.0)\n", + "Requirement already satisfied: grpcio!=1.68.*,!=1.69.*,!=1.70.*,!=1.71.0,!=1.71.1,!=1.72.0,!=1.72.1,!=1.73.0,>=1.62.3 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (1.76.0)\n", + "Requirement already satisfied: importlib-metadata>=6.8.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (8.7.0)\n", + "Requirement already satisfied: jsonschema<5.0.0,>=4.23.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (4.25.1)\n", + "Requirement already satisfied: openai>=2.8.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (2.15.0)\n", + "Requirement already satisfied: python-dotenv>=0.2.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (1.2.1)\n", + "Requirement already satisfied: tiktoken>=0.7.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (0.12.0)\n", + "Requirement already satisfied: tokenizers in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (0.22.2)\n", + "Requirement already satisfied: jsonschema-specifications>=2023.03.6 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.2.0) (2025.9.1)\n", + "Requirement already satisfied: referencing>=0.28.4 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.2.0) (0.37.0)\n", + "Requirement already satisfied: rpds-py>=0.7.1 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.2.0) (0.30.0)\n", + "Requirement already satisfied: zipp>=3.20 in /opt/conda/lib/python3.13/site-packages (from importlib-metadata>=6.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (3.23.0)\n", + "Requirement already satisfied: distro<2,>=1.7.0 in /opt/conda/lib/python3.13/site-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (1.9.0)\n", + "Requirement already satisfied: jiter<1,>=0.10.0 in /opt/conda/lib/python3.13/site-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (0.12.0)\n", + "Requirement already satisfied: sniffio in /opt/conda/lib/python3.13/site-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (1.3.1)\n", + "Requirement already satisfied: psutil in /opt/conda/lib/python3.13/site-packages (from peft>=0.7->llmrouter-lib==0.2.0) (7.2.1)\n", + "Requirement already satisfied: accelerate>=0.21.0 in /opt/conda/lib/python3.13/site-packages (from peft>=0.7->llmrouter-lib==0.2.0) (1.12.0)\n", + "Requirement already satisfied: safetensors in /opt/conda/lib/python3.13/site-packages (from peft>=0.7->llmrouter-lib==0.2.0) (0.7.0)\n", + "Requirement already satisfied: six>=1.5 in /opt/conda/lib/python3.13/site-packages (from python-dateutil>=2.8.2->pandas>=1.5->llmrouter-lib==0.2.0) (1.17.0)\n", + "Requirement already satisfied: charset_normalizer<4,>=2 in /opt/conda/lib/python3.13/site-packages (from requests>=2.32.2->datasets>=2.14->llmrouter-lib==0.2.0) (3.4.4)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/conda/lib/python3.13/site-packages (from requests>=2.32.2->datasets>=2.14->llmrouter-lib==0.2.0) (2.6.2)\n", + "Requirement already satisfied: markdown-it-py>=2.2.0 in /opt/conda/lib/python3.13/site-packages (from rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (4.0.0)\n", + "Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /opt/conda/lib/python3.13/site-packages (from rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (2.19.2)\n", + "Requirement already satisfied: mdurl~=0.1 in /opt/conda/lib/python3.13/site-packages (from markdown-it-py>=2.2.0->rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (0.1.2)\n", + "Requirement already satisfied: joblib>=1.3.0 in /opt/conda/lib/python3.13/site-packages (from scikit-learn>=1.2->llmrouter-lib==0.2.0) (1.5.3)\n", + "Requirement already satisfied: threadpoolctl>=3.2.0 in /opt/conda/lib/python3.13/site-packages (from scikit-learn>=1.2->llmrouter-lib==0.2.0) (3.6.0)\n", + "Requirement already satisfied: regex>=2022.1.18 in /opt/conda/lib/python3.13/site-packages (from tiktoken>=0.7.0->litellm>=1.0->llmrouter-lib==0.2.0) (2026.1.15)\n", + "Requirement already satisfied: setuptools in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (80.9.0)\n", + "Requirement already satisfied: sympy>=1.13.3 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (1.14.0)\n", + "Requirement already satisfied: networkx>=2.5.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (3.6.1)\n", + "Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.93)\n", + "Requirement already satisfied: nvidia-cuda-runtime-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.90)\n", + "Requirement already satisfied: nvidia-cuda-cupti-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.90)\n", + "Requirement already satisfied: nvidia-cudnn-cu12==9.10.2.21 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (9.10.2.21)\n", + "Requirement already satisfied: nvidia-cublas-cu12==12.8.4.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.4.1)\n", + "Requirement already satisfied: nvidia-cufft-cu12==11.3.3.83 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (11.3.3.83)\n", + "Requirement already satisfied: nvidia-curand-cu12==10.3.9.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (10.3.9.90)\n", + "Requirement already satisfied: nvidia-cusolver-cu12==11.7.3.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (11.7.3.90)\n", + "Requirement already satisfied: nvidia-cusparse-cu12==12.5.8.93 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.5.8.93)\n", + "Requirement already satisfied: nvidia-cusparselt-cu12==0.7.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (0.7.1)\n", + "Requirement already satisfied: nvidia-nccl-cu12==2.27.5 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (2.27.5)\n", + "Requirement already satisfied: nvidia-nvshmem-cu12==3.3.20 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (3.3.20)\n", + "Requirement already satisfied: nvidia-nvtx-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.90)\n", + "Requirement already satisfied: nvidia-nvjitlink-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.93)\n", + "Requirement already satisfied: nvidia-cufile-cu12==1.13.1.3 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (1.13.1.3)\n", + "Requirement already satisfied: triton==3.5.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (3.5.1)\n", + "Requirement already satisfied: mpmath<1.4,>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from sympy>=1.13.3->torch>=2.0->llmrouter-lib==0.2.0) (1.3.0)\n", + "Requirement already satisfied: pyparsing in /opt/conda/lib/python3.13/site-packages (from torch-geometric>=2.3->llmrouter-lib==0.2.0) (3.3.1)\n", + "Building wheels for collected packages: llmrouter-lib\n", + " Building editable for llmrouter-lib (pyproject.toml) ... \u001b[?25ldone\n", + "\u001b[?25h Created wheel for llmrouter-lib: filename=llmrouter_lib-0.2.0-0.editable-py3-none-any.whl size=15709 sha256=ba03c8a03a8d719114455fce29065dff7e24993dcf454c700113246d50a7b242\n", + " Stored in directory: /tmp/pip-ephem-wheel-cache-dox4tdl9/wheels/82/4a/fd/59c4aec93c356c380d86a88ab74bece164c0aa855d68b155e4\n", + "Successfully built llmrouter-lib\n", + "Installing collected packages: llmrouter-lib\n", + " Attempting uninstall: llmrouter-lib\n", + " Found existing installation: llmrouter-lib 0.2.0\n", + " Uninstalling llmrouter-lib-0.2.0:\n", + " Successfully uninstalled llmrouter-lib-0.2.0\n", + "Successfully installed llmrouter-lib-0.2.0\n", + "Requirement already satisfied: transformers in /opt/conda/lib/python3.13/site-packages (4.57.6)\n", + "Requirement already satisfied: torch in /opt/conda/lib/python3.13/site-packages (2.9.1)\n", + "Requirement already satisfied: filelock in /opt/conda/lib/python3.13/site-packages (from transformers) (3.20.2)\n", + "Requirement already satisfied: huggingface-hub<1.0,>=0.34.0 in /opt/conda/lib/python3.13/site-packages (from transformers) (0.36.0)\n", + "Requirement already satisfied: numpy>=1.17 in /opt/conda/lib/python3.13/site-packages (from transformers) (2.3.5)\n", + "Requirement already satisfied: packaging>=20.0 in /opt/conda/lib/python3.13/site-packages (from transformers) (25.0)\n", + "Requirement already satisfied: pyyaml>=5.1 in /opt/conda/lib/python3.13/site-packages (from transformers) (6.0.3)\n", + "Requirement already satisfied: regex!=2019.12.17 in /opt/conda/lib/python3.13/site-packages (from transformers) (2026.1.15)\n", + "Requirement already satisfied: requests in /opt/conda/lib/python3.13/site-packages (from transformers) (2.32.5)\n", + "Requirement already satisfied: tokenizers<=0.23.0,>=0.22.0 in /opt/conda/lib/python3.13/site-packages (from transformers) (0.22.2)\n", + "Requirement already satisfied: safetensors>=0.4.3 in /opt/conda/lib/python3.13/site-packages (from transformers) (0.7.0)\n", + "Requirement already satisfied: tqdm>=4.27 in /opt/conda/lib/python3.13/site-packages (from transformers) (4.67.1)\n", + "Requirement already satisfied: fsspec>=2023.5.0 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (2025.10.0)\n", + "Requirement already satisfied: typing-extensions>=3.7.4.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (4.15.0)\n", + "Requirement already satisfied: hf-xet<2.0.0,>=1.1.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (1.2.0)\n", + "Requirement already satisfied: setuptools in /opt/conda/lib/python3.13/site-packages (from torch) (80.9.0)\n", + "Requirement already satisfied: sympy>=1.13.3 in /opt/conda/lib/python3.13/site-packages (from torch) (1.14.0)\n", + "Requirement already satisfied: networkx>=2.5.1 in /opt/conda/lib/python3.13/site-packages (from torch) (3.6.1)\n", + "Requirement already satisfied: jinja2 in /opt/conda/lib/python3.13/site-packages (from torch) (3.1.6)\n", + "Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.93)\n", + "Requirement already satisfied: nvidia-cuda-runtime-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-cuda-cupti-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-cudnn-cu12==9.10.2.21 in /opt/conda/lib/python3.13/site-packages (from torch) (9.10.2.21)\n", + "Requirement already satisfied: nvidia-cublas-cu12==12.8.4.1 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.4.1)\n", + "Requirement already satisfied: nvidia-cufft-cu12==11.3.3.83 in /opt/conda/lib/python3.13/site-packages (from torch) (11.3.3.83)\n", + "Requirement already satisfied: nvidia-curand-cu12==10.3.9.90 in /opt/conda/lib/python3.13/site-packages (from torch) (10.3.9.90)\n", + "Requirement already satisfied: nvidia-cusolver-cu12==11.7.3.90 in /opt/conda/lib/python3.13/site-packages (from torch) (11.7.3.90)\n", + "Requirement already satisfied: nvidia-cusparse-cu12==12.5.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.5.8.93)\n", + "Requirement already satisfied: nvidia-cusparselt-cu12==0.7.1 in /opt/conda/lib/python3.13/site-packages (from torch) (0.7.1)\n", + "Requirement already satisfied: nvidia-nccl-cu12==2.27.5 in /opt/conda/lib/python3.13/site-packages (from torch) (2.27.5)\n", + "Requirement already satisfied: nvidia-nvshmem-cu12==3.3.20 in /opt/conda/lib/python3.13/site-packages (from torch) (3.3.20)\n", + "Requirement already satisfied: nvidia-nvtx-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-nvjitlink-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.93)\n", + "Requirement already satisfied: nvidia-cufile-cu12==1.13.1.3 in /opt/conda/lib/python3.13/site-packages (from torch) (1.13.1.3)\n", + "Requirement already satisfied: triton==3.5.1 in /opt/conda/lib/python3.13/site-packages (from torch) (3.5.1)\n", + "Requirement already satisfied: mpmath<1.4,>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from sympy>=1.13.3->torch) (1.3.0)\n", + "Requirement already satisfied: MarkupSafe>=2.0 in /opt/conda/lib/python3.13/site-packages (from jinja2->torch) (3.0.3)\n", + "Requirement already satisfied: charset_normalizer<4,>=2 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (3.4.4)\n", + "Requirement already satisfied: idna<4,>=2.5 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (3.11)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (2.6.2)\n", + "Requirement already satisfied: certifi>=2017.4.17 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (2026.1.4)\n" + ] + } + ], + "source": [ + "# Install required packages (for Colab)\n", + "!git clone https://github.com/ulab-uiuc/LLMRouter.git\n", + "%cd LLMRouter\n", + "!pip install -e .\n", + "!pip install transformers torch\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "os.environ['OPENAI_API_KEY'] = 'your-key'\n", + "os.environ['ANTHROPIC_API_KEY'] = 'your-key'\n", + "# Or for multiple keys:\n", + "os.environ['API_KEYS'] = '[\"key1\", \"key2\"]'" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Environment setup complete!\n" + ] + } + ], + "source": [ + "from llmrouter.models.hybrid_llm import HybridLLMRouter, HybridLLMTrainer\n", + "from llmrouter.utils import setup_environment\n", + "\n", + "setup_environment()\n", + "print(\"Environment setup complete!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Configuration\n", + "\n", + "HybridLLMRouter uses the following configuration parameters:\n", + "\n", + "| Parameter | Description | Default |\n", + "|-----------|-------------|--------|\n", + "| `router_mode` | Routing mode | \"probabilistic\" |\n", + "| `router_tau` | Temperature for probabilistic routing | 0.1 |\n", + "| `router_threshold` | Decision threshold | 0.5 |\n", + "| `hidden_layer_sizes` | MLP architecture | [128, 64] |" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Current Configuration:\n", + "==================================================\n", + "data_path:\n", + " llm_data: data/example_data/llm_candidates/default_llm.json\n", + " llm_embedding_data: data/example_data/llm_candidates/default_llm_embeddings.json\n", + " query_data_test: data/example_data/query_data/default_query_test.jsonl\n", + " query_data_train: data/example_data/query_data/default_query_train.jsonl\n", + " query_embedding_data: data/example_data/routing_data/query_embeddings_longformer.pt\n", + " routing_data_test: data/example_data/routing_data/default_routing_test_data.jsonl\n", + " routing_data_train: data/example_data/routing_data/default_routing_train_data.jsonl\n", + "hparam:\n", + " activation: relu\n", + " hidden_layer_sizes:\n", + " - 128\n", + " - 64\n", + " max_iter: 300\n", + " solver: adam\n", + "model_path:\n", + " ini_model_path: configs/hybrid_ini.pkl\n", + " load_model_path: configs/hybrid_trained.pkl\n", + " save_model_path: configs/hybrid_trained.pkl\n", + "router_mode: probabilistic\n", + "router_tau: 0.1\n", + "router_threshold: 0.5\n", + "\n" + ] + } + ], + "source": [ + "import yaml\n", + "\n", + "CONFIG_PATH = \"configs/model_config_train/hybrid_llm.yaml\"\n", + "\n", + "with open(CONFIG_PATH, 'r') as f:\n", + " config = yaml.safe_load(f)\n", + "\n", + "print(\"Current Configuration:\")\n", + "print(\"=\" * 50)\n", + "print(yaml.dump(config, default_flow_style=False))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Initialize Router" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "✅ MetaRouter initialized successfully (YAML + data loaded).\n", + "[HybridLLMRouter] Filtered to 4 models with routing data.\n", + "[HybridLLMRouter] Mode=probabilistic, Small='qwen2.5-7b-instruct', Large='llama-3.3-nemotron-super-49b-v1'\n", + "Router initialized successfully!\n", + "Router mode: probabilistic\n", + "Threshold: 0.5\n" + ] + } + ], + "source": [ + "router = HybridLLMRouter(yaml_path=CONFIG_PATH)\n", + "\n", + "print(\"Router initialized successfully!\")\n", + "print(f\"Router mode: {config.get('router_mode', 'probabilistic')}\")\n", + "print(f\"Threshold: {config.get('router_threshold', 0.5)}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Training" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[HybridLLMTrainer] Initialized. Mode=probabilistic\n", + "Trainer initialized!\n" + ] + } + ], + "source": [ + "trainer = HybridLLMTrainer(router=router, device='cpu')\n", + "\n", + "print(\"Trainer initialized!\")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting training...\n", + "==================================================\n", + "[HybridLLMTrainer] Training on 5616 samples... mode=probabilistic\n", + "[HybridLLMTrainer] Saving final model: /home/zhongjie/LLMRouter/llmrouter/configs/hybrid_trained.pkl\n", + "Created directory: /home/zhongjie/LLMRouter/llmrouter/configs\n", + "Successfully saved pickle model: /home/zhongjie/LLMRouter/llmrouter/configs/hybrid_trained.pkl\n", + "==================================================\n", + "Training completed!\n" + ] + } + ], + "source": [ + "print(\"Starting training...\")\n", + "print(\"=\" * 50)\n", + "\n", + "trainer.train()\n", + "\n", + "print(\"=\" * 50)\n", + "print(\"Training completed!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Model Verification" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/configs/hybrid_trained.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Input ids are automatically padded to be a multiple of `config.attention_window`: 512\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Test query: What is the capital of France?\n", + "Routed to: qwen2.5-7b-instruct\n" + ] + } + ], + "source": [ + "# Test prediction\n", + "test_query = {\"query\": \"What is the capital of France?\"}\n", + "result = router.route_single(test_query)\n", + "\n", + "print(f\"Test query: {test_query['query']}\")\n", + "print(f\"Routed to: {result['model_name']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "In this notebook, we:\n", + "\n", + "1. **Loaded Configuration**: Set up HybridLLMRouter with YAML configuration\n", + "2. **Trained Model**: Learned routing decision boundary\n", + "3. **Verified Model**: Tested routing with sample queries\n", + "\n", + "**Routing Modes**:\n", + "- **deterministic**: Hard threshold decision\n", + "- **probabilistic**: Soft probability-based routing\n", + "- **transformed**: Temperature-scaled probabilities\n", + "\n", + "**Next Steps**:\n", + "- Use the next part of notebook for inference" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# HybridLLMRouter - Inference\n", + "\n", + "This notebook demonstrates how to use a trained **HybridLLMRouter** for inference." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Environment Setup (optional)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from llmrouter.models.hybrid_llm import HybridLLMRouter\n", + "from llmrouter.utils import setup_environment\n", + "import yaml\n", + "\n", + "setup_environment()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Load Trained Router" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "✅ MetaRouter initialized successfully (YAML + data loaded).\n", + "[HybridLLMRouter] Filtered to 4 models with routing data.\n", + "[HybridLLMRouter] Mode=probabilistic, Small='qwen2.5-7b-instruct', Large='llama-3.3-nemotron-super-49b-v1'\n", + "Router loaded!\n" + ] + } + ], + "source": [ + "CONFIG_PATH = \"configs/model_config_train/hybrid_llm.yaml\"\n", + "\n", + "router = HybridLLMRouter(yaml_path=CONFIG_PATH)\n", + "print(\"Router loaded!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Query Routing" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Routing Results:\n", + "============================================================\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/configs/hybrid_trained.pkl\n", + "1. What is 2 + 2?...\n", + " Routed to: qwen2.5-7b-instruct\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/configs/hybrid_trained.pkl\n", + "2. Explain quantum entanglement....\n", + " Routed to: qwen2.5-7b-instruct\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/configs/hybrid_trained.pkl\n", + "3. Write a sorting algorithm....\n", + " Routed to: llama-3.3-nemotron-super-49b-v1\n" + ] + } + ], + "source": [ + "EXAMPLE_QUERIES = [\n", + " {\"query\": \"What is 2 + 2?\"},\n", + " {\"query\": \"Explain quantum entanglement.\"},\n", + " {\"query\": \"Write a sorting algorithm.\"},\n", + "]\n", + "\n", + "print(\"Routing Results:\")\n", + "print(\"=\" * 60)\n", + "\n", + "for i, query in enumerate(EXAMPLE_QUERIES, 1):\n", + " result = router.route_single(query)\n", + " print(f\"{i}. {query['query'][:50]}...\")\n", + " print(f\" Routed to: {result['model_name']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. File-Based Inference\n", + "\n", + "Load queries from a file and save results." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loaded 706 queries from: data/example_data/query_data/default_query_test.jsonl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/configs/hybrid_trained.pkl\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "\n", + "\u001b[1;31mGive Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new\u001b[0m\n", + "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.\n", + "\n", + "Routed 10 queries\n", + "Results saved to: outputs/hybrid_llm_router_results.jsonl\n", + "\n", + "Sample results:\n", + " 1. Q: There are 4 houses in a row, numbered... -> qwen2.5-7b-instruct\n", + " 2. Q: There are 3 houses in a row, numbered... -> qwen2.5-7b-instruct\n", + " 3. Q: There are 3 houses in a row, numbered... -> llama-3.3-nemotron-super-49b-v1\n" + ] + } + ], + "source": [ + "import json\n", + "\n", + "# Load queries from a JSONL file\n", + "def load_queries_from_file(file_path):\n", + " \"\"\"Load queries from a JSONL file.\"\"\"\n", + " queries = []\n", + " with open(file_path, 'r', encoding='utf-8') as f:\n", + " for line in f:\n", + " if line.strip():\n", + " queries.append(json.loads(line))\n", + " return queries\n", + "\n", + "# Save results to a JSONL file\n", + "def save_results_to_file(results, output_path):\n", + " \"\"\"Save routing results to a JSONL file.\"\"\"\n", + " os.makedirs(os.path.dirname(output_path), exist_ok=True)\n", + " with open(output_path, 'w', encoding='utf-8') as f:\n", + " for result in results:\n", + " f.write(json.dumps(result, ensure_ascii=False) + '\\n')\n", + " print(f\"Results saved to: {output_path}\")\n", + "\n", + "# Example: Load from default query file\n", + "QUERY_FILE = \"data/example_data/query_data/default_query_test.jsonl\"\n", + "OUTPUT_FILE = \"outputs/hybrid_llm_router_results.jsonl\"\n", + "\n", + "if os.path.exists(QUERY_FILE):\n", + " # Load queries\n", + " file_queries = load_queries_from_file(QUERY_FILE)\n", + " print(f\"Loaded {len(file_queries)} queries from: {QUERY_FILE}\")\n", + " \n", + " # Route queries\n", + " file_results = router.route_batch(batch=file_queries[:10])\n", + " print(f\"Routed {len(file_results)} queries\")\n", + " \n", + " # Save results\n", + " save_results_to_file(file_results, OUTPUT_FILE)\n", + " \n", + " # Show sample results\n", + " print(f\"\\nSample results:\")\n", + " for i, result in enumerate(file_results[:3], 1):\n", + " print(f\" {i}. {result.get('query', '')[:40]}... -> {result['model_name']}\")\n", + "else:\n", + " print(f\"Query file not found: {QUERY_FILE}\")\n", + " print(\"Create a JSONL file with format: {\\\"query\\\": \\\"Your question\\\"}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "HybridLLMRouter provides:\n", + "- Efficient binary routing between small and large models\n", + "- Configurable routing modes for different use cases" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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.13.11" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/colab_notebooks/knnmultiroundrouter/01_knnmultiroundrouter_training_and_inference.ipynb b/colab_notebooks/knnmultiroundrouter/01_knnmultiroundrouter_training_and_inference.ipynb new file mode 100644 index 0000000..96ade97 --- /dev/null +++ b/colab_notebooks/knnmultiroundrouter/01_knnmultiroundrouter_training_and_inference.ipynb @@ -0,0 +1,1237 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# KNNMultiRoundRouter - Training\n", + "\n", + "This notebook demonstrates how to train the **KNNMultiRoundRouter**.\n", + "\n", + "## Overview\n", + "\n", + "KNNMultiRoundRouter extends the KNNRouter with a multi-round pipeline:\n", + "1. **Decompose**: Break down complex queries into sub-queries\n", + "2. **Route**: Use KNN to route each sub-query to the best model\n", + "3. **Execute**: Call APIs to get responses from routed models\n", + "4. **Aggregate**: Combine sub-query responses into final answer\n", + "\n", + "**Key Features**:\n", + "- KNN-based routing (same as single-round KNNRouter)\n", + "- Multi-round decomposition and aggregation\n", + "- Supports both local LLM (vLLM) and API-based decomposition\n", + "- Configurable K value and distance metrics" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Environment Setup" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cloning into 'LLMRouter'...\n", + "remote: Enumerating objects: 6017, done.\u001b[K\n", + "remote: Counting objects: 100% (172/172), done.\u001b[K\n", + "remote: Compressing objects: 100% (91/91), done.\u001b[K\n", + "remote: Total 6017 (delta 98), reused 87 (delta 81), pack-reused 5845 (from 2)\u001b[K\n", + "Receiving objects: 100% (6017/6017), 89.41 MiB | 50.95 MiB/s, done.\n", + "Resolving deltas: 100% (2946/2946), done.\n", + "Updating files: 100% (288/288), done.\n", + "/home/zhongjie/LLMRouter\n", + "Obtaining file:///home/zhongjie/LLMRouter\n", + " Installing build dependencies ... \u001b[?25ldone\n", + "\u001b[?25h Checking if build backend supports build_editable ... \u001b[?25ldone\n", + "\u001b[?25h Getting requirements to build editable ... \u001b[?25ldone\n", + "\u001b[?25h Preparing editable metadata (pyproject.toml) ... \u001b[?25ldone\n", + "\u001b[?25hRequirement already satisfied: torch>=2.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.9.1)\n", + "Requirement already satisfied: transformers>=4.40 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (4.57.6)\n", + "Requirement already satisfied: sentencepiece>=0.1.99 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (0.2.1)\n", + "Requirement already satisfied: numpy>=1.21 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.3.5)\n", + "Requirement already satisfied: pandas>=1.5 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.3.3)\n", + "Requirement already satisfied: scikit-learn>=1.2 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (1.8.0)\n", + "Requirement already satisfied: pyyaml>=6.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (6.0.3)\n", + "Requirement already satisfied: tqdm>=4.65 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (4.67.1)\n", + "Requirement already satisfied: datasets>=2.14 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (4.5.0)\n", + "Requirement already satisfied: pydantic>=2.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.12.5)\n", + "Requirement already satisfied: gradio>=4.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (6.3.0)\n", + "Requirement already satisfied: litellm>=1.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (1.81.0)\n", + "Requirement already satisfied: peft>=0.7 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (0.18.1)\n", + "Requirement already satisfied: torch-geometric>=2.3 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.7.0)\n", + "Requirement already satisfied: scipy>=1.10 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (1.16.3)\n", + "Requirement already satisfied: protobuf>=3.20 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (6.31.1)\n", + "Requirement already satisfied: filelock in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (3.20.2)\n", + "Requirement already satisfied: pyarrow>=21.0.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (22.0.0)\n", + "Requirement already satisfied: dill<0.4.1,>=0.3.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (0.4.0)\n", + "Requirement already satisfied: requests>=2.32.2 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (2.32.5)\n", + "Requirement already satisfied: httpx<1.0.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (0.28.1)\n", + "Requirement already satisfied: xxhash in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (3.6.0)\n", + "Requirement already satisfied: multiprocess<0.70.19 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (0.70.18)\n", + "Requirement already satisfied: fsspec<=2025.10.0,>=2023.1.0 in /opt/conda/lib/python3.13/site-packages (from fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (2025.10.0)\n", + "Requirement already satisfied: huggingface-hub<2.0,>=0.25.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (0.36.0)\n", + "Requirement already satisfied: packaging in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (25.0)\n", + "Requirement already satisfied: aiohttp!=4.0.0a0,!=4.0.0a1 in /opt/conda/lib/python3.13/site-packages (from fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (3.13.3)\n", + "Requirement already satisfied: anyio in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (4.12.0)\n", + "Requirement already satisfied: certifi in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (2026.1.4)\n", + "Requirement already satisfied: httpcore==1.* in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.0.9)\n", + "Requirement already satisfied: idna in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (3.11)\n", + "Requirement already satisfied: h11>=0.16 in /opt/conda/lib/python3.13/site-packages (from httpcore==1.*->httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (0.16.0)\n", + "Requirement already satisfied: typing-extensions>=3.7.4.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<2.0,>=0.25.0->datasets>=2.14->llmrouter-lib==0.2.0) (4.15.0)\n", + "Requirement already satisfied: hf-xet<2.0.0,>=1.1.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<2.0,>=0.25.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.2.0)\n", + "Requirement already satisfied: aiohappyeyeballs>=2.5.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (2.6.1)\n", + "Requirement already satisfied: aiosignal>=1.4.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.4.0)\n", + "Requirement already satisfied: attrs>=17.3.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (25.4.0)\n", + "Requirement already satisfied: frozenlist>=1.1.1 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.8.0)\n", + "Requirement already satisfied: multidict<7.0,>=4.5 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (6.7.0)\n", + "Requirement already satisfied: propcache>=0.2.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (0.4.1)\n", + "Requirement already satisfied: yarl<2.0,>=1.17.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.22.0)\n", + "Requirement already satisfied: aiofiles<25.0,>=22.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (24.1.0)\n", + "Requirement already satisfied: audioop-lts<1.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.2.2)\n", + "Requirement already satisfied: brotli>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (1.2.0)\n", + "Requirement already satisfied: fastapi<1.0,>=0.115.2 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.128.0)\n", + "Requirement already satisfied: ffmpy in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (1.0.0)\n", + "Requirement already satisfied: gradio-client==2.0.3 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (2.0.3)\n", + "Requirement already satisfied: groovy~=0.1 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.1.2)\n", + "Requirement already satisfied: jinja2<4.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (3.1.6)\n", + "Requirement already satisfied: markupsafe<4.0,>=2.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (3.0.3)\n", + "Requirement already satisfied: orjson~=3.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (3.11.5)\n", + "Requirement already satisfied: pillow<13.0,>=8.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (12.1.0)\n", + "Requirement already satisfied: pydub in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.25.1)\n", + "Requirement already satisfied: python-multipart>=0.0.18 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.0.21)\n", + "Requirement already satisfied: safehttpx<0.2.0,>=0.1.7 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.1.7)\n", + "Requirement already satisfied: semantic-version~=2.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (2.10.0)\n", + "Requirement already satisfied: starlette<1.0,>=0.40.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.50.0)\n", + "Requirement already satisfied: tomlkit<0.14.0,>=0.12.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.13.3)\n", + "Requirement already satisfied: typer<1.0,>=0.12 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.21.1)\n", + "Requirement already satisfied: uvicorn>=0.14.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.40.0)\n", + "Requirement already satisfied: annotated-doc>=0.0.2 in /opt/conda/lib/python3.13/site-packages (from fastapi<1.0,>=0.115.2->gradio>=4.0->llmrouter-lib==0.2.0) (0.0.4)\n", + "Requirement already satisfied: python-dateutil>=2.8.2 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.2.0) (2.9.0.post0)\n", + "Requirement already satisfied: pytz>=2020.1 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.2.0) (2025.2)\n", + "Requirement already satisfied: tzdata>=2022.7 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.2.0) (2025.3)\n", + "Requirement already satisfied: annotated-types>=0.6.0 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.2.0) (0.7.0)\n", + "Requirement already satisfied: pydantic-core==2.41.5 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.2.0) (2.41.5)\n", + "Requirement already satisfied: typing-inspection>=0.4.2 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.2.0) (0.4.2)\n", + "Requirement already satisfied: click>=8.0.0 in /opt/conda/lib/python3.13/site-packages (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (8.3.1)\n", + "Requirement already satisfied: shellingham>=1.3.0 in /opt/conda/lib/python3.13/site-packages (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (1.5.4)\n", + "Requirement already satisfied: rich>=10.11.0 in /opt/conda/lib/python3.13/site-packages (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (14.2.0)\n", + "Requirement already satisfied: fastuuid>=0.13.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (0.14.0)\n", + "Requirement already satisfied: grpcio!=1.68.*,!=1.69.*,!=1.70.*,!=1.71.0,!=1.71.1,!=1.72.0,!=1.72.1,!=1.73.0,>=1.62.3 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (1.76.0)\n", + "Requirement already satisfied: importlib-metadata>=6.8.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (8.7.0)\n", + "Requirement already satisfied: jsonschema<5.0.0,>=4.23.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (4.25.1)\n", + "Requirement already satisfied: openai>=2.8.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (2.15.0)\n", + "Requirement already satisfied: python-dotenv>=0.2.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (1.2.1)\n", + "Requirement already satisfied: tiktoken>=0.7.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (0.12.0)\n", + "Requirement already satisfied: tokenizers in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (0.22.2)\n", + "Requirement already satisfied: jsonschema-specifications>=2023.03.6 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.2.0) (2025.9.1)\n", + "Requirement already satisfied: referencing>=0.28.4 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.2.0) (0.37.0)\n", + "Requirement already satisfied: rpds-py>=0.7.1 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.2.0) (0.30.0)\n", + "Requirement already satisfied: zipp>=3.20 in /opt/conda/lib/python3.13/site-packages (from importlib-metadata>=6.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (3.23.0)\n", + "Requirement already satisfied: distro<2,>=1.7.0 in /opt/conda/lib/python3.13/site-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (1.9.0)\n", + "Requirement already satisfied: jiter<1,>=0.10.0 in /opt/conda/lib/python3.13/site-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (0.12.0)\n", + "Requirement already satisfied: sniffio in /opt/conda/lib/python3.13/site-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (1.3.1)\n", + "Requirement already satisfied: psutil in /opt/conda/lib/python3.13/site-packages (from peft>=0.7->llmrouter-lib==0.2.0) (7.2.1)\n", + "Requirement already satisfied: accelerate>=0.21.0 in /opt/conda/lib/python3.13/site-packages (from peft>=0.7->llmrouter-lib==0.2.0) (1.12.0)\n", + "Requirement already satisfied: safetensors in /opt/conda/lib/python3.13/site-packages (from peft>=0.7->llmrouter-lib==0.2.0) (0.7.0)\n", + "Requirement already satisfied: six>=1.5 in /opt/conda/lib/python3.13/site-packages (from python-dateutil>=2.8.2->pandas>=1.5->llmrouter-lib==0.2.0) (1.17.0)\n", + "Requirement already satisfied: charset_normalizer<4,>=2 in /opt/conda/lib/python3.13/site-packages (from requests>=2.32.2->datasets>=2.14->llmrouter-lib==0.2.0) (3.4.4)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/conda/lib/python3.13/site-packages (from requests>=2.32.2->datasets>=2.14->llmrouter-lib==0.2.0) (2.6.2)\n", + "Requirement already satisfied: markdown-it-py>=2.2.0 in /opt/conda/lib/python3.13/site-packages (from rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (4.0.0)\n", + "Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /opt/conda/lib/python3.13/site-packages (from rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (2.19.2)\n", + "Requirement already satisfied: mdurl~=0.1 in /opt/conda/lib/python3.13/site-packages (from markdown-it-py>=2.2.0->rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (0.1.2)\n", + "Requirement already satisfied: joblib>=1.3.0 in /opt/conda/lib/python3.13/site-packages (from scikit-learn>=1.2->llmrouter-lib==0.2.0) (1.5.3)\n", + "Requirement already satisfied: threadpoolctl>=3.2.0 in /opt/conda/lib/python3.13/site-packages (from scikit-learn>=1.2->llmrouter-lib==0.2.0) (3.6.0)\n", + "Requirement already satisfied: regex>=2022.1.18 in /opt/conda/lib/python3.13/site-packages (from tiktoken>=0.7.0->litellm>=1.0->llmrouter-lib==0.2.0) (2026.1.15)\n", + "Requirement already satisfied: setuptools in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (80.9.0)\n", + "Requirement already satisfied: sympy>=1.13.3 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (1.14.0)\n", + "Requirement already satisfied: networkx>=2.5.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (3.6.1)\n", + "Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.93)\n", + "Requirement already satisfied: nvidia-cuda-runtime-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.90)\n", + "Requirement already satisfied: nvidia-cuda-cupti-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.90)\n", + "Requirement already satisfied: nvidia-cudnn-cu12==9.10.2.21 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (9.10.2.21)\n", + "Requirement already satisfied: nvidia-cublas-cu12==12.8.4.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.4.1)\n", + "Requirement already satisfied: nvidia-cufft-cu12==11.3.3.83 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (11.3.3.83)\n", + "Requirement already satisfied: nvidia-curand-cu12==10.3.9.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (10.3.9.90)\n", + "Requirement already satisfied: nvidia-cusolver-cu12==11.7.3.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (11.7.3.90)\n", + "Requirement already satisfied: nvidia-cusparse-cu12==12.5.8.93 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.5.8.93)\n", + "Requirement already satisfied: nvidia-cusparselt-cu12==0.7.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (0.7.1)\n", + "Requirement already satisfied: nvidia-nccl-cu12==2.27.5 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (2.27.5)\n", + "Requirement already satisfied: nvidia-nvshmem-cu12==3.3.20 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (3.3.20)\n", + "Requirement already satisfied: nvidia-nvtx-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.90)\n", + "Requirement already satisfied: nvidia-nvjitlink-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.93)\n", + "Requirement already satisfied: nvidia-cufile-cu12==1.13.1.3 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (1.13.1.3)\n", + "Requirement already satisfied: triton==3.5.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (3.5.1)\n", + "Requirement already satisfied: mpmath<1.4,>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from sympy>=1.13.3->torch>=2.0->llmrouter-lib==0.2.0) (1.3.0)\n", + "Requirement already satisfied: pyparsing in /opt/conda/lib/python3.13/site-packages (from torch-geometric>=2.3->llmrouter-lib==0.2.0) (3.3.1)\n", + "Building wheels for collected packages: llmrouter-lib\n", + " Building editable for llmrouter-lib (pyproject.toml) ... \u001b[?25ldone\n", + "\u001b[?25h Created wheel for llmrouter-lib: filename=llmrouter_lib-0.2.0-0.editable-py3-none-any.whl size=15709 sha256=08cf66436fc43f6442e1ad3868c6d8b0ef18961954fa2f170263e563fab8d7ca\n", + " Stored in directory: /tmp/pip-ephem-wheel-cache-03ewvlf_/wheels/82/4a/fd/59c4aec93c356c380d86a88ab74bece164c0aa855d68b155e4\n", + "Successfully built llmrouter-lib\n", + "Installing collected packages: llmrouter-lib\n", + " Attempting uninstall: llmrouter-lib\n", + " Found existing installation: llmrouter-lib 0.2.0\n", + " Uninstalling llmrouter-lib-0.2.0:\n", + " Successfully uninstalled llmrouter-lib-0.2.0\n", + "Successfully installed llmrouter-lib-0.2.0\n", + "Requirement already satisfied: pyyaml in /opt/conda/lib/python3.13/site-packages (6.0.3)\n", + "Requirement already satisfied: scikit-learn in /opt/conda/lib/python3.13/site-packages (1.8.0)\n", + "Requirement already satisfied: transformers in /opt/conda/lib/python3.13/site-packages (4.57.6)\n", + "Requirement already satisfied: torch in /opt/conda/lib/python3.13/site-packages (2.9.1)\n", + "Requirement already satisfied: numpy>=1.24.1 in /opt/conda/lib/python3.13/site-packages (from scikit-learn) (2.3.5)\n", + "Requirement already satisfied: scipy>=1.10.0 in /opt/conda/lib/python3.13/site-packages (from scikit-learn) (1.16.3)\n", + "Requirement already satisfied: joblib>=1.3.0 in /opt/conda/lib/python3.13/site-packages (from scikit-learn) (1.5.3)\n", + "Requirement already satisfied: threadpoolctl>=3.2.0 in /opt/conda/lib/python3.13/site-packages (from scikit-learn) (3.6.0)\n", + "Requirement already satisfied: filelock in /opt/conda/lib/python3.13/site-packages (from transformers) (3.20.2)\n", + "Requirement already satisfied: huggingface-hub<1.0,>=0.34.0 in /opt/conda/lib/python3.13/site-packages (from transformers) (0.36.0)\n", + "Requirement already satisfied: packaging>=20.0 in /opt/conda/lib/python3.13/site-packages (from transformers) (25.0)\n", + "Requirement already satisfied: regex!=2019.12.17 in /opt/conda/lib/python3.13/site-packages (from transformers) (2026.1.15)\n", + "Requirement already satisfied: requests in /opt/conda/lib/python3.13/site-packages (from transformers) (2.32.5)\n", + "Requirement already satisfied: tokenizers<=0.23.0,>=0.22.0 in /opt/conda/lib/python3.13/site-packages (from transformers) (0.22.2)\n", + "Requirement already satisfied: safetensors>=0.4.3 in /opt/conda/lib/python3.13/site-packages (from transformers) (0.7.0)\n", + "Requirement already satisfied: tqdm>=4.27 in /opt/conda/lib/python3.13/site-packages (from transformers) (4.67.1)\n", + "Requirement already satisfied: fsspec>=2023.5.0 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (2025.10.0)\n", + "Requirement already satisfied: typing-extensions>=3.7.4.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (4.15.0)\n", + "Requirement already satisfied: hf-xet<2.0.0,>=1.1.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (1.2.0)\n", + "Requirement already satisfied: setuptools in /opt/conda/lib/python3.13/site-packages (from torch) (80.9.0)\n", + "Requirement already satisfied: sympy>=1.13.3 in /opt/conda/lib/python3.13/site-packages (from torch) (1.14.0)\n", + "Requirement already satisfied: networkx>=2.5.1 in /opt/conda/lib/python3.13/site-packages (from torch) (3.6.1)\n", + "Requirement already satisfied: jinja2 in /opt/conda/lib/python3.13/site-packages (from torch) (3.1.6)\n", + "Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.93)\n", + "Requirement already satisfied: nvidia-cuda-runtime-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-cuda-cupti-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-cudnn-cu12==9.10.2.21 in /opt/conda/lib/python3.13/site-packages (from torch) (9.10.2.21)\n", + "Requirement already satisfied: nvidia-cublas-cu12==12.8.4.1 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.4.1)\n", + "Requirement already satisfied: nvidia-cufft-cu12==11.3.3.83 in /opt/conda/lib/python3.13/site-packages (from torch) (11.3.3.83)\n", + "Requirement already satisfied: nvidia-curand-cu12==10.3.9.90 in /opt/conda/lib/python3.13/site-packages (from torch) (10.3.9.90)\n", + "Requirement already satisfied: nvidia-cusolver-cu12==11.7.3.90 in /opt/conda/lib/python3.13/site-packages (from torch) (11.7.3.90)\n", + "Requirement already satisfied: nvidia-cusparse-cu12==12.5.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.5.8.93)\n", + "Requirement already satisfied: nvidia-cusparselt-cu12==0.7.1 in /opt/conda/lib/python3.13/site-packages (from torch) (0.7.1)\n", + "Requirement already satisfied: nvidia-nccl-cu12==2.27.5 in /opt/conda/lib/python3.13/site-packages (from torch) (2.27.5)\n", + "Requirement already satisfied: nvidia-nvshmem-cu12==3.3.20 in /opt/conda/lib/python3.13/site-packages (from torch) (3.3.20)\n", + "Requirement already satisfied: nvidia-nvtx-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-nvjitlink-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.93)\n", + "Requirement already satisfied: nvidia-cufile-cu12==1.13.1.3 in /opt/conda/lib/python3.13/site-packages (from torch) (1.13.1.3)\n", + "Requirement already satisfied: triton==3.5.1 in /opt/conda/lib/python3.13/site-packages (from torch) (3.5.1)\n", + "Requirement already satisfied: mpmath<1.4,>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from sympy>=1.13.3->torch) (1.3.0)\n", + "Requirement already satisfied: MarkupSafe>=2.0 in /opt/conda/lib/python3.13/site-packages (from jinja2->torch) (3.0.3)\n", + "Requirement already satisfied: charset_normalizer<4,>=2 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (3.4.4)\n", + "Requirement already satisfied: idna<4,>=2.5 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (3.11)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (2.6.2)\n", + "Requirement already satisfied: certifi>=2017.4.17 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (2026.1.4)\n" + ] + } + ], + "source": [ + "# For Google Colab: Clone repository and install dependencies\n", + "import os\n", + "# Install required packages (for Colab)\n", + "!git clone https://github.com/ulab-uiuc/LLMRouter.git\n", + "%cd LLMRouter\n", + "!pip install -e .\n", + "!pip install pyyaml scikit-learn transformers torch\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "os.environ['OPENAI_API_KEY'] = 'your-key'\n", + "os.environ['ANTHROPIC_API_KEY'] = 'your-key'\n", + "# Or for multiple keys:\n", + "os.environ['API_KEYS'] = '[\"key1\", \"key2\"]'" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Environment setup complete!\n" + ] + } + ], + "source": [ + "from llmrouter.models.knnmultiroundrouter import KNNMultiRoundRouter, KNNMultiRoundRouterTrainer\n", + "from llmrouter.utils import setup_environment\n", + "\n", + "setup_environment()\n", + "print(\"Environment setup complete!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Configuration\n", + "\n", + "KNNMultiRoundRouter uses the following configuration parameters:\n", + "\n", + "### KNN Parameters\n", + "\n", + "| Parameter | Description | Default |\n", + "|-----------|-------------|--------|\n", + "| `n_neighbors` | Number of neighbors (K value) | 5 |\n", + "| `weights` | Weight function: \"uniform\" or \"distance\" | \"uniform\" |\n", + "| `algorithm` | KNN algorithm: \"auto\", \"ball_tree\", \"kd_tree\", \"brute\" | \"auto\" |\n", + "| `metric` | Distance metric | \"minkowski\" |\n", + "| `p` | Power for Minkowski (1=Manhattan, 2=Euclidean) | 2 |\n", + "\n", + "### Multi-Round Parameters\n", + "\n", + "| Parameter | Description | Default |\n", + "|-----------|-------------|--------|\n", + "| `base_model` | LLM for decomposition/aggregation | \"Qwen/Qwen2.5-3B-Instruct\" |\n", + "| `use_local_llm` | Use vLLM for local inference | false |\n", + "| `api_endpoint` | API endpoint for execution | - |" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Current Configuration:\n", + "==================================================\n", + "api_endpoint: https://integrate.api.nvidia.com/v1\n", + "api_key: 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\n", + "base_model: qwen/qwen2.5-7b-instruct\n", + "data_path:\n", + " llm_data: data/example_data/llm_candidates/default_llm.json\n", + " llm_embedding_data: data/example_data/llm_candidates/default_llm_embeddings.json\n", + " query_data_test: data/example_data/query_data/default_query_test.jsonl\n", + " query_data_train: data/example_data/query_data/default_query_train.jsonl\n", + " query_embedding_data: data/example_data/routing_data/query_embeddings_longformer.pt\n", + " routing_data_test: data/example_data/routing_data/default_routing_test_data.jsonl\n", + " routing_data_train: data/example_data/routing_data/default_routing_train_data.jsonl\n", + "hparam:\n", + " algorithm: auto\n", + " leaf_size: 30\n", + " metric: minkowski\n", + " n_jobs: -1\n", + " n_neighbors: 5\n", + " p: 2\n", + " weights: uniform\n", + "metric:\n", + " weights:\n", + " cost: 0\n", + " llm_judge: 0\n", + " performance: 1\n", + "model_path:\n", + " ini_model_path: ''\n", + " save_model_path: saved_models/knnmultiroundrouter/knnmultiroundrouter.pkl\n", + "use_local_llm: false\n", + "\n" + ] + } + ], + "source": [ + "import yaml\n", + "\n", + "CONFIG_PATH = \"configs/model_config_train/knnmultiroundrouter.yaml\"\n", + "\n", + "with open(CONFIG_PATH, 'r') as f:\n", + " config = yaml.safe_load(f)\n", + "\n", + "print(\"Current Configuration:\")\n", + "print(\"=\" * 50)\n", + "print(yaml.dump(config, default_flow_style=False))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Initialize Router" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "✅ MetaRouter initialized successfully (YAML + data loaded).\n", + "Router initialized successfully!\n", + "Number of training samples: 50544\n", + "Number of LLM candidates: 7\n", + "K value (n_neighbors): 5\n" + ] + } + ], + "source": [ + "router = KNNMultiRoundRouter(yaml_path=CONFIG_PATH)\n", + "\n", + "print(\"Router initialized successfully!\")\n", + "print(f\"Number of training samples: {len(router.routing_data_train)}\")\n", + "print(f\"Number of LLM candidates: {len(router.llm_data)}\")\n", + "print(f\"K value (n_neighbors): {config['hparam']['n_neighbors']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Training\n", + "\n", + "Training fits the KNN model on query embeddings and their best LLM labels." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[KNNMultiRoundRouterTrainer] Initialized with router.\n", + "Trainer initialized!\n", + "Using device: cuda\n" + ] + } + ], + "source": [ + "import torch\n", + "\n", + "device = (\n", + " \"cuda\" if torch.cuda.is_available()\n", + " else \"cpu\"\n", + ")\n", + "\n", + "trainer = KNNMultiRoundRouterTrainer(router=router, device='device')\n", + "\n", + "print(\"Trainer initialized!\")\n", + "print(f\"Using device: {device}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting training...\n", + "==================================================\n", + "Training KNN model on 5608 examples...\n", + "KNN model training completed!\n", + "Saving trained model to: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnmultiroundrouter/knnmultiroundrouter.pkl\n", + "Successfully saved pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnmultiroundrouter/knnmultiroundrouter.pkl\n", + "Model saved successfully!\n", + "==================================================\n", + "Training completed!\n" + ] + } + ], + "source": [ + "print(\"Starting training...\")\n", + "print(\"=\" * 50)\n", + "\n", + "trainer.train()\n", + "\n", + "print(\"=\" * 50)\n", + "print(\"Training completed!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Hyperparameter Tuning" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "K Value Comparison (5-fold CV):\n", + "========================================\n", + "K=1: Accuracy = 0.3607 (+/- 0.0708)\n", + "K=3: Accuracy = 0.4158 (+/- 0.0700)\n", + "K=5: Accuracy = 0.4399 (+/- 0.0738)\n", + "K=7: Accuracy = 0.4542 (+/- 0.0724)\n", + "K=9: Accuracy = 0.4672 (+/- 0.0681)\n", + "K=11: Accuracy = 0.4725 (+/- 0.0701)\n", + "\n", + "Best K: 11 with accuracy: 0.4725\n" + ] + } + ], + "source": [ + "from sklearn.model_selection import cross_val_score\n", + "from sklearn.neighbors import KNeighborsClassifier\n", + "import numpy as np\n", + "\n", + "# Prepare data for hyperparameter tuning\n", + "X_train = router.query_embedding_train.numpy() if hasattr(router.query_embedding_list, 'numpy') else router.query_embedding_list\n", + "y_train = router.model_name_list\n", + "\n", + "# Test different K values\n", + "k_values = [1, 3, 5, 7, 9, 11]\n", + "print(\"K Value Comparison (5-fold CV):\")\n", + "print(\"=\" * 40)\n", + "\n", + "best_k = 5\n", + "best_score = 0\n", + "\n", + "for k in k_values:\n", + " knn = KNeighborsClassifier(n_neighbors=k)\n", + " scores = cross_val_score(knn, X_train, y_train, cv=5, scoring='accuracy')\n", + " mean_score = scores.mean()\n", + " print(f\"K={k}: Accuracy = {mean_score:.4f} (+/- {scores.std():.4f})\")\n", + " \n", + " if mean_score > best_score:\n", + " best_score = mean_score\n", + " best_k = k\n", + "\n", + "print(f\"\\nBest K: {best_k} with accuracy: {best_score:.4f}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Test different distance metrics\n", + "metrics = [('euclidean', 2), ('manhattan', 1), ('minkowski', 3)]\n", + "\n", + "print(\"\\nDistance Metric Comparison:\")\n", + "print(\"=\" * 40)\n", + "\n", + "for metric_name, p in metrics:\n", + " if metric_name == 'minkowski':\n", + " knn = KNeighborsClassifier(n_neighbors=best_k, metric='minkowski', p=p)\n", + " name = f\"Minkowski (p={p})\"\n", + " else:\n", + " knn = KNeighborsClassifier(n_neighbors=best_k, metric=metric_name)\n", + " name = metric_name.capitalize()\n", + " \n", + " scores = cross_val_score(knn, X_train, y_train, cv=5, scoring='accuracy')\n", + " print(f\"{name}: Accuracy = {scores.mean():.4f} (+/- {scores.std():.4f})\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. Model Verification" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Test Routing (KNN-based):\n", + "============================================================\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnmultiroundrouter/knnmultiroundrouter.pkl\n", + "[KNNMultiRoundRouter] Loaded KNN model from /home/zhongjie/LLMRouter/llmrouter/saved_models/knnmultiroundrouter/knnmultiroundrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Input ids are automatically padded to be a multiple of `config.attention_window`: 512\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1. What is the capital of France?...\n", + " Routed to: gemma-2-9b-it\n", + "2. Explain machine learning in simple terms....\n", + " Routed to: qwen2.5-7b-instruct\n", + "3. Calculate the integral of x^2....\n", + " Routed to: qwen2.5-7b-instruct\n" + ] + } + ], + "source": [ + "# Test routing on a sample query (without execution)\n", + "# Note: Multi-round routers perform decomposition + routing, not just routing\n", + "\n", + "test_queries = [\n", + " {\"query\": \"What is the capital of France?\"},\n", + " {\"query\": \"Explain machine learning in simple terms.\"},\n", + " {\"query\": \"Calculate the integral of x^2.\"},\n", + "]\n", + "\n", + "print(\"Test Routing (KNN-based):\")\n", + "print(\"=\" * 60)\n", + "\n", + "for i, query in enumerate(test_queries, 1):\n", + " # Use the underlying KNN router for simple routing test\n", + " result = router._route_sub_query(query['query'])\n", + " print(f\"{i}. {query['query'][:50]}...\")\n", + " print(f\" Routed to: {result}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7. Save Model" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Model saved to: saved_models/knnmultiroundrouter/knnmultiroundrouter.pkl\n" + ] + } + ], + "source": [ + "import pickle\n", + "\n", + "save_path = config['model_path']['save_model_path']\n", + "os.makedirs(os.path.dirname(save_path), exist_ok=True)\n", + "\n", + "with open(save_path, 'wb') as f:\n", + " pickle.dump(router.model, f)\n", + "\n", + "print(f\"Model saved to: {save_path}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "In this notebook, we:\n", + "\n", + "1. **Loaded Configuration**: Set up KNNMultiRoundRouter with YAML config\n", + "2. **Trained Model**: Fitted KNN classifier on query embeddings\n", + "3. **Tuned Hyperparameters**: Tested different K values and distance metrics\n", + "4. **Verified Model**: Tested routing on sample queries\n", + "5. **Saved Model**: Persisted trained model for inference\n", + "\n", + "**Key Differences from Single-Round KNNRouter**:\n", + "- Supports query decomposition into sub-queries\n", + "- Aggregates responses from multiple models\n", + "- Uses LLM for decomposition and aggregation steps\n", + "\n", + "**Next Steps**:\n", + "- Use the next part of notebook for full pipeline inference" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# KNNMultiRoundRouter - Inference\n", + "\n", + "This notebook demonstrates how to use a trained **KNNMultiRoundRouter** for inference.\n", + "\n", + "## Pipeline Overview\n", + "\n", + "The multi-round routing pipeline consists of:\n", + "\n", + "1. **Decompose**: Break complex queries into simpler sub-queries using LLM\n", + "2. **Route**: Use trained KNN to route each sub-query to the best model\n", + "3. **Execute**: Call the selected model API to get responses\n", + "4. **Aggregate**: Combine all sub-responses into a final answer" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Environment Setup" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "from llmrouter.models.knnmultiroundrouter import KNNMultiRoundRouter\n", + "from llmrouter.utils import setup_environment\n", + "import yaml\n", + "\n", + "setup_environment()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Load Trained Router" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "✅ MetaRouter initialized successfully (YAML + data loaded).\n", + "Router loaded!\n", + "Base model for decomposition: qwen/qwen2.5-7b-instruct\n", + "Use local LLM: False\n" + ] + } + ], + "source": [ + "CONFIG_PATH = \"configs/model_config_train/knnmultiroundrouter.yaml\"\n", + "\n", + "router = KNNMultiRoundRouter(yaml_path=CONFIG_PATH)\n", + "print(\"Router loaded!\")\n", + "\n", + "# Load configuration\n", + "with open(CONFIG_PATH, 'r') as f:\n", + " config = yaml.safe_load(f)\n", + "\n", + "print(f\"Base model for decomposition: {config.get('base_model', 'Qwen/Qwen2.5-3B-Instruct')}\")\n", + "print(f\"Use local LLM: {config.get('use_local_llm', False)}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Simple Query Routing (Chat Mode)\n", + "\n", + "For simple string queries, the router returns just the response." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Query: What is the capital of France and what is its population?\n", + "============================================================\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnmultiroundrouter/knnmultiroundrouter.pkl\n", + "[KNNMultiRoundRouter] Loaded KNN model from /home/zhongjie/LLMRouter/llmrouter/saved_models/knnmultiroundrouter/knnmultiroundrouter.pkl\n", + "\n", + "\u001b[1;31mGive Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new\u001b[0m\n", + "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.\n", + "\n", + "\n", + "Response:\n", + "To answer the question \"What is the capital of France and what is its population?\", let's break it down step by step.\n", + "\n", + "1. **Identifying the Capital of France:**\n", + " - The capital of France is Paris. This is a well-known fact and does not require additional information to confirm.\n", + "\n", + "2. **Finding the Population of Paris:**\n", + " - The population of Paris can vary slightly depending on the source and the time of the latest census. However, as of the most recent data available, Paris has a population of approximately 2.2 million people.\n", + "\n", + "Combining these two pieces of information:\n", + "\n", + "- The capital of France is Paris.\n", + "- The population of Paris is approximately 2.2 million.\n", + "\n", + "Therefore, the answer to the original question is:\n", + "\n", + "The capital of France is Paris, and its population is approximately 2.2 million.\n", + "\n", + "\n", + "The capital of France is Paris, and its population is approximately 2.2 million.\n", + "\n" + ] + } + ], + "source": [ + "# Simple chat mode - pass string, get string response\n", + "simple_query = \"What is the capital of France and what is its population?\"\n", + "\n", + "print(f\"Query: {simple_query}\")\n", + "print(\"=\" * 60)\n", + "\n", + "try:\n", + " response = router.route_single(simple_query)\n", + " print(f\"\\nResponse:\\n{response}\")\n", + "except Exception as e:\n", + " print(f\"Error: {e}\")\n", + " print(\"Note: Multi-round routing requires API access for execution.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Evaluation Mode\n", + "\n", + "For evaluation with metrics, pass a dict with query, task_name, and ground_truth." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Query: What is 15 * 23?\n", + "Task: math\n", + "Ground Truth: 345\n", + "============================================================\n", + "\n", + "Response: The question is asking for the product of 15 and 23.\n", + "\n", + "To solve this, we can break it down as follows:\n", + "\n", + "- First, we can consider 15 * 20, which is 300.\n", + "- Then, we can consider 15 * 3, which is 45.\n", + "- Adding these two results together: 300 + 45 = 345.\n", + "\n", + "Therefore, the answer is 345.\n", + "\n", + "345\n", + "Success: True\n", + "Prompt Tokens: 271\n", + "Completion Tokens: 66\n", + "Task Performance: 0.00\n" + ] + } + ], + "source": [ + "# Evaluation mode - pass dict, get detailed result with metrics\n", + "eval_query = {\n", + " \"query\": \"What is 15 * 23?\",\n", + " \"task_name\": \"math\",\n", + " \"ground_truth\": \"345\"\n", + "}\n", + "\n", + "print(f\"Query: {eval_query['query']}\")\n", + "print(f\"Task: {eval_query['task_name']}\")\n", + "print(f\"Ground Truth: {eval_query['ground_truth']}\")\n", + "print(\"=\" * 60)\n", + "\n", + "try:\n", + " result = router.route_single(eval_query)\n", + " \n", + " print(f\"\\nResponse: {result.get('response', 'N/A')}\")\n", + " print(f\"Success: {result.get('success', False)}\")\n", + " print(f\"Prompt Tokens: {result.get('prompt_tokens', 0)}\")\n", + " print(f\"Completion Tokens: {result.get('completion_tokens', 0)}\")\n", + " if 'task_performance' in result:\n", + " print(f\"Task Performance: {result['task_performance']:.2f}\")\n", + "except Exception as e:\n", + " print(f\"Error: {e}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Batch Processing" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Processing 3 queries...\n", + "============================================================\n", + "\n", + "1. Query: Explain photosynthesis....\n", + " Success: True\n", + " Response: Photosynthesis is a process used by plants, algae, and some bacteria to convert light energy, usuall...\n", + "\n", + "2. Query: What causes earthquakes?...\n", + " Success: True\n", + " Response: To answer the question \"What causes earthquakes?\", let's break it down step by step:\n", + "\n", + "1. **Understan...\n", + "\n", + "3. Query: How do computers work?...\n", + " Success: True\n", + " Response: To understand how computers work, let's break it down step by step:\n", + "\n", + "1. **Hardware Components**: Com...\n" + ] + } + ], + "source": [ + "# Batch processing with multiple queries\n", + "batch_queries = [\n", + " {\"query\": \"Explain photosynthesis.\"},\n", + " {\"query\": \"What causes earthquakes?\"},\n", + " {\"query\": \"How do computers work?\"},\n", + "]\n", + "\n", + "print(f\"Processing {len(batch_queries)} queries...\")\n", + "print(\"=\" * 60)\n", + "\n", + "try:\n", + " results = router.route_batch(batch_queries)\n", + " \n", + " for i, result in enumerate(results, 1):\n", + " print(f\"\\n{i}. Query: {result.get('query', 'N/A')[:50]}...\")\n", + " print(f\" Success: {result.get('success', False)}\")\n", + " print(f\" Response: {result.get('response', 'N/A')[:100]}...\")\n", + "except Exception as e:\n", + " print(f\"Error: {e}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. Understanding the Pipeline\n", + "\n", + "Let's examine the multi-round pipeline steps." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Multi-Round Pipeline Components:\n", + "============================================================\n", + "\n", + "1. DECOMPOSITION\n", + " - Uses LLM to break complex query into sub-queries\n", + " - Base model: qwen/qwen2.5-7b-instruct\n", + "\n", + "2. ROUTING (KNN-based)\n", + " - K value: 5\n", + " - Distance metric: minkowski\n", + " - Weight function: uniform\n", + "\n", + "3. EXECUTION\n", + " - Calls routed model API for each sub-query\n", + " - API endpoint: https://integrate.api.nvidia.com/v1\n", + "\n", + "4. AGGREGATION\n", + " - Combines sub-query responses into final answer\n", + " - Uses LLM for intelligent synthesis\n" + ] + } + ], + "source": [ + "# Demonstrate the pipeline components\n", + "print(\"Multi-Round Pipeline Components:\")\n", + "print(\"=\" * 60)\n", + "\n", + "print(\"\\n1. DECOMPOSITION\")\n", + "print(\" - Uses LLM to break complex query into sub-queries\")\n", + "print(f\" - Base model: {config.get('base_model', 'Qwen/Qwen2.5-3B-Instruct')}\")\n", + "\n", + "print(\"\\n2. ROUTING (KNN-based)\")\n", + "print(f\" - K value: {config['hparam']['n_neighbors']}\")\n", + "print(f\" - Distance metric: {config['hparam']['metric']}\")\n", + "print(f\" - Weight function: {config['hparam']['weights']}\")\n", + "\n", + "print(\"\\n3. EXECUTION\")\n", + "print(\" - Calls routed model API for each sub-query\")\n", + "print(f\" - API endpoint: {config.get('api_endpoint', 'Not configured')}\")\n", + "\n", + "print(\"\\n4. AGGREGATION\")\n", + "print(\" - Combines sub-query responses into final answer\")\n", + "print(\" - Uses LLM for intelligent synthesis\")" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Available LLM Candidates:\n", + "============================================================\n", + "1. qwen2.5-7b-instruct: 7BB parameters\n", + "2. llama-3.1-8b-instruct: 8BB parameters\n", + "3. mistral-7b-instruct-v0.3: 7BB parameters\n", + "4. llama-3.3-nemotron-super-49b-v1: 49BB parameters\n", + "5. llama3-70b-instruct: 70BB parameters\n", + "6. mixtral-8x7b-instruct-v0.1: 45BB parameters\n", + "7. mixtral-8x22b-instruct-v0.1: 141BB parameters\n" + ] + } + ], + "source": [ + "# Show available LLM candidates for routing\n", + "print(\"\\nAvailable LLM Candidates:\")\n", + "print(\"=\" * 60)\n", + "\n", + "for i, (name, info) in enumerate(router.llm_data.items(), 1):\n", + " size = info.get('size', 'unknown')\n", + " print(f\"{i}. {name}: {size}B parameters\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7. Evaluation" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Evaluation Metrics:\n", + "--------------------------------------------------\n", + "Total queries: 3\n", + "Evaluated queries: 0\n", + "Average score (N/A): 0.0000\n" + ] + } + ], + "source": [ + "from llmrouter.evaluation import evaluate_batch\n", + "from collections import Counter\n", + "\n", + "eval_data = []\n", + "for r in results:\n", + " if r.get('ground_truth'):\n", + " eval_data.append({\n", + " 'prediction': r.get('response', ''),\n", + " 'ground_truth': r.get('ground_truth'),\n", + " 'metric': r.get('metric', 'em')\n", + " })\n", + "\n", + "if eval_data:\n", + " eval_results = evaluate_batch(eval_data)\n", + " scores = [r['score'] for r in eval_results]\n", + " avg_score = sum(scores) / len(scores) if scores else 0\n", + "else:\n", + " eval_results = []\n", + " avg_score = 0\n", + "\n", + "\n", + "print(\"\\nEvaluation Metrics:\")\n", + "print(\"-\" * 50)\n", + "print(f\"Total queries: {len(results)}\")\n", + "print(f\"Evaluated queries: {len(eval_data)}\")\n", + "print(f\"Average score ({eval_data[0]['metric'] if eval_data else 'N/A'}): {avg_score:.4f}\")\n", + "\n", + "if eval_results:\n", + " print(\"\\nDetailed Scores:\")\n", + " print(\"-\" * 50)\n", + " for i, r in enumerate(eval_results[:5], 1):\n", + " pred = r.get('prediction', '')[:30]\n", + " gt = r.get('ground_truth', '')[:30]\n", + " print(f\" {i}. Score: {r['score']:.4f} | pred: {pred} | ground truth: {gt}\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 8. File-Based Inference\n", + "\n", + "Load queries from a file and save results." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loaded 706 queries from: data/example_data/query_data/default_query_test.jsonl\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "\n", + "\u001b[1;31mGive Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new\u001b[0m\n", + "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.\n", + "\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "\n", + "\u001b[1;31mGive Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new\u001b[0m\n", + "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.\n", + "\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "\n", + "\u001b[1;31mGive Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new\u001b[0m\n", + "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.\n", + "\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "\n", + "\u001b[1;31mGive Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new\u001b[0m\n", + "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.\n", + "\n", + "Routed 5 queries\n", + "Results saved to: outputs/knnmultiroundrouter_results.jsonl\n", + "\n", + "Sample results:\n", + " 1. Q: There are 4 houses in a row, numbered...\n", + " Success: True\n", + " 2. Q: There are 3 houses in a row, numbered...\n", + " Success: False\n", + " 3. Q: There are 3 houses in a row, numbered...\n", + " Success: False\n" + ] + } + ], + "source": [ + "import json\n", + "\n", + "# Load queries from a JSONL file\n", + "def load_queries_from_file(file_path):\n", + " \"\"\"Load queries from a JSONL file.\"\"\"\n", + " queries = []\n", + " with open(file_path, 'r', encoding='utf-8') as f:\n", + " for line in f:\n", + " if line.strip():\n", + " queries.append(json.loads(line))\n", + " return queries\n", + "\n", + "# Save results to a JSONL file\n", + "def save_results_to_file(results, output_path):\n", + " \"\"\"Save routing results to a JSONL file.\"\"\"\n", + " os.makedirs(os.path.dirname(output_path), exist_ok=True)\n", + " with open(output_path, 'w', encoding='utf-8') as f:\n", + " for result in results:\n", + " f.write(json.dumps(result, ensure_ascii=False) + '\\n')\n", + " print(f\"Results saved to: {output_path}\")\n", + "\n", + "# Example: Load from default query file\n", + "QUERY_FILE = \"data/example_data/query_data/default_query_test.jsonl\"\n", + "OUTPUT_FILE = \"outputs/knnmultiroundrouter_results.jsonl\"\n", + "\n", + "if os.path.exists(QUERY_FILE):\n", + " # Load queries\n", + " file_queries = load_queries_from_file(QUERY_FILE)\n", + " print(f\"Loaded {len(file_queries)} queries from: {QUERY_FILE}\")\n", + " \n", + " # Route queries (limit to 5 for demo due to API costs)\n", + " try:\n", + " file_results = router.route_batch(file_queries[:5])\n", + " print(f\"Routed {len(file_results)} queries\")\n", + " \n", + " # Save results\n", + " save_results_to_file(file_results, OUTPUT_FILE)\n", + " \n", + " # Show sample results\n", + " print(f\"\\nSample results:\")\n", + " for i, result in enumerate(file_results[:3], 1):\n", + " print(f\" {i}. {result.get('query', '')[:40]}...\")\n", + " print(f\" Success: {result.get('success', False)}\")\n", + " except Exception as e:\n", + " print(f\"Error during batch routing: {e}\")\n", + "else:\n", + " print(f\"Query file not found: {QUERY_FILE}\")\n", + " print(\"Create a JSONL file with format: {\\\"query\\\": \\\"Your question\\\"}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "**KNNMultiRoundRouter** provides:\n", + "- Query decomposition for complex questions\n", + "- KNN-based routing for each sub-query\n", + "- Parallel execution across multiple models\n", + "- Intelligent response aggregation\n", + "\n", + "**Use Cases**:\n", + "- Complex questions requiring multiple expertise areas\n", + "- Multi-step reasoning tasks\n", + "- Questions that benefit from specialized models\n", + "\n", + "**Requirements**:\n", + "- Trained KNN model (from training notebook)\n", + "- API access for LLM execution\n", + "- Optional: vLLM for local decomposition/aggregation" + ] + } + ], + "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.13.11" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/colab_notebooks/knnrouter/01_knnrouter_training_and_inference.ipynb b/colab_notebooks/knnrouter/01_knnrouter_training_and_inference.ipynb new file mode 100644 index 0000000..552cd5d --- /dev/null +++ b/colab_notebooks/knnrouter/01_knnrouter_training_and_inference.ipynb @@ -0,0 +1,4167 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# KNNRouter - Training\n", + "\n", + "This notebook demonstrates how to train the **KNNRouter** (K-Nearest Neighbors Router).\n", + "\n", + "## Overview\n", + "\n", + "KNNRouter uses a K-Nearest Neighbors classifier to route queries to the most suitable LLM based on:\n", + "- Query embeddings (using Longformer)\n", + "- Historical performance data\n", + "\n", + "**Key Features**:\n", + "- Simple and interpretable\n", + "- Fast training and inference\n", + "- Works well with limited training data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Environment Setup" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cloning into 'LLMRouter'...\n", + "remote: Enumerating objects: 5897, done.\u001b[K\n", + "remote: Counting objects: 100% (212/212), done.\u001b[K\n", + "remote: Compressing objects: 100% (135/135), done.\u001b[K\n", + "remote: Total 5897 (delta 102), reused 112 (delta 77), pack-reused 5685 (from 1)\u001b[K\n", + "Receiving objects: 100% (5897/5897), 88.90 MiB | 55.07 MiB/s, done.\n", + "Resolving deltas: 100% (2881/2881), done.\n", + "Updating files: 100% (280/280), done.\n", + "/home/zhongjie/LLMRouter\n", + "Obtaining file:///home/zhongjie/LLMRouter\n", + " Installing build dependencies ... \u001b[?25ldone\n", + "\u001b[?25h Checking if build backend supports build_editable ... \u001b[?25ldone\n", + "\u001b[?25h Getting requirements to build editable ... \u001b[?25ldone\n", + "\u001b[?25h Preparing editable metadata (pyproject.toml) ... \u001b[?25ldone\n", + "\u001b[?25hRequirement already satisfied: torch>=2.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (2.9.1)\n", + "Requirement already satisfied: transformers>=4.40 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (4.57.3)\n", + "Requirement already satisfied: sentencepiece>=0.1.99 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (0.2.1)\n", + "Requirement already satisfied: numpy>=1.21 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (2.4.1)\n", + "Requirement already satisfied: pandas>=1.5 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (2.3.3)\n", + "Requirement already satisfied: scikit-learn>=1.2 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (1.8.0)\n", + "Requirement already satisfied: pyyaml>=6.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (6.0.3)\n", + "Requirement already satisfied: tqdm>=4.65 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (4.67.1)\n", + "Requirement already satisfied: datasets>=2.14 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (4.4.2)\n", + "Requirement already satisfied: pydantic>=2.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (2.12.5)\n", + "Requirement already satisfied: gradio>=4.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (6.3.0)\n", + "Requirement already satisfied: litellm>=1.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (1.80.13)\n", + "Requirement already satisfied: peft>=0.7 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (0.18.1)\n", + "Requirement already satisfied: torch-geometric>=2.3 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (2.7.0)\n", + "Requirement already satisfied: scipy>=1.10 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (1.17.0)\n", + "Requirement already satisfied: protobuf>=3.20 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.1.1) (6.33.3)\n", + "Requirement already satisfied: filelock in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.1.1) (3.20.3)\n", + "Requirement already satisfied: pyarrow>=21.0.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.1.1) (22.0.0)\n", + "Requirement already satisfied: dill<0.4.1,>=0.3.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.1.1) (0.4.0)\n", + "Requirement already satisfied: requests>=2.32.2 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.1.1) (2.32.5)\n", + "Requirement already satisfied: httpx<1.0.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.1.1) (0.28.1)\n", + "Requirement already satisfied: xxhash in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.1.1) (3.6.0)\n", + "Requirement already satisfied: multiprocess<0.70.19 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.1.1) (0.70.18)\n", + "Requirement already satisfied: fsspec<=2025.10.0,>=2023.1.0 in /opt/conda/lib/python3.13/site-packages (from fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.1.1) (2025.10.0)\n", + "Requirement already satisfied: huggingface-hub<2.0,>=0.25.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.1.1) (0.36.0)\n", + "Requirement already satisfied: packaging in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.1.1) (25.0)\n", + "Requirement already satisfied: aiohttp!=4.0.0a0,!=4.0.0a1 in /opt/conda/lib/python3.13/site-packages (from fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.1.1) (3.13.3)\n", + "Requirement already satisfied: anyio in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.1.1) (4.12.0)\n", + "Requirement already satisfied: certifi in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.1.1) (2026.1.4)\n", + "Requirement already satisfied: httpcore==1.* in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.1.1) (1.0.9)\n", + "Requirement already satisfied: idna in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.1.1) (3.11)\n", + "Requirement already satisfied: h11>=0.16 in /opt/conda/lib/python3.13/site-packages (from httpcore==1.*->httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.1.1) (0.16.0)\n", + "Requirement already satisfied: typing-extensions>=3.7.4.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<2.0,>=0.25.0->datasets>=2.14->llmrouter-lib==0.1.1) (4.15.0)\n", + "Requirement already satisfied: hf-xet<2.0.0,>=1.1.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<2.0,>=0.25.0->datasets>=2.14->llmrouter-lib==0.1.1) (1.2.0)\n", + "Requirement already satisfied: aiohappyeyeballs>=2.5.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.1.1) (2.6.1)\n", + "Requirement already satisfied: aiosignal>=1.4.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.1.1) (1.4.0)\n", + "Requirement already satisfied: attrs>=17.3.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.1.1) (25.4.0)\n", + "Requirement already satisfied: frozenlist>=1.1.1 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.1.1) (1.8.0)\n", + "Requirement already satisfied: multidict<7.0,>=4.5 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.1.1) (6.7.0)\n", + "Requirement already satisfied: propcache>=0.2.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.1.1) (0.4.1)\n", + "Requirement already satisfied: yarl<2.0,>=1.17.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.1.1) (1.22.0)\n", + "Requirement already satisfied: aiofiles<25.0,>=22.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (24.1.0)\n", + "Requirement already satisfied: audioop-lts<1.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (0.2.2)\n", + "Requirement already satisfied: brotli>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (1.2.0)\n", + "Requirement already satisfied: fastapi<1.0,>=0.115.2 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (0.128.0)\n", + "Requirement already satisfied: ffmpy in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (1.0.0)\n", + "Requirement already satisfied: gradio-client==2.0.3 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (2.0.3)\n", + "Requirement already satisfied: groovy~=0.1 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (0.1.2)\n", + "Requirement already satisfied: jinja2<4.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (3.1.6)\n", + "Requirement already satisfied: markupsafe<4.0,>=2.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (3.0.3)\n", + "Requirement already satisfied: orjson~=3.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (3.11.5)\n", + "Requirement already satisfied: pillow<13.0,>=8.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (12.1.0)\n", + "Requirement already satisfied: pydub in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (0.25.1)\n", + "Requirement already satisfied: python-multipart>=0.0.18 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (0.0.21)\n", + "Requirement already satisfied: safehttpx<0.2.0,>=0.1.7 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (0.1.7)\n", + "Requirement already satisfied: semantic-version~=2.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (2.10.0)\n", + "Requirement already satisfied: starlette<1.0,>=0.40.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (0.50.0)\n", + "Requirement already satisfied: tomlkit<0.14.0,>=0.12.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (0.13.3)\n", + "Requirement already satisfied: typer<1.0,>=0.12 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (0.21.1)\n", + "Requirement already satisfied: uvicorn>=0.14.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.1.1) (0.40.0)\n", + "Requirement already satisfied: annotated-doc>=0.0.2 in /opt/conda/lib/python3.13/site-packages (from fastapi<1.0,>=0.115.2->gradio>=4.0->llmrouter-lib==0.1.1) (0.0.4)\n", + "Requirement already satisfied: python-dateutil>=2.8.2 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.1.1) (2.9.0.post0)\n", + "Requirement already satisfied: pytz>=2020.1 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.1.1) (2025.2)\n", + "Requirement already satisfied: tzdata>=2022.7 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.1.1) (2025.3)\n", + "Requirement already satisfied: annotated-types>=0.6.0 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.1.1) (0.7.0)\n", + "Requirement already satisfied: pydantic-core==2.41.5 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.1.1) (2.41.5)\n", + "Requirement already satisfied: typing-inspection>=0.4.2 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.1.1) (0.4.2)\n", + "Requirement already satisfied: click>=8.0.0 in /opt/conda/lib/python3.13/site-packages (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.1.1) (8.3.1)\n", + "Requirement already satisfied: shellingham>=1.3.0 in /opt/conda/lib/python3.13/site-packages (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.1.1) (1.5.4)\n", + "Requirement already satisfied: rich>=10.11.0 in /opt/conda/lib/python3.13/site-packages (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.1.1) (14.2.0)\n", + "Requirement already satisfied: fastuuid>=0.13.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.1.1) (0.14.0)\n", + "Requirement already satisfied: grpcio!=1.68.*,!=1.69.*,!=1.70.*,!=1.71.0,!=1.71.1,!=1.72.0,!=1.72.1,!=1.73.0,>=1.62.3 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.1.1) (1.76.0)\n", + "Requirement already satisfied: importlib-metadata>=6.8.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.1.1) (8.7.0)\n", + "Requirement already satisfied: jsonschema<5.0.0,>=4.23.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.1.1) (4.25.1)\n", + "Requirement already satisfied: openai>=2.8.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.1.1) (2.15.0)\n", + "Requirement already satisfied: python-dotenv>=0.2.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.1.1) (1.2.1)\n", + "Requirement already satisfied: tiktoken>=0.7.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.1.1) (0.12.0)\n", + "Requirement already satisfied: tokenizers in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.1.1) (0.22.2)\n", + "Requirement already satisfied: jsonschema-specifications>=2023.03.6 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.1.1) (2025.9.1)\n", + "Requirement already satisfied: referencing>=0.28.4 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.1.1) (0.37.0)\n", + "Requirement already satisfied: rpds-py>=0.7.1 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.1.1) (0.30.0)\n", + "Requirement already satisfied: zipp>=3.20 in /opt/conda/lib/python3.13/site-packages (from importlib-metadata>=6.8.0->litellm>=1.0->llmrouter-lib==0.1.1) (3.23.0)\n", + "Requirement already satisfied: distro<2,>=1.7.0 in /opt/conda/lib/python3.13/site-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.1.1) (1.9.0)\n", + "Requirement already satisfied: jiter<1,>=0.10.0 in /opt/conda/lib/python3.13/site-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.1.1) (0.12.0)\n", + "Requirement already satisfied: sniffio in /opt/conda/lib/python3.13/site-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.1.1) (1.3.1)\n", + "Requirement already satisfied: psutil in /opt/conda/lib/python3.13/site-packages (from peft>=0.7->llmrouter-lib==0.1.1) (7.2.1)\n", + "Requirement already satisfied: accelerate>=0.21.0 in /opt/conda/lib/python3.13/site-packages (from peft>=0.7->llmrouter-lib==0.1.1) (1.12.0)\n", + "Requirement already satisfied: safetensors in /opt/conda/lib/python3.13/site-packages (from peft>=0.7->llmrouter-lib==0.1.1) (0.7.0)\n", + "Requirement already satisfied: six>=1.5 in /opt/conda/lib/python3.13/site-packages (from python-dateutil>=2.8.2->pandas>=1.5->llmrouter-lib==0.1.1) (1.17.0)\n", + "Requirement already satisfied: charset_normalizer<4,>=2 in /opt/conda/lib/python3.13/site-packages (from requests>=2.32.2->datasets>=2.14->llmrouter-lib==0.1.1) (3.4.4)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/conda/lib/python3.13/site-packages (from requests>=2.32.2->datasets>=2.14->llmrouter-lib==0.1.1) (2.6.2)\n", + "Requirement already satisfied: markdown-it-py>=2.2.0 in /opt/conda/lib/python3.13/site-packages (from rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.1.1) (4.0.0)\n", + "Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /opt/conda/lib/python3.13/site-packages (from rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.1.1) (2.19.2)\n", + "Requirement already satisfied: mdurl~=0.1 in /opt/conda/lib/python3.13/site-packages (from markdown-it-py>=2.2.0->rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.1.1) (0.1.2)\n", + "Requirement already satisfied: joblib>=1.3.0 in /opt/conda/lib/python3.13/site-packages (from scikit-learn>=1.2->llmrouter-lib==0.1.1) (1.5.3)\n", + "Requirement already satisfied: threadpoolctl>=3.2.0 in /opt/conda/lib/python3.13/site-packages (from scikit-learn>=1.2->llmrouter-lib==0.1.1) (3.6.0)\n", + "Requirement already satisfied: regex>=2022.1.18 in /opt/conda/lib/python3.13/site-packages (from tiktoken>=0.7.0->litellm>=1.0->llmrouter-lib==0.1.1) (2025.11.3)\n", + "Requirement already satisfied: setuptools in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (80.9.0)\n", + "Requirement already satisfied: sympy>=1.13.3 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (1.14.0)\n", + "Requirement already satisfied: networkx>=2.5.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (3.6.1)\n", + "Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (12.8.93)\n", + "Requirement already satisfied: nvidia-cuda-runtime-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (12.8.90)\n", + "Requirement already satisfied: nvidia-cuda-cupti-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (12.8.90)\n", + "Requirement already satisfied: nvidia-cudnn-cu12==9.10.2.21 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (9.10.2.21)\n", + "Requirement already satisfied: nvidia-cublas-cu12==12.8.4.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (12.8.4.1)\n", + "Requirement already satisfied: nvidia-cufft-cu12==11.3.3.83 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (11.3.3.83)\n", + "Requirement already satisfied: nvidia-curand-cu12==10.3.9.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (10.3.9.90)\n", + "Requirement already satisfied: nvidia-cusolver-cu12==11.7.3.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (11.7.3.90)\n", + "Requirement already satisfied: nvidia-cusparse-cu12==12.5.8.93 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (12.5.8.93)\n", + "Requirement already satisfied: nvidia-cusparselt-cu12==0.7.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (0.7.1)\n", + "Requirement already satisfied: nvidia-nccl-cu12==2.27.5 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (2.27.5)\n", + "Requirement already satisfied: nvidia-nvshmem-cu12==3.3.20 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (3.3.20)\n", + "Requirement already satisfied: nvidia-nvtx-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (12.8.90)\n", + "Requirement already satisfied: nvidia-nvjitlink-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (12.8.93)\n", + "Requirement already satisfied: nvidia-cufile-cu12==1.13.1.3 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (1.13.1.3)\n", + "Requirement already satisfied: triton==3.5.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.1.1) (3.5.1)\n", + "Requirement already satisfied: mpmath<1.4,>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from sympy>=1.13.3->torch>=2.0->llmrouter-lib==0.1.1) (1.3.0)\n", + "Requirement already satisfied: pyparsing in /opt/conda/lib/python3.13/site-packages (from torch-geometric>=2.3->llmrouter-lib==0.1.1) (3.3.1)\n", + "Building wheels for collected packages: llmrouter-lib\n", + " Building editable for llmrouter-lib (pyproject.toml) ... \u001b[?25ldone\n", + "\u001b[?25h Created wheel for llmrouter-lib: filename=llmrouter_lib-0.1.1-0.editable-py3-none-any.whl size=14451 sha256=119e4af13ede197b2881f7a0166c72c828683560b6a0073e246276c39d125995\n", + " Stored in directory: /tmp/pip-ephem-wheel-cache-ocl_w7jy/wheels/82/4a/fd/59c4aec93c356c380d86a88ab74bece164c0aa855d68b155e4\n", + "Successfully built llmrouter-lib\n", + "Installing collected packages: llmrouter-lib\n", + " Attempting uninstall: llmrouter-lib\n", + " Found existing installation: llmrouter-lib 0.1.1\n", + " Uninstalling llmrouter-lib-0.1.1:\n", + " Successfully uninstalled llmrouter-lib-0.1.1\n", + "Successfully installed llmrouter-lib-0.1.1\n", + "Requirement already satisfied: scikit-learn in /opt/conda/lib/python3.13/site-packages (1.8.0)\n", + "Requirement already satisfied: transformers in /opt/conda/lib/python3.13/site-packages (4.57.3)\n", + "Requirement already satisfied: torch in /opt/conda/lib/python3.13/site-packages (2.9.1)\n", + "Requirement already satisfied: numpy>=1.24.1 in /opt/conda/lib/python3.13/site-packages (from scikit-learn) (2.4.1)\n", + "Requirement already satisfied: scipy>=1.10.0 in /opt/conda/lib/python3.13/site-packages (from scikit-learn) (1.17.0)\n", + "Requirement already satisfied: joblib>=1.3.0 in /opt/conda/lib/python3.13/site-packages (from scikit-learn) (1.5.3)\n", + "Requirement already satisfied: threadpoolctl>=3.2.0 in /opt/conda/lib/python3.13/site-packages (from scikit-learn) (3.6.0)\n", + "Requirement already satisfied: filelock in /opt/conda/lib/python3.13/site-packages (from transformers) (3.20.3)\n", + "Requirement already satisfied: huggingface-hub<1.0,>=0.34.0 in /opt/conda/lib/python3.13/site-packages (from transformers) (0.36.0)\n", + "Requirement already satisfied: packaging>=20.0 in /opt/conda/lib/python3.13/site-packages (from transformers) (25.0)\n", + "Requirement already satisfied: pyyaml>=5.1 in /opt/conda/lib/python3.13/site-packages (from transformers) (6.0.3)\n", + "Requirement already satisfied: regex!=2019.12.17 in /opt/conda/lib/python3.13/site-packages (from transformers) (2025.11.3)\n", + "Requirement already satisfied: requests in /opt/conda/lib/python3.13/site-packages (from transformers) (2.32.5)\n", + "Requirement already satisfied: tokenizers<=0.23.0,>=0.22.0 in /opt/conda/lib/python3.13/site-packages (from transformers) (0.22.2)\n", + "Requirement already satisfied: safetensors>=0.4.3 in /opt/conda/lib/python3.13/site-packages (from transformers) (0.7.0)\n", + "Requirement already satisfied: tqdm>=4.27 in /opt/conda/lib/python3.13/site-packages (from transformers) (4.67.1)\n", + "Requirement already satisfied: fsspec>=2023.5.0 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (2025.10.0)\n", + "Requirement already satisfied: typing-extensions>=3.7.4.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (4.15.0)\n", + "Requirement already satisfied: hf-xet<2.0.0,>=1.1.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (1.2.0)\n", + "Requirement already satisfied: setuptools in /opt/conda/lib/python3.13/site-packages (from torch) (80.9.0)\n", + "Requirement already satisfied: sympy>=1.13.3 in /opt/conda/lib/python3.13/site-packages (from torch) (1.14.0)\n", + "Requirement already satisfied: networkx>=2.5.1 in /opt/conda/lib/python3.13/site-packages (from torch) (3.6.1)\n", + "Requirement already satisfied: jinja2 in /opt/conda/lib/python3.13/site-packages (from torch) (3.1.6)\n", + "Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.93)\n", + "Requirement already satisfied: nvidia-cuda-runtime-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-cuda-cupti-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-cudnn-cu12==9.10.2.21 in /opt/conda/lib/python3.13/site-packages (from torch) (9.10.2.21)\n", + "Requirement already satisfied: nvidia-cublas-cu12==12.8.4.1 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.4.1)\n", + "Requirement already satisfied: nvidia-cufft-cu12==11.3.3.83 in /opt/conda/lib/python3.13/site-packages (from torch) (11.3.3.83)\n", + "Requirement already satisfied: nvidia-curand-cu12==10.3.9.90 in /opt/conda/lib/python3.13/site-packages (from torch) (10.3.9.90)\n", + "Requirement already satisfied: nvidia-cusolver-cu12==11.7.3.90 in /opt/conda/lib/python3.13/site-packages (from torch) (11.7.3.90)\n", + "Requirement already satisfied: nvidia-cusparse-cu12==12.5.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.5.8.93)\n", + "Requirement already satisfied: nvidia-cusparselt-cu12==0.7.1 in /opt/conda/lib/python3.13/site-packages (from torch) (0.7.1)\n", + "Requirement already satisfied: nvidia-nccl-cu12==2.27.5 in /opt/conda/lib/python3.13/site-packages (from torch) (2.27.5)\n", + "Requirement already satisfied: nvidia-nvshmem-cu12==3.3.20 in /opt/conda/lib/python3.13/site-packages (from torch) (3.3.20)\n", + "Requirement already satisfied: nvidia-nvtx-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-nvjitlink-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.93)\n", + "Requirement already satisfied: nvidia-cufile-cu12==1.13.1.3 in /opt/conda/lib/python3.13/site-packages (from torch) (1.13.1.3)\n", + "Requirement already satisfied: triton==3.5.1 in /opt/conda/lib/python3.13/site-packages (from torch) (3.5.1)\n", + "Requirement already satisfied: mpmath<1.4,>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from sympy>=1.13.3->torch) (1.3.0)\n", + "Requirement already satisfied: MarkupSafe>=2.0 in /opt/conda/lib/python3.13/site-packages (from jinja2->torch) (3.0.3)\n", + "Requirement already satisfied: charset_normalizer<4,>=2 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (3.4.4)\n", + "Requirement already satisfied: idna<4,>=2.5 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (3.11)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (2.6.2)\n", + "Requirement already satisfied: certifi>=2017.4.17 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (2026.1.4)\n" + ] + } + ], + "source": [ + "# Install required packages (for Colab)\n", + "!git clone https://github.com/ulab-uiuc/LLMRouter.git\n", + "%cd LLMRouter\n", + "!pip install -e .\n", + "!pip install scikit-learn transformers torch" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "os.environ['OPENAI_API_KEY'] = 'your-key'\n", + "os.environ['ANTHROPIC_API_KEY'] = 'your-key'\n", + "# Or for multiple keys:\n", + "os.environ['API_KEYS'] = '[\"key1\", \"key2\"]'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Import required modules\n", + "from llmrouter.models.knnrouter import KNNRouter, KNNRouterTrainer\n", + "from llmrouter.utils import setup_environment\n", + "\n", + "setup_environment()\n", + "print(\"Environment setup complete!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Configuration\n", + "\n", + "KNNRouter uses the following configuration parameters:\n", + "\n", + "| Parameter | Description | Default |\n", + "|-----------|-------------|--------|\n", + "| `n_neighbors` | Number of neighbors (K value) | 5 |\n", + "| `weights` | Weight function: \"uniform\" or \"distance\" | \"uniform\" |\n", + "| `algorithm` | Algorithm: \"auto\", \"ball_tree\", \"kd_tree\", \"brute\" | \"auto\" |\n", + "| `metric` | Distance metric | \"minkowski\" |\n", + "| `p` | Power for Minkowski metric (1=Manhattan, 2=Euclidean) | 2 |" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Current Configuration:\n", + "==================================================\n", + "data_path:\n", + " llm_data: data/example_data/llm_candidates/default_llm.json\n", + " llm_embedding_data: data/example_data/llm_candidates/default_llm_embeddings.json\n", + " query_data_test: data/example_data/query_data/default_query_test.jsonl\n", + " query_data_train: data/example_data/query_data/default_query_train.jsonl\n", + " query_embedding_data: data/example_data/routing_data/query_embeddings_longformer.pt\n", + " routing_data_test: data/example_data/routing_data/default_routing_test_data.jsonl\n", + " routing_data_train: data/example_data/routing_data/default_routing_train_data.jsonl\n", + "hparam:\n", + " algorithm: auto\n", + " leaf_size: 30\n", + " metric: minkowski\n", + " n_jobs: -1\n", + " n_neighbors: 5\n", + " p: 2\n", + " weights: uniform\n", + "metric:\n", + " weights:\n", + " cost: 0\n", + " llm_judge: 0\n", + " performance: 1\n", + "model_path:\n", + " ini_model_path: ''\n", + " save_model_path: saved_models/knnrouter/knnrouter.pkl\n", + "\n" + ] + } + ], + "source": [ + "import yaml\n", + "\n", + "# Configuration file path\n", + "CONFIG_PATH = \"configs/model_config_train/knnrouter.yaml\"\n", + "\n", + "# Load and display configuration\n", + "with open(CONFIG_PATH, 'r') as f:\n", + " config = yaml.safe_load(f)\n", + "\n", + "print(\"Current Configuration:\")\n", + "print(\"=\" * 50)\n", + "print(yaml.dump(config, default_flow_style=False))" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "# Optionally modify configuration\n", + "# You can create a custom config for experimentation\n", + "\n", + "CUSTOM_CONFIG = {\n", + " 'data_path': {\n", + " 'query_data_train': 'data/example_data/query_data/default_query_train.jsonl',\n", + " 'query_data_test': 'data/example_data/query_data/default_query_test.jsonl',\n", + " 'query_embedding_data': 'data/example_data/routing_data/query_embeddings_longformer.pt',\n", + " 'routing_data_train': 'data/example_data/routing_data/default_routing_train_data.jsonl',\n", + " 'routing_data_test': 'data/example_data/routing_data/default_routing_test_data.jsonl',\n", + " 'llm_data': 'data/example_data/llm_candidates/default_llm.json',\n", + " 'llm_embedding_data': 'data/example_data/llm_candidates/default_llm_embeddings.json'\n", + " },\n", + " 'model_path': {\n", + " 'ini_model_path': '',\n", + " 'save_model_path': 'saved_models/knnrouter/knnrouter.pkl'\n", + " },\n", + " 'metric': {\n", + " 'weights': {\n", + " 'performance': 1,\n", + " 'cost': 0,\n", + " 'llm_judge': 0\n", + " }\n", + " },\n", + " 'hparam': {\n", + " 'n_neighbors': 5,\n", + " 'weights': 'uniform',\n", + " 'algorithm': 'auto',\n", + " 'leaf_size': 30,\n", + " 'p': 2,\n", + " 'metric': 'minkowski',\n", + " 'n_jobs': -1\n", + " }\n", + "}\n", + "\n", + "# Save custom config (optional)\n", + "# custom_config_path = 'configs/model_config_train/knnrouter_custom.yaml'\n", + "# with open(custom_config_path, 'w') as f:\n", + "# yaml.dump(CUSTOM_CONFIG, f)\n", + "# print(f\"Custom config saved to {custom_config_path}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Initialize Router" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "✅ MetaRouter initialized successfully (YAML + data loaded).\n", + "Router initialized successfully!\n", + "Number of training samples: 50544\n", + "Number of LLM candidates: 14\n", + "LLM candidates: ['qwen2.5-7b-instruct', 'codegemma-7b', 'gemma-2-9b-it', 'llama-3.1-8b-instruct', 'llama3-chatqa-1.5-8b', 'mistral-7b-instruct-v0.3', 'llama-3.3-nemotron-super-49b-v1', 'llama-3.1-nemotron-51b-instruct', 'llama3-chatqa-1.5-70b', 'llama3-70b-instruct', 'mixtral-8x7b-instruct-v0.1', 'mixtral-8x22b-instruct-v0.1', 'palmyra-creative-122b', 'mistral-nemo-12b-instruct']\n" + ] + } + ], + "source": [ + "# Initialize KNNRouter with configuration\n", + "router = KNNRouter(yaml_path=CONFIG_PATH)\n", + "\n", + "print(\"Router initialized successfully!\")\n", + "print(f\"Number of training samples: {len(router.routing_data_train)}\")\n", + "print(f\"Number of LLM candidates: {len(router.llm_data)}\")\n", + "print(f\"LLM candidates: {list(router.llm_data.keys())}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "KNN Model Parameters:\n", + "{'algorithm': 'auto', 'leaf_size': 30, 'metric': 'minkowski', 'metric_params': None, 'n_jobs': -1, 'n_neighbors': 5, 'p': 2, 'weights': 'uniform'}\n" + ] + } + ], + "source": [ + "# Inspect the KNN model configuration\n", + "print(\"KNN Model Parameters:\")\n", + "print(router.knn_model.get_params())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Data Exploration" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Training Data Shape: (50544, 17)\n", + "\n", + "Columns: ['task_name', 'query', 'ground_truth', 'metric', 'choices', 'task_id', 'model_name', 'response', 'token_num', 'input_tokens', 'output_tokens', 'response_time', 'api_key_used', 'performance', 'embedding_id', 'user_id', 'fig_id']\n", + "\n", + "Sample data:\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
task_namequeryground_truthmetricchoicestask_idmodel_nameresponsetoken_numinput_tokensoutput_tokensresponse_timeapi_key_usedperformanceembedding_iduser_idfig_id
0agentverse-logicgridQ: There are 3 houses in a row, numbered 1 on ...Cem_mc{'text': ['1', '2', '3'], 'labels': ['A', 'B',...Nonellama3-chatqa-1.5-8bB. labels45344941.786449rivTkKeBPm0.061NoneNone
1agentverse-logicgridQ: There are 4 houses in a row, numbered 1 on ...Bem_mc{'text': ['1', '2', '3', '4'], 'labels': ['A',...Nonellama3-chatqa-1.5-8bB. labels52251841.80800807iTpWkg530.0144NoneNone
2agentverse-logicgridQ: There are 3 houses in a row, numbered 1 on ...Cem_mc{'text': ['1', '2', '3'], 'labels': ['A', 'B',...Noneqwen2.5-7b-instructC41341213.261811rivTkKeBPm0.061NoneNone
3agentverse-logicgridQ: There are 4 houses in a row, numbered 1 on ...Bem_mc{'text': ['1', '2', '3', '4'], 'labels': ['A',...Noneqwen2.5-7b-instructD48448313.276698oE9VxHtPRB0.0144NoneNone
4agentverse-logicgridQ: There are 3 houses in a row, numbered 1 on ...Aem_mc{'text': ['1', '2', '3'], 'labels': ['A', 'B',...Noneqwen2.5-7b-instructC38438313.278904hgicYjBYxR0.075NoneNone
\n", + "
" + ], + "text/plain": [ + " task_name query \\\n", + "0 agentverse-logicgrid Q: There are 3 houses in a row, numbered 1 on ... \n", + "1 agentverse-logicgrid Q: There are 4 houses in a row, numbered 1 on ... \n", + "2 agentverse-logicgrid Q: There are 3 houses in a row, numbered 1 on ... \n", + "3 agentverse-logicgrid Q: There are 4 houses in a row, numbered 1 on ... \n", + "4 agentverse-logicgrid Q: There are 3 houses in a row, numbered 1 on ... \n", + "\n", + " ground_truth metric choices \\\n", + "0 C em_mc {'text': ['1', '2', '3'], 'labels': ['A', 'B',... \n", + "1 B em_mc {'text': ['1', '2', '3', '4'], 'labels': ['A',... \n", + "2 C em_mc {'text': ['1', '2', '3'], 'labels': ['A', 'B',... \n", + "3 B em_mc {'text': ['1', '2', '3', '4'], 'labels': ['A',... \n", + "4 A em_mc {'text': ['1', '2', '3'], 'labels': ['A', 'B',... \n", + "\n", + " task_id model_name response token_num input_tokens \\\n", + "0 None llama3-chatqa-1.5-8b B. labels 453 449 \n", + "1 None llama3-chatqa-1.5-8b B. labels 522 518 \n", + "2 None qwen2.5-7b-instruct C 413 412 \n", + "3 None qwen2.5-7b-instruct D 484 483 \n", + "4 None qwen2.5-7b-instruct C 384 383 \n", + "\n", + " output_tokens response_time api_key_used performance embedding_id \\\n", + "0 4 1.786449 rivTkKeBPm 0.0 61 \n", + "1 4 1.808008 07iTpWkg53 0.0 144 \n", + "2 1 3.261811 rivTkKeBPm 0.0 61 \n", + "3 1 3.276698 oE9VxHtPRB 0.0 144 \n", + "4 1 3.278904 hgicYjBYxR 0.0 75 \n", + "\n", + " user_id fig_id \n", + "0 None None \n", + "1 None None \n", + "2 None None \n", + "3 None None \n", + "4 None None " + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import pandas as pd\n", + "\n", + "# Explore training data\n", + "train_df = router.routing_data_train\n", + "print(\"Training Data Shape:\", train_df.shape)\n", + "print(\"\\nColumns:\", list(train_df.columns))\n", + "print(\"\\nSample data:\")\n", + "train_df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Performance Statistics:\n", + "count 50544.000000\n", + "mean 0.428694\n", + "std 0.489734\n", + "min 0.000000\n", + "25% 0.000000\n", + "50% 0.000000\n", + "75% 1.000000\n", + "max 1.000000\n", + "Name: performance, dtype: float64\n", + "\n", + "Performance by Model:\n", + "model_name\n", + "llama-3.1-nemotron-51b-instruct 0.621506\n", + "llama-3.3-nemotron-super-49b-v1 0.578728\n", + "llama-3.1-8b-instruct 0.560574\n", + "gemma-2-9b-it 0.534966\n", + "qwen2.5-7b-instruct 0.519796\n", + "mistral-7b-instruct-v0.3 0.371144\n", + "codegemma-7b 0.302852\n", + "llama3-chatqa-1.5-70b 0.193845\n", + "llama3-chatqa-1.5-8b 0.174838\n", + "Name: performance, dtype: float64\n" + ] + } + ], + "source": [ + "# Analyze performance distribution\n", + "print(\"Performance Statistics:\")\n", + "print(train_df['performance'].describe())\n", + "\n", + "print(\"\\nPerformance by Model:\")\n", + "print(train_df.groupby('model_name')['performance'].mean().sort_values(ascending=False))" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABW0AAAHqCAYAAAB/bWzAAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAA0jpJREFUeJzs3XlcTun/P/DX3b5vtJC0iFRIsmfJ8pFCY4kIFWEsDUmWZsgSylL23YxC9m2MfTeksUR2ERIjWyiJSvf9+6Nf5+tWUsnc0ev5eNyPT+c613Wd9zn1Gee87+tcl0gikUhAREREREREREREROWCnKwDICIiIiIiIiIiIqL/w6QtERERERERERERUTnCpC0RERERERERERFROcKkLREREREREREREVE5wqQtERERERERERERUTnCpC0RERERERERERFROcKkLREREREREREREVE5wqQtERERERERERERUTnCpC0RERERERERERFROcKkLREVas6cObCwsIC8vDzq168v63AqHCcnJzg5Of0nxxKJRJgyZYqwPWXKFIhEIrx48eI/Ob6ZmRl8fHz+k2MRERERERXlzp076NChA7S1tSESibBr1y5Zh/TdOnHiBEQiEbZt2ybrUL6JpKQkiEQiREZGlrht/rU5ceJEmcdFPw4mbYm+E5GRkRCJRMJHRUUFtWrVgp+fH54+fVqmxzp06BDGjRsHR0dHrFmzBjNnzizT/isaHx8fqd+dhoYGLCws4O7uju3bt0MsFpfJcc6cOYMpU6bg9evXZdJfWSrPsRERERGVB0uXLoVIJEKTJk1kHUq5Y2ZmJnU/bWBggJYtW2Lnzp1lfixvb29cvXoVM2bMwLp169CwYcMyPwaVrfxBJ3Jycnj48GGB/enp6VBVVYVIJIKfn58MIiQqHQVZB0BEJTNt2jSYm5vj/fv3OH36NJYtW4Z9+/bh2rVrUFNTK5NjHDt2DHJycvj999+hpKRUJn1WdMrKyli9ejUA4N27d3jw4AH++usvuLu7w8nJCX/++Se0tLSE+ocOHSrxMc6cOYOpU6fCx8cHOjo6xW737t07KCh8238OiootISEBcnL8DpGIiIgqtujoaJiZmeHcuXNITEyEpaWlrEMqV+rXr48xY8YAAB4/fowVK1age/fuWLZsGYYOHVomx3j37h1iY2Px22+/Mbn3HVJWVsbGjRsxbtw4qfIdO3bIKCKir8OnZKLvjIuLC/r164dBgwYhMjIS/v7+uH//Pv7888+v7jszMxMA8OzZM6iqqpZZwlYikeDdu3dl0tf3SkFBAf369UO/fv0wePBgTJ8+HZcvX0ZoaChOnDiBwYMHS9VXUlL6pglzsViM9+/fAwBUVFS+edK2KMrKylBUVJTZ8YmIiIhk7f79+zhz5gwiIiKgr6+P6Ojo/zyGj+8PyyNjY2PhfnrcuHGIiYmBuro65s2b99V9v3//HmKxGM+fPweAEg2A+JK3b9+WWV9UNFdXV2zcuLFA+YYNG9CpUycZRET0dZi0JfrOtW3bFkDejV6+9evXw8HBAaqqqtDT00Pv3r0LvCbi5OSEOnXqIC4uDq1atYKamhp+/fVXiEQirFmzBm/fvhVeP8qfo+fDhw8ICQlBjRo1oKysDDMzM/z666/IysqS6tvMzAydO3fGwYMH0bBhQ6iqqmLFihXCvD1btmzB1KlTYWxsDE1NTbi7uyMtLQ1ZWVnw9/eHgYEBNDQ0MGDAgAJ9r1mzBm3btoWBgQGUlZVhY2ODZcuWFbgu+TGcPn0ajRs3hoqKCiwsLLB27doCdV+/fo3Ro0fDzMwMysrKqFatGry8vKTmdM3KysLkyZNhaWkJZWVlmJiYYNy4cQXiK6kJEyagQ4cO2Lp1K27fvi2UFzan7aJFi2Braws1NTXo6uqiYcOG2LBhA4C8V4LGjh0LADA3Nxd+d0lJSQAgvAoUHR0NW1tbKCsr48CBA8K+j+e0zffixQv06tULWlpaqFSpEkaNGiV1I1/UHE4f9/ml2Aqb0/bevXvo2bMn9PT0oKamhqZNm2Lv3r1SdT7+e5oxYwaqVasGFRUVtGvXDomJiZ+95kRERETlTXR0NHR1ddGpUye4u7tLJW1zcnKgp6eHAQMGFGiXnp4OFRUVBAYGCmXFvW8t6v5w7ty5aN68OSpVqgRVVVU4ODgUOi/pu3fvMHLkSFSuXBmamppwc3PDv//+W+j95b///ouBAwfC0NAQysrKsLW1xR9//FHqa2ZkZARra2up56DiHCP/HnLTpk2YOHEijI2NoaamhoCAAJiamgIAxo4dC5FIBDMzM6HdpUuX4OLiAi0tLWhoaKBdu3b4559/pPrOn9Lu5MmTGD58OAwMDFCtWjUA//f8deXKFbRu3RpqamqwtLQUruvJkyfRpEkTqKqqwsrKCkeOHJHq+8GDBxg+fDisrKygqqqKSpUqoWfPnsI99acxxMTEICAgAPr6+lBXV0e3bt2EpPTH9u/fj9atW0NTUxNaWlpo1KiR8IyR7+zZs+jYsSO0tbWhpqaG1q1bIyYmphi/pTy5ubn49ddfYWRkBHV1dbi5uUk9n06ePBmKioqFxjdkyBDo6OgU6wsFT09PxMfH49atW0LZkydPcOzYMXh6ehba5tmzZ/D19YWhoSFUVFRgZ2eHqKioAvVev34NHx8faGtrQ0dHB97e3p+d+u3WrVtwd3eHnp4eVFRU0LBhQ+zevfuL8RN9itMjEH3n7t69CwCoVKkSAGDGjBmYNGkSevXqhUGDBuH58+dYtGgRWrVqhUuXLkl9a5yamgoXFxf07t0b/fr1g6GhIRo2bIiVK1fi3Llzwuv8zZs3BwAMGjQIUVFRcHd3x5gxY3D27FmEhobi5s2bBeaTSkhIQJ8+ffDzzz9j8ODBsLKyEvaFhoZCVVUVEyZMQGJiIhYtWgRFRUXIycnh1atXmDJlCv755x9ERkbC3NwcwcHBQttly5bB1tYWbm5uUFBQwF9//YXhw4dDLBZjxIgRUjEkJibC3d0dvr6+8Pb2xh9//AEfHx84ODjA1tYWAJCRkYGWLVvi5s2bGDhwIBo0aIAXL15g9+7dePToESpXrgyxWAw3NzecPn0aQ4YMgbW1Na5evYp58+bh9u3bX704Qf/+/XHo0CEcPnwYtWrVKrTOqlWrMHLkSLi7uwvJ0ytXruDs2bPw9PRE9+7dcfv2bWzcuBHz5s1D5cqVAQD6+vpCH8eOHcOWLVvg5+eHypUrS92EFqZXr14wMzNDaGgo/vnnHyxcuBCvXr0qNPFdlOLE9rGnT5+iefPmyMzMxMiRI1GpUiVERUXBzc0N27ZtQ7du3aTqh4WFQU5ODoGBgUhLS8Ps2bPRt29fnD17tkRxEhEREclKdHQ0unfvDiUlJfTp0wfLli3D+fPn0ahRIygqKqJbt27YsWMHVqxYIfU21q5du5CVlYXevXsDQInvWz93f7hgwQK4ubmhb9++yM7OxqZNm9CzZ0/s2bNHasSij48PtmzZgv79+6Np06Y4efJkoSManz59iqZNmwqJYn19fezfvx++vr5IT0+Hv79/ia9ZTk4OHj58KDwHlfQYISEhUFJSQmBgILKysuDq6gozMzOMHj0affr0gaurKzQ0NAAA169fR8uWLaGlpYVx48ZBUVERK1asgJOTk5Bs/djw4cOhr6+P4OBgqZG2r169QufOndG7d2/07NkTy5YtQ+/evREdHQ1/f38MHToUnp6emDNnDtzd3fHw4UNoamoCAM6fP48zZ86gd+/eqFatGpKSkrBs2TI4OTnhxo0bBabK++WXX6Crq4vJkycjKSkJ8+fPh5+fHzZv3izUiYyMxMCBA2Fra4ugoCDo6Ojg0qVLOHDggJDkPHbsGFxcXODg4IDJkydDTk5OGEhz6tQpNG7c+Iu/qxkzZkAkEmH8+PF49uwZ5s+fj/bt2yM+Ph6qqqro378/pk2bhs2bN0tNS5GdnY1t27ahR48eUFFR+eJxWrVqhWrVqmHDhg2YNm0aAGDz5s3Q0NAo9O/y3bt3cHJyQmJiIvz8/GBubo6tW7fCx8cHr1+/xqhRowDkvTn6008/4fTp0xg6dCisra2xc+dOeHt7F+jz+vXrcHR0hLGxMSZMmAB1dXVs2bIFXbt2xfbt2ws8yxAVSUJE34U1a9ZIAEiOHDkief78ueThw4eSTZs2SSpVqiRRVVWVPHr0SJKUlCSRl5eXzJgxQ6rt1atXJQoKClLlrVu3lgCQLF++vMCxvL29Jerq6lJl8fHxEgCSQYMGSZUHBgZKAEiOHTsmlJmamkoASA4cOCBV9/jx4xIAkjp16kiys7OF8j59+khEIpHExcVFqn6zZs0kpqamUmWZmZkF4nV2dpZYWFhIleXH8Pfffwtlz549kygrK0vGjBkjlAUHB0sASHbs2FGgX7FYLJFIJJJ169ZJ5OTkJKdOnZLav3z5cgkASUxMTIG2Hyvsen7s0qVLEgCS0aNHC2WtW7eWtG7dWtj+6aefJLa2tkUeZ86cORIAkvv37xfYB0AiJycnuX79eqH7Jk+eLGxPnjxZAkDi5uYmVW/48OESAJLLly9LJBKJ5P79+xIAkjVr1nyxz6JiMzU1lXh7ewvb/v7+EgBS1/vNmzcSc3NziZmZmSQ3N1cikfzf35O1tbUkKytLqLtgwQIJAMnVq1cLHIuIiIiovLlw4YIEgOTw4cMSiSTvHrRatWqSUaNGCXUOHjwoASD566+/pNq6urpK3QeX5L61qPvDT++5s7OzJXXq1JG0bdtWKIuLi5MAkPj7+0vV9fHxKXAv6OvrK6lSpYrkxYsXUnV79+4t0dbWLvQe/2OmpqaSDh06SJ4/fy55/vy55PLly5LevXtLAEh++eWXEh0j/x7SwsKiwHHz72/nzJkjVd61a1eJkpKS5O7du0LZ48ePJZqampJWrVoJZfnPbC1atJB8+PBBqo/8568NGzYIZbdu3RJ+D//8849Qnv/7/vg+u7BrFBsbKwEgWbt2bYEY2rdvLzzPSCQSyejRoyXy8vKS169fSyQSieT169cSTU1NSZMmTSTv3r2T6je/nVgsltSsWVPi7Ows1VdmZqbE3Nxc8r///a9ATB/Lv9bGxsaS9PR0oXzLli0SAJIFCxYIZc2aNZM0adJEqv2OHTskACTHjx8v8jj5zy/Pnz+XBAYGSiwtLYV9jRo1kgwYMEAikeT9zY8YMULYN3/+fAkAyfr164Wy7OxsSbNmzSQaGhpCzLt27ZIAkMyePVuo9+HDB0nLli0L/J7atWsnqVu3ruT9+/dCmVgsljRv3lxSs2bNAtfmS+dGFRunRyD6zrRv3x76+vowMTFB7969oaGhgZ07d8LY2Bg7duyAWCxGr1698OLFC+FjZGSEmjVr4vjx41J9KSsrF/qaVWH27dsHAAgICJAqz18M4NNX183NzeHs7FxoX15eXlJzmDZp0gQSiQQDBw6UqtekSRM8fPgQHz58EMpUVVWFn9PS0vDixQu0bt0a9+7dQ1pamlR7GxsbtGzZUtjW19eHlZUV7t27J5Rt374ddnZ2hX7jKRKJAABbt26FtbU1ateuLXVd86em+PS6llT+N/hv3rz5bB0dHR08evQI58+fL/VxWrduDRsbm2LX/3Tk8i+//ALg//4WvpV9+/ahcePGaNGihVCmoaGBIUOGICkpCTdu3JCqP2DAAKkRJ/m/849/z0RERETlVXR0NAwNDdGmTRsAefegHh4e2LRpE3JzcwHkTYlWuXJlqVGSr169wuHDh+Hh4SGUlfS+9XP3hx/fc7969QppaWlo2bIlLl68KJTnT6UwfPhwqbb594z5JBIJtm/fji5dukAikUjF5ezsjLS0NKl+P+fQoUPQ19eHvr4+7OzssHXrVvTv3x+zZs0q1TG8vb2lzvNzcnNzcejQIXTt2hUWFhZCeZUqVeDp6YnTp08jPT1dqs3gwYMhLy9foC8NDQ1hVDQAWFlZQUdHB9bW1lKjdfN//vh+9uNYc3JykJqaCktLS+jo6BR6/YYMGSI8zwB598i5ubl48OABAODw4cN48+YNJkyYUGAUa367+Ph43LlzB56enkhNTRWu6du3b9GuXTv8/fffEIvFRVy9PF5eXsKIYQBwd3dHlSpVpJ4rvLy8cPbsWeFNUiDv/xsmJiZo3br1F4+Rz9PTE4mJiTh//rzwv5+bGmHfvn0wMjJCnz59hDJFRUWMHDkSGRkZOHnypFBPQUEBw4YNE+rJy8sX+Ft/+fIljh07hl69euHNmzfC9UpNTYWzszPu3LmDf//9t9jnQsTpEYi+M0uWLEGtWrWgoKAAQ0NDWFlZQU4u7/uXO3fuQCKRoGbNmoW2/XSxJ2Nj42IvdvXgwQPIyckVWMXWyMgIOjo6wj/++czNzT/bV/Xq1aW2tbW1AQAmJiYFysViMdLS0oTXnmJiYjB58mTExsYKC6flS0tLE/oq7DgAoKuri1evXgnbd+/eRY8ePT4bK5B3XW/evPnZ1/mfPXtWZPsvycjIAACpG5lPjR8/HkeOHEHjxo1haWmJDh06wNPTE46OjsU+TlG/k8J8+ndUo0YNyMnJFZg3q6w9ePCgwCtmAGBtbS3sr1OnjlD+6e9ZV1cXAKR+z0RERETlUW5uLjZt2oQ2bdpIzc3apEkThIeH4+jRo+jQoQMUFBTQo0cPbNiwAVlZWVBWVsaOHTuQk5MjlbQt6X3r5+4P9+zZg+nTpyM+Pl5qLtyPk4D5zwef9vHp88Lz58/x+vVrrFy5EitXrixWXIVp0qQJpk+fDpFIBDU1NVhbWwtTvz179qzExyjuvfHz58+RmZkpNd1bPmtra4jFYjx8+FCYfq2ovqtVqyZ1DYG8Z57CnoMA6fvZd+/eITQ0FGvWrMG///4LiUQi7Pt08Arw5Xvk/OTox/fVn7pz5w4AFDoNwMfHzu/7cz59rhCJRLC0tJR6rvDw8IC/vz+io6MRHByMtLQ07NmzB6NHjy5wzYpib2+P2rVrY8OGDdDR0YGRkZHwpcWnHjx4gJo1awrP0/k+fu7I/98qVaoIg23yffo3kZiYCIlEgkmTJmHSpEmFHvPZs2cwNjYu9vlQxcakLdF3pnHjxmjYsGGh+8RiMUQiEfbv3//Zb3Y/Vpxvlj9V3H8wi+q7sNiKKs+/Ibl79y7atWuH2rVrIyIiAiYmJlBSUsK+ffswb968At/yfqm/4hKLxahbty4iIiIK3f/pTVZJXbt2DUDBG9yPWVtbIyEhAXv27MGBAwewfft2LF26FMHBwZg6dWqxjlOa3/fHPv3df+5vIX9EyH+lrH7PRERERP+1Y8eOISUlBZs2bcKmTZsK7I+OjkaHDh0AAL1798aKFSuwf/9+dO3aFVu2bEHt2rVhZ2cn1C/pfWth94enTp2Cm5sbWrVqhaVLl6JKlSpQVFTEmjVrCixQVRz59+j9+vX7bPKvXr16X+yncuXKaN++fZkd42vvjYvyub5L+xwE5I1gXrNmDfz9/dGsWTNoa2tDJBKhd+/ehY52LYt75Px+58yZg/r16xda59NnzNLS1dVF586dhaTttm3bkJWVhX79+pW4L09PTyxbtgyamprw8PAokJT9VvKvV2Bg4GffOi3qmY/oU0zaEv1AatSoAYlEAnNz888uaFVapqamEIvFuHPnjvDNI5A34f/r16+FlVa/pb/++gtZWVnYvXu31DfHXzM9QY0aNYSkaVF1Ll++jHbt2pXoW97iWrduHUQiEf73v/8VWU9dXR0eHh7w8PBAdnY2unfvjhkzZiAoKAgqKiplHtudO3ekRgkkJiZCLBYLC1Tkf6P+6aqpn466Boqf7Afy/tYSEhIKlOevAvtf/K0RERER/Reio6NhYGCAJUuWFNi3Y8cO7Ny5E8uXL4eqqipatWqFKlWqYPPmzWjRogWOHTuG3377TapNWdy3bt++HSoqKjh48CCUlZWF8jVr1kjVy38+uH//vtRIysTERKl6+vr60NTURG5u7meTrl/rWx5DX18fampqn70/lZOT++pBHMWxbds2eHt7Izw8XCh7//59gXvx4qpRowaAvAEkn0sk5tfR0tL6quuaP2I3n0QiQWJiYoFEupeXF3766SecP38e0dHRsLe3lxrBXFyenp4IDg5GSkoK1q1b99l6pqamuHLlCsRisVRi99PnDlNTUxw9ehQZGRlSSepP/ybyp89QVFT8Zn/rVLFwTluiH0j37t0hLy+PqVOnFvgGVSKRIDU1tdR9u7q6AgDmz58vVZ7/LX5hq3GWtfxviz99FejTG8iS6NGjBy5fvoydO3cW2Jd/nF69euHff//FqlWrCtR59+6d1IqwJRUWFoZDhw7Bw8Pjs9NaACjwu1NSUoKNjQ0kEglycnIA5CV1gYJJ1NL69OFh0aJFAAAXFxcAeTdvlStXxt9//y1Vb+nSpQX6Kklsrq6uOHfuHGJjY4Wyt2/fYuXKlTAzMyvRvLxERERE5dW7d++wY8cOdO7cGe7u7gU+fn5+ePPmDXbv3g0AkJOTg7u7O/766y+sW7cOHz58kJoaASib+1Z5eXmIRCKpt6eSkpKwa9cuqXr5Iwk/vffLv2f8uL8ePXpg+/bthQ6WeP78+RdjKk7M3+oY8vLy6NChA/7880+p1/mfPn2KDRs2oEWLFtDS0ip1/yWJ49NnvEWLFpX6LbcOHTpAU1MToaGheP/+vdS+/OM4ODigRo0amDt3rjCl28eKe13Xrl0rtX7Htm3bkJKSIjxX5HNxcUHlypUxa9YsnDx5slSjbIG8ZPP8+fMRGhqKxo0bf7aeq6srnjx5IjVX9IcPH7Bo0SJoaGgIc+m6urriw4cPWLZsmVAvNze3wN+6gYEBnJycsGLFCqSkpBQ4Xln8rVPFwpG2RD+QGjVqYPr06QgKCkJSUhK6du0KTU1N3L9/Hzt37sSQIUMQGBhYqr7t7Ozg7e2NlStX4vXr12jdujXOnTuHqKgodO3aVVg44Vvq0KEDlJSU0KVLF/z888/IyMjAqlWrYGBgUOg/isUxduxYbNu2DT179sTAgQPh4OCAly9fYvfu3Vi+fDns7OzQv39/bNmyBUOHDsXx48fh6OiI3Nxc3Lp1C1u2bMHBgwc/O2VFvg8fPmD9+vUA8r4Rf/DgAXbv3o0rV66gTZs2n5176+NzNzIygqOjIwwNDXHz5k0sXrwYnTp1EubCdXBwAAD89ttv6N27NxQVFdGlSxchYVpS9+/fh5ubGzp27IjY2FisX78enp6eUq/gDRo0CGFhYRg0aBAaNmyIv//+G7dv3y7QV0limzBhAjZu3AgXFxeMHDkSenp6iIqKwv3797F9+/b/7PUmIiIiom9p9+7dePPmDdzc3Ard37RpU+jr6yM6OlpIznp4eGDRokWYPHky6tatK/UGHIAyuW/t1KkTIiIi0LFjR3h6euLZs2dYsmQJLC0tceXKFaGeg4MDevTogfnz5yM1NRVNmzbFyZMnhXvBj0f6hoWF4fjx42jSpAkGDx4MGxsbvHz5EhcvXsSRI0fw8uXLUl3Dj33LY0yfPh2HDx9GixYtMHz4cCgoKGDFihXIysrC7Nmzvzr24ujcuTPWrVsHbW1t2NjYIDY2FkeOHBHW/igpLS0tzJs3D4MGDUKjRo3g6ekJXV1dXL58GZmZmYiKioKcnBxWr14NFxcX2NraYsCAATA2Nsa///6L48ePQ0tLC3/99dcXj6Wnp4cWLVpgwIABePr0KebPnw9LS0sMHjxYqp6ioiJ69+6NxYsXQ15eXmqBsJIaNWrUF+sMGTIEK1asgI+PD+Li4mBmZoZt27YhJiYG8+fPF56zunTpAkdHR0yYMAFJSUmwsbHBjh07Cp1LeMmSJWjRogXq1q2LwYMHw8LCAk+fPkVsbCwePXqEy5cvl/qcqOJh0pboBzNhwgTUqlUL8+bNE+Y6NTExQYcOHT57Q1hcq1evhoWFBSIjI7Fz504YGRkhKCgIkydPLovQv8jKygrbtm3DxIkTERgYCCMjIwwbNgz6+voYOHBgqfrU0NDAqVOnMHnyZOzcuRNRUVEwMDBAu3btUK1aNQB5oxp27dqFefPmYe3atdi5cyfU1NRgYWGBUaNGFWsqiqysLPTv3x8AoKamBgMDAzg4OCA4OBjdunX7YiLy559/RnR0NCIiIpCRkYFq1aph5MiRmDhxolCnUaNGCAkJwfLly3HgwAHhdbXSJm03b96M4OBgTJgwAQoKCvDz88OcOXOk6gQHB+P58+fYtm0btmzZAhcXF+zfvx8GBgZS9UoSm6GhIc6cOYPx48dj0aJFeP/+PerVq4e//vrrPxnRTURERPRfiI6OhoqKymenyJKTk0OnTp0QHR2N1NRUVKpUCc2bN4eJiQkePnxYYJRtfpuvvW9t27Ytfv/9d4SFhcHf3x/m5uaYNWsWkpKSpJK2QN4ISiMjI2zcuBE7d+5E+/btsXnzZlhZWUFFRUWoZ2hoiHPnzmHatGnYsWMHli5dikqVKsHW1hazZs0q4ZUr3Lc8hq2tLU6dOoWgoCCEhoZCLBajSZMmWL9+faEL6H4LCxYsgLy8PKKjo/H+/Xs4OjriyJEjn507tTh8fX1hYGCAsLAwhISEQFFREbVr18bo0aOFOk5OToiNjUVISAgWL16MjIwMGBkZoUmTJvj555+LdZxff/0VV65cQWhoKN68eYN27dph6dKlUFNTK1DXy8sLixcvRrt27VClSpVSn1txqKqq4sSJE5gwYQKioqKQnp4OKysrrFmzBj4+PkI9OTk57N69G/7+/li/fj1EIhHc3NwQHh4Oe3t7qT5tbGxw4cIFTJ06FZGRkUhNTYWBgQHs7e0RHBz8Tc+HfjwiCVdqISIiIiIiIqIyEB8fD3t7e6xfvx59+/aVdTj0nbl8+TLq16+PtWvXCoNeiCoqvmNKRERERERERCX27t27AmXz58+HnJwcWrVqJYOI6Hu3atUqaGhooHv37rIOhUjmOD0CEREREREREZXY7NmzERcXhzZt2kBBQQH79+/H/v37MWTIEJiYmMg6PPqO/PXXX7hx4wZWrlwJPz+/Uk/xRvQj4fQIRERERERERFRihw8fxtSpU3Hjxg1kZGSgevXq6N+/P3777TcoKHCMGBWfmZkZnj59CmdnZ6xbt05YBIyoImPSloiIiIiIiIiIiKgc4Zy2REREREREREREROUIk7ZERERERERERERE5QgnmSkjYrEYjx8/hqamJkQikazDISIiom9IIpHgzZs3qFq1KuTk+B04EX09Pk8QERFVDMV9lmDStow8fvyYq2MSERFVMA8fPkS1atVkHQYR/QD4PEFERFSxfOlZgknbMpK/suHDhw+hpaUl42iIiIjoW0pPT4eJiQlXNiaiMsPnCSIiooqhuM8STNqWkfxXmLS0tHiTRUREVEHwFWYiKit8niAiIqpYvvQswUnYiIiIiIiIiIiIiMoRJm2JiIiIiIiIiIiIyhEmbYmIiIiIiIiIiIjKESZtiYiIiIiIiIiIiMoRJm2JiIiIiIiIiIiIyhEmbYmIiIiIiIiIiIjKESZtiYiIiIiIiIiIiMoRJm2JiIiIiIiIiIiIyhEmbYmIiIiIiIiIiIjKEQVZB0BERERERER56kw+CDllNVmHQUREVKElhXWSdQgcaUtERERERERERERUnjBpS0RERERERERERFSOMGlLREREREREREREVI4waUtERERERERERERUjjBpS0RERPQDc3Jygr+/PwDAzMwM8+fPl2k8VHFMmTIF9evXL3G7j/9miYiIiCoqJm2JiIiISOYSEhLQpk0bGBoaQkVFBRYWFpg4cSJycnKKbDdy5Eg4ODhAWVm5VAnC8ubEiRMQiUR4/fq1TOMwMzODSCSS+oSFhQn7379/Dx8fH9StWxcKCgro2rVrmR17x44dCAkJKbP+RCIRdu3aVWb9FSUyMhI6Ojr/ybGIiIjox6Yg6wCoeJKTk/HixYtv0nflypVRvXr1b9I3ERERUXEoKirCy8sLDRo0gI6ODi5fvozBgwdDLBZj5syZRbYdOHAgzp49iytXrvxH0cpednY2lJSUvukxpk2bhsGDBwvbmpqaws+5ublQVVXFyJEjsX379jI9rp6eXpn2Vxz/xfUkIiIiKgmOtP0OJCcnw6q2NRwcHL7Jx6q2NZKTk2V9mkRERPQfi4iIQN26daGurg4TExMMHz4cGRkZwv78UYN79uyBlZUV1NTU4O7ujszMTERFRcHMzAy6uroYOXIkcnNzhXbr1q1Dw4YNoampCSMjI3h6euLZs2dFxmJhYYEBAwbAzs4OpqamcHNzQ9++fXHq1Kki2y1cuBAjRoyAhYVFsc87/7X9devWwczMDNra2ujduzfevHkj1BGLxQgNDYW5uTlUVVVhZ2eHbdu2CfvzR8QePHgQ9vb2UFVVRdu2bfHs2TPs378f1tbW0NLSgqenJzIzM4V2WVlZGDlyJAwMDKCiooIWLVrg/PnzAICkpCS0adMGAKCrqwuRSAQfHx8AeVMG+Pn5wd/fH5UrV4azszMA4OTJk2jcuDGUlZVRpUoVTJgwAR8+fBCO5+TkhJEjR2LcuHHQ09ODkZERpkyZUqzrlP/7y/+oq6sL+9TV1bFs2TIMHjwYRkZGRfazYsUKmJiYQE1NDb169UJaWlqR9T+dHsHMzAwzZ87EwIEDoampierVq2PlypXC/uzsbPj5+aFKlSpQUVGBqakpQkNDhbYA0K1bN4hEImE7/29g9erVMDc3h4qKilD/0ylE6tevL3XNXr9+jZ9//lkYFV6nTh3s2bMHJ06cwIABA5CWliaMTi7utSYiIiL6FEfafgdevHiB9+8yUanzGChWMinTvnNSHyJ1TzhevHjB0bZEREQVjJycHBYuXAhzc3Pcu3cPw4cPx7hx47B06VKhTmZmJhYuXIhNmzbhzZs36N69O7p16wYdHR3s27cP9+7dQ48ePeDo6AgPDw8AQE5ODkJCQmBlZYVnz54hICAAPj4+2LdvX7FjS0xMxIEDB9C9e/cyP28AuHv3Lnbt2oU9e/bg1atX6NWrF8LCwjBjxgwAQGhoKNavX4/ly5ejZs2a+Pvvv9GvXz/o6+ujdevWQj9TpkzB4sWLhYRkr169oKysjA0bNiAjIwPdunXDokWLMH78eADAuHHjsH37dkRFRcHU1BSzZ8+Gs7MzEhMTYWJigu3bt6NHjx5ISEiAlpYWVFVVhWNFRUVh2LBhiImJAQD8+++/cHV1hY+PD9auXYtbt25h8ODBUFFRkUoWRkVFISAgAGfPnkVsbCx8fHzg6OiI//3vf0Veo7CwMISEhKB69erw9PTE6NGjoaBQsseHxMREbNmyBX/99RfS09Ph6+uL4cOHIzo6ukT9hIeHIyQkBL/++iu2bduGYcOGoXXr1rCyssLChQuxe/dubNmyBdWrV8fDhw/x8OFDAMD58+dhYGCANWvWoGPHjpCXl5eKbfv27dixY4dUeVHEYjFcXFzw5s0brF+/HjVq1MCNGzcgLy+P5s2bY/78+QgODkZCQgIAQENDo0TnSURERJSPSdvviGIlEygbWco6DCIiIvpBfDqacfr06Rg6dKhU0jYnJwfLli1DjRo1AADu7u5Yt24dnj59Cg0NDdjY2KBNmzY4fvy4kLQdOHCg0N7CwgILFy5Eo0aNkJGR8cUkVvPmzXHx4kVkZWVhyJAhmDZtWhme8f8Ri8WIjIwUXvnv378/jh49ihkzZiArKwszZ87EkSNH0KxZM+E8Tp8+jRUrVkglbadPnw5HR0cAgK+vL4KCgnD37l1h5K+7uzuOHz+O8ePH4+3bt1i2bBkiIyPh4uICAFi1ahUOHz6M33//HWPHjhWmBjAwMCgwN2rNmjUxe/ZsYfu3336DiYkJFi9eDJFIhNq1a+Px48cYP348goODISeX91JdvXr1MHnyZKGPxYsX4+jRo0UmbUeOHIkGDRpAT08PZ86cQVBQEFJSUhAREVGi6/z+/XusXbsWxsbGAIBFixahU6dOCA8P/+II3Y+5urpi+PDhAIDx48dj3rx5OH78OKysrJCcnIyaNWuiRYsWEIlEMDU1Fdrp6+sDAHR0dAocLzs7G2vXrhXqFMeRI0dw7tw53Lx5E7Vq1QIAqVHe2traEIlExTq3rKwsZGVlCdvp6enFjoOIiIh+fJwegYiIiKiCOnLkCNq1awdjY2Noamqif//+SE1NlXqdX01NTUjYAoChoSHMzMykkq+GhoZS0x/ExcWhS5cuqF69OjQ1NYUkZ/50TLa2ttDQ0ICGhoaQvMy3efNmXLx4ERs2bMDevXsxd+7crzrH/ONoaGhg6NChQrmZmZnUHK1VqlQRziExMRGZmZn43//+J9V+7dq1uHv3rlT/9erVk7oOampqUkm8j6/N3bt3kZOTIyR5gby5fBs3boybN29+8VwcHByktm/evIlmzZpBJBIJZY6OjsjIyMCjR48KjfHTcx06dKjUOeYLCAiAk5MT6tWrh6FDhyI8PByLFi2SSjIWR/Xq1YWELQA0a9YMYrEYCQkJOHXqlNSxixp9+/E55CdF88/Bx8cH8fHxsLKywsiRI3Ho0KFixWZqalqihC0AxMfHo1q1akLC9muEhoZCW1tb+JiYlO0bdURERPR940hbIiIiogooKSkJnTt3xrBhwzBjxgzo6enh9OnT8PX1RXZ2NtTU1ADkJRU/JhKJCi0Ti8UAgLdv38LZ2RnOzs6Ijo6Gvr4+kpOT4ezsjOzsbADAvn37kJOTAwBSr/8DEBJXNjY2yM3NxZAhQzBmzJhiv77+qfj4eOFnLS0t4eeiziF/Xt+9e/dKJRwBQFlZWWr7436+dG2+1sdzypZEUTFNmzYNgYGBX+yjSZMm+PDhA5KSkmBlZVWqOD7VsGFDqd+PoaHhZ+sWdQ4NGjTA/fv3sX//fhw5cgS9evVC+/btpeYgLkxh11NOTg4SiUSqLP9vFSj49/o1goKCEBAQIGynp6czcUtEREQCJm2JiIiIKqC4uDiIxWKEh4cLr9Fv2bLlq/u9desWUlNTERYWJiSgLly4IFXn49fXiyIWi5GTkwOxWFzqpK2lZcmnlrKxsYGysjKSk5OlpkL4WjVq1ICSkhJiYmKEa5CTk4Pz588LU1UoKSkBgNTCbp9jbW2N7du3QyKRCKNtY2JioKmpiWrVqhUrJgMDAxgYGHyxXnx8POTk5IpV92PJycl4/PgxqlatCgD4559/ICcnBysrK6iqqpbq91MYLS0teHh4wMPDA+7u7ujYsSNevnwJPT09KCoqFut6AnnTKaSkpAjb6enpuH//vrBdr149PHr0CLdv3y50tK2SklKxj6WsrFzgSwAiIiKifEzaEhEREVVAlpaWyMnJwaJFi9ClSxfExMRg+fLlX91v9erVoaSkhEWLFmHo0KG4du0aQkJCvtguOjoaioqKqFu3LpSVlXHhwgUEBQXBw8NDGGW5c+dOBAUF4datW0K7xMREZGRk4MmTJ3j37p0wctPGxkZIgJaUpqYmAgMDMXr0aIjFYrRo0QJpaWmIiYmBlpYWvL29S9Wvuro6hg0bJsxdW716dcyePRuZmZnw9fUFkJfQFolE2LNnD1xdXaGqqvrZeYCHDx+O+fPn45dffoGfnx8SEhIwefJkBAQECIn40oiNjcXZs2fRpk0baGpqIjY2FqNHj0a/fv2gq6sr1Ltx4ways7Px8uVLvHnzRrj29evXF+qoqKjA29sbc+fORXp6OkaOHIlevXqVaD7bL4mIiECVKlVgb28POTk5bN26FUZGRsKcwGZmZjh69CgcHR2hrKwsdQ6fatu2LSIjI9GlSxfo6OggODhY6guD1q1bo1WrVujRowciIiJgaWmJW7duQSQSoWPHjjAzM0NGRgaOHj0KOzs7qKmpCaPWiYiIiEqCSVsiIiKiCsjOzg4RERGYNWsWgoKC0KpVK4SGhsLLy+ur+tXX10dkZCR+/fVXLFy4EA0aNMDcuXPh5uZWZDsFBQXMmjULt2/fhkQigampKfz8/DB69GihTlpaGhISEqTaDRo0CCdPnhS27e3tAQD379+HmZlZqc8jJCQE+vr6CA0Nxb1796Cjo4MGDRrg119/LXWfABAWFgaxWIz+/fvjzZs3aNiwIQ4ePCgkEo2NjTF16lRMmDABAwYMgJeXFyIjIwvty9jYGPv27cPYsWNhZ2cHPT09+Pr6YuLEiV8Vo7KyMjZt2oQpU6YgKysL5ubmGD16tNSr/EDe4mAPHjwQtvOv/cfTC1haWqJ79+5wdXXFy5cv0blzZ6mF7sqCpqYmZs+ejTt37kBeXh6NGjXCvn37hMR1eHg4AgICsGrVKhgbGyMpKemzfQUFBeH+/fvo3LkztLW1ERISIjXSFgC2b9+OwMBA9OnTB2/fvoWlpSXCwsIA5C2kN3ToUHh4eCA1NRWTJ0/GlClTyvR8iYiIqGIQST6dtIlKJT09Hdra2khLS5OaL60sXLx4EQ4ODjDyng9lo7J5hSxf1pNEPInyR1xcHBo0aFCmfRMREf2ovuW/+0RUMeX/d8XEfwvklDk6l4iISJaSwjp9s76L+yxR+vemiIiIiIiIiIiIiKjMMWlLREREREREREREVI4waUtERERERERERERUjjBpS0RERERERERERFSOMGlLREREREREREREVI4oyDoAIiIiIiIiynNtqnORK0kTERFRxcCRtkRERERERERERETlCJO2REREREREREREROUIk7ZERERERERERERE5QiTtkRERERERERERETlCJO2REREREREREREROUIk7ZERERERERERERE5QiTtkRERERERERERETlCJO2REREREREREREROUIk7ZERERERERERERE5QiTtkRERERERERERETlCJO2REREREREREREROUIk7ZERERERERERERE5QiTtkRERERERERERETlCJO2REREREREREREROUIk7ZERERERERERERE5QiTtkRERERERERERETlCJO2REREREREREREROUIk7ZERERERERERERE5QiTtkRERERERERERETlCJO2REREREREREREROUIk7ZERERERERERERE5YiCrAMgIiIiIiKiPHUmH4ScspqswyAiIpKJpLBOsg6h3OBIWyIiIiIiIiIiIqJyhElbIiIiIiIiIiIionKESVsiIiIiIiIiIiKickSmSdvQ0FA0atQImpqaMDAwQNeuXZGQkCBVx8nJCSKRSOozdOhQqTrJycno1KkT1NTUYGBggLFjx+LDhw9SdU6cOIEGDRpAWVkZlpaWiIyMLBDPkiVLYGZmBhUVFTRp0gTnzp0r83MmIiIiIiIiIiIiKopMk7YnT57EiBEj8M8//+Dw4cPIyclBhw4d8PbtW6l6gwcPRkpKivCZPXu2sC83NxedOnVCdnY2zpw5g6ioKERGRiI4OFioc//+fXTq1Alt2rRBfHw8/P39MWjQIBw8eFCos3nzZgQEBGDy5Mm4ePEi7Ozs4OzsjGfPnn37C0FERERERERERET0/8k0aXvgwAH4+PjA1tYWdnZ2iIyMRHJyMuLi4qTqqampwcjISPhoaWkJ+w4dOoQbN25g/fr1qF+/PlxcXBASEoIlS5YgOzsbALB8+XKYm5sjPDwc1tbW8PPzg7u7O+bNmyf0ExERgcGDB2PAgAGwsbHB8uXLoaamhj/++OO/uRhEREREX8HJyQn+/v4AADMzM8yfP1+m8RCVRz4+PujatauswyAiIiL6onI1p21aWhoAQE9PT6o8OjoalStXRp06dRAUFITMzExhX2xsLOrWrQtDQ0OhzNnZGenp6bh+/bpQp3379lJ9Ojs7IzY2FgCQnZ2NuLg4qTpycnJo3769UOdTWVlZSE9Pl/oQERERUdESEhLQpk0bGBoaQkVFBRYWFpg4cSJycnI+2yY1NRUdO3ZE1apVoaysDBMTE/j5+X3X91+RkZHQ0dGRdRjfpdTUVFSrVg0ikQivX7+W2rdkyRJYW1tDVVUVVlZWWLt27X8S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Bfj5+aFp06ZCEjc4OBidO3dG9erV4e7uDjk5OVy+fBnXrl3D9OnTix3rp4nZ/Dlqra2toaOjAwC4ffs2zp07hyZNmuDVq1eIiIjAtWvXEBUVJdU2Ozsbvr6+mDhxIpKSkjB58mT4+fkVurhZPjMzMzRv3hy+vr7Izc2Fm5ub1P78ad7yF0u+evUqNDU1Ub16damR30RERETFJdM5bYmIiIjo27Kzs0NERARmzZqFOnXqIDo6GqGhoV/dr76+PiIjI7F161bY2NggLCysWPPSKigoYNasWWjcuDHq1auHqVOnws/PT2pRsbS0NCQkJAjbqqqqWLVqFVq0aAFra2uMHj0abm5u2LNnz1efx5o1a+Dl5YUxY8bAysoKXbt2xfnz5z+72FVJ+nVwcEDnzp3RrFkzSCQS7Nu3T0hMN2/eHEOHDoWHhwf09fUxe/bsz/bVoEEDbNmyBZs2bUKdOnUQHByMadOmwcfH56tiVFJSQlBQEOrVq4dWrVpBXl4emzZtApCXjN24cSNu3bqFevXqYdasWYUmWdXU1DB+/Hh4enrC0dERGhoa2Lx5s7Df2dkZe/bswaFDh9CoUSM0bdoU8+bNK/Yo7JLIzc1FeHg47Ozs8L///Q/v37/HmTNnCiT227Vrh5o1a6JVq1bw8PCAm5sbpkyZ8sX++/bti8uXL6Nbt24FpvtYvnw57O3tMXjwYABAq1atYG9vj927d5fV6REREVEFI5J8aY4CKpb09HRoa2sjLS0NWlpaZdr3xYsX4eDgACPv+VA2Kvmrf0XJepKIJ1H+iIuLQ4MGDcq0byIioh/Vt/x3n+h7ERkZCX9/f7x+/VrWofwQ8v+7YuK/BXLKhY+6JiIi+tElhZXuTafvSXGfJTjSloiIiIiIiIiIiKgcYdKWiIiIiIiIiIiIqBxh0paIiIiIiErMx8eHUyMQERERfSNM2hIRERERERERERGVI0zaEhEREREREREREZUjCrIOgIiIiIiIiPJcm+pc5ErSREREVDFwpC0RERERERERERFROcKkLREREREREREREVE5wqQtERERERERERERUTnCpC0RERERERERERFROcKkLREREREREREREVE5wqQtERERERERERERUTnCpC0RERERERERERFROcKkLREREREREREREVE5wqQtERERERERERERUTnCpC0RERERERERERFROcKkLREREREREREREVE5wqQtERERERERERERUTnCpC0RERERERERERFROcKkLREREREREREREVE5wqQtERERERERERERUTnCpC0RERERERERERFROcKkLREREREREREREVE5wqQtERERERERERERUTnCpC0RERERERERERFROcKkLREREREREREREVE5wqQtERERERERERERUTmiIOsAiIiIiIiIKE+dyQchp6wm6zCIiIi+SlJYJ1mH8N3jSFsiIiIiIiIiIiKicoRJWyIiIiIiIiIiIqJyhElbIiIiIiIiIiIionKESVsiIiIiIiIiIiKicoRJWyIiIiIiIiIiIqJyhElbIiIiIqIiODk5wd/fHwBgZmaG+fPnyzSe74WPjw+6du1a4na8xkRERERM2hIRERER/RASEhLQpk0bGBoaQkVFBRYWFpg4cSJycnKKbDdy5Eg4ODhAWVkZ9evXL/bxoqOjYWdnBzU1NVSpUgUDBw5EamrqV54FcP78eQwZMuSr+wGApKQkiEQixMfHl0l/XzJlypQSXUMiIiKiz2HSloiIiIjoB6CoqAgvLy8cOnQICQkJmD9/PlatWoXJkyd/se3AgQPh4eFR7GPFxMTAy8sLvr6+uH79OrZu3Ypz585h8ODBX3MKAAB9fX2oqal9dT8lkZ2d/Z8ej4iIiOhLmLQlIiIiIiqliIgI1K1bF+rq6jAxMcHw4cORkZEh7I+MjISOjg727NkDKysrqKmpwd3dHZmZmYiKioKZmRl0dXUxcuRI5ObmCu3WrVuHhg0bQlNTE0ZGRvD09MSzZ8+KjMXCwgIDBgyAnZ0dTE1N4ebmhr59++LUqVNFtlu4cCFGjBgBCwuLYp93bGwszMzMMHLkSJibm6NFixb4+eefce7cuQJ1p06dCn19fWhpaWHo0KFfTJB+Oj2CSCTC6tWr0a1bN6ipqaFmzZrYvXu3sP/Vq1fo27cv9PX1oaqqipo1a2LNmjUAAHNzcwCAvb09RCIRnJycAPzf1A0zZsxA1apVYWVlJRxr165dUvHo6OggMjJS2H706BH69OkDPT09qKuro2HDhjh79iwiIyMxdepUXL58GSKRCCKRSKodERERUUkoyDoAIiIiIqLvlZycHBYuXAhzc3Pcu3cPw4cPx7hx47B06VKhTmZmJhYuXIhNmzbhzZs36N69O7p16wYdHR3s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TkRERERERERERPSRcpO0HTFiBK5du4ZNmzbJOpRiCQoKQlpamvB5+PChrEMiIiIiIiIiIiKiH4BM57TN5+fnhz179uDvv/9GtWrVhHIjIyNkZ2fj9evXUqNtnz59CiMjI6HOuXPnpPp7+vSpsC//f/PLPq6jpaUFVVVVyMvLQ15evtA6+X18SllZGcrKyqU7YSIiIiIiIiIiIqLPkOlIW4lEAj8/P+zcuRPHjh2Dubm51H4HBwcoKiri6NGjQllCQgKSk5PRrFkzAECzZs1w9epVPHv2TKhz+PBhaGlpwcbGRqjzcR/5dfL7UFJSgoODg1QdsViMo0ePCnWIiIiIiIiIiIiI/gsyHWk7YsQIbNiwAX/++Sc0NTWF+WO1tbWhqqoKbW1t+Pr6IiAgAHp6etDS0sIvv/yCZs2aoWnTpgCADh06wMbGBv3798fs2bPx5MkTTJw4ESNGjBBGwg4dOhSLFy/GuHHjMHDgQBw7dgxbtmzB3r17hVgCAgLg7e2Nhg0bonHjxpg/fz7evn2LAQMG/PcXhoiIiIiIiIiIiCosmSZtly1bBgBwcnKSKl+zZg18fHwAAPPmzYOcnBx69OiBrKwsODs7Y+nSpUJdeXl57NmzB8OGDUOzZs2grq4Ob29vTJs2Tahjbm6OvXv3YvTo0ViwYAGqVauG1atXw9nZWajj4eGB58+fIzg4GE+ePEH9+vVx4MCBAouTEREREREREREREX1LMk3aSiSSL9ZRUVHBkiVLsGTJks/WMTU1xb59+4rsx8nJCZcuXSqyjp+fH/z8/L4YExEREREREREREdG3ItM5bYmIiIiIiIiIiIhIGpO2REREREREREREROUIk7ZERERERERERERE5QiTtkRERERERERERETlCJO2REREREREREREROUIk7ZERERERERERERE5QiTtkRERERERERERETliIKsAyAiIiIiIqI8dSYfhJyymqzDICKiCiYprJOsQ6BPcKQtERERERERERERUTnCpC0RERERERERERFROcKkLREREREREREREVE5wqQtERERERERERERUTnCpC0RERERERERERFROcKkLRERERERVXiRkZHQ0dEpcTsfHx907dq1zOMhIiKiio1JWyIiIiIi+i7k5ORg/PjxqFu3LtTV1VG1alV4eXnh8ePHX2x79OhRNG/eHJqamjAyMsL4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vz98s29PEyEpycHr+XjsxyN77bXX/uzVNGevj3XWfuaZZx5InJGRkTma9K5Tp06WktSZ2bJli6pUqSInJyeVLFlSkZGRmdaPi4tT3bp15eXlJWdnZ5UoUUIff/yxUlJSHkg8AADgycRMWwAAgFx08+ZNtW7dWnZ2/Fu6LXB0dJS3t/cDbfPmzZtydHR8IG0tXbpUN2/eNPcvXryoihUr6tVXX/1H7drb2z/w+74fD7JvHoQTJ06oUaNG6tatm6KiorRx40a99dZb8vHxUVhYWIbn5MmTR+3bt1eVKlXk6emp/fv3q3PnzkpNTdXIkSNz+A4AAMDjgtEBAABALurQoYMWLVqU22E8turUqaN33nlHPXv2VP78+eXl5aVZs2bp6tWreuONN+Tu7q6SJUvq+++/l5R+9uxvv/2mJk2aKH/+/MqbN6/Kly+v1atX6+TJk6pbt64kKX/+/LJYLOrYsaN5zYiICPXs2VOFChUyk30TJkxQhQoVlDdvXvn5+al79+5KSkrK0v0UKFBA3t7e5rZ+/Xq5urqmS9r++eefatOmjfLmzauiRYtq2rRpmbZ78uRJWSwWxcbGWvXDxo0bFRwcLFdXV4WGhiouLs48Z//+/apbt67c3d3l4eGhqlWras+ePdqyZYveeOMNXblyxZwJPGTIEEl3lm4YNmyY2rdvLw8PD3Xp0iXDGcuxsbGyWCw6efKkWRYdHa06derI1dVV+fPnV1hYmP773/+qY8eO+uGHHzR58mTzen89L80XX3whX19fpaamWpU3bdpUb775piRpxowZCggI0Pjx41W2bFlFRESoZcuWmjhx4l37rkSJEnrjjTdUsWJFFS9eXC+//LLCw8O1devWTPscAAAgMyRtAQAActHt27c1duxY1a5dW++884569+5tteGfmzdvngoVKqRdu3bpnXfe0dtvv61XX31VoaGh2rt3r1588UW1a9dO165dS3dujx49lJycrB9//FEHDhzQmDFj5ObmJj8/P3377beS7nw9PiEhQZMnT7a6pqOjo6KjozVjxgxJkp2dnaZMmaKDBw9q3rx52rRpk/r16/eP7m3OnDl67bXXlDdvXqvyTz/9VBUrVtS+ffvUv39/vffee1q/fn2W2//oo480fvx47dmzRw4ODmZyU5LCw8P11FNPaffu3YqJiVH//v2VJ08ehYaGatKkSfLw8FBCQoISEhLUt29f87xx48aZsQ0cOPC+4oiNjVW9evVUrlw5bd++Xdu2bVOTJk10+/ZtTZ48WSEhIercubN5PT8/v3RtvPrqq7p48aI2b95sll26dElr1qxReHi4JGn79u2qX7++1XlhYWHavn37fffZsWPHtGbNGtWuXTvTesnJyUpMTLTaAAAA0rA8AgAAQC46cOCAKleuLEn65ZdfrI799aVkyL6KFSvq448/liQNGDBAo0ePVqFChdS5c2dJ0qBBgzR9+nT9/PPP6c49deqUWrRooQoVKki6M6syTYECBSRJRYoUSbd+a6lSpTR27Firsr+uuerv76/hw4erW7du+vzzz7N1X7t27dIvv/yiOXPmpDtWs2ZN9e/fX5JUunRpRUdHa+LEiXrhhReydI0RI0aYycf+/furUaNGunHjhpydnXXq1Cm9//77KlOmjKQ795wmX758slgsGS658Pzzz6tPnz7m/unTp+8Zx9ixYxUcHGzVV+XLlzd/dnR0lKura6ZLPOTPn18NGzbUV199pXr16kmSvvnmGxUqVMicNX327Fl5eXlZnefl5aXExERdv35dLi4ud20/7R8BkpOT1aVLF33yySeZ3tOoUaM0dOjQTOsAAIAnFzNtAQAActHmzZvvum3atCm3w3ssBAUFmT/b29urYMGCZhJWkpmkO3/+fLpz3333XQ0fPlw1a9bU4MGDM0zsZqRq1arpyjZs2KB69eqpaNGicnd3V7t27XTx4sUMZ/ieOnVKbm5u5pbR2qhz5sxRhQoVVL169XTHQkJC0u0fPnxYktStWzertjPz177z8fGR9L9+6t27t9566y3Vr19fo0ePVnx8fKZtpQkODr6ven+VNtM2K8qXL2/eY8OGDSXdmR387bffKjk5WZIUFRWl11577YGsKb1o0SLt3btXX331lVatWqVx48ZlWn/AgAG6cuWKud1P8hoAADw5SNoCAADgsZYnTx6rfYvFYlWWNqP572udStJbb72l48ePq127djpw4ICCg4M1derUe17z78sVnDx5Uo0bN1ZQUJC+/fZbxcTEmOvM/vXFYml8fX0VGxtrbt26dbM6fvXqVS1cuFCdOnW6Zyx/98knn1i1nZnM+mnIkCE6ePCgGjVqpE2bNqlcuXL67rvv7nn9v/dNWsLUMAyzLCUlxapOZjNc72b16tXmPc6ePVuS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XfmVK1c0Z84cffXVV3r++eclSV9++aXKli2rHTt26F//+pfWrVunQ4cOacOGDfLy8lKlSpU0bNgwffDBBxoyZIgcHR01Y8YMBQQEaPz48ZKksmXLatu2bZo4caLCwsJy9F4BAADwZLKzs1PTpk01fPhwFS5cWMeOHVOPHj106dIlffXVV7kdXo66evWqKlasqDfffNNqQsb9iIuLk4eHh7lfpEiRTOsnJCRY7X///ffq1KmTWrRoYZaFh4crISFB69evV0pKit544w116dLlifvvAgAAcpbNz7Q9evSofH19VaJECYWHh+vUqVOSpJiYGKWkpKh+/fpm3TJlyqhYsWLavn27JGn79u2qUKGCvLy8zDphYWFKTEzUwYMHzTp/bSOtTlobAAAAeHJMmDBBFSpUUN68eeXn56fu3btbzapM+yr8ypUrFRgYKFdXV7Vs2VLXrl3TvHnz5O/vr/z58+vdd9/V7du3zfMWLFig4OBgubu7y9vbW23bttX58+fN4/nz59fbb7+t4OBgFS9eXPXq1VP37t21devWe8Y8d+5clS9fXk5OTvLx8VFERITV8QsXLqh58+ZydXVVqVKltGLFCvPY7du31alTJwUEBMjFxUWBgYGaPHmyeXzIkCGaN2+eli9fbs5C3bJliyRp165dqly5spydnRUcHKzvvvtOFotFsbGx99X23TRs2FDDhw9X8+bN71n374oUKSJvb29zs7PLfLjz17re3t5avny56tatqxIlSkiSDh8+rDVr1mj27NmqUaOGnn32WU2dOlULFy7UmTNnrNpatmyZSpUqJWdnZ4WFhen06dNZjh8AACCNTSdta9SoocjISK1Zs0bTp0/XiRMnVKtWLf355586e/asHB0d060f5eXlZX4N6uzZs1YJ27Tjaccyq5OYmKjr16/fNbbk5GQlJiZabQAAAHi02dnZacqUKTp48KDmzZunTZs2qV+/flZ1rl27pilTpmjhwoVas2aNtmzZoubNm2v16tVavXq1FixYoJkzZ+qbb74xz0lJSdGwYcO0f/9+LVu2TCdPnlTHjh3vGseZM2e0dOlS1a5dO9N4p0+frh49eqhLly46cOCAVqxYoZIlS1rVGTp0qFq1aqWff/5ZL730ksLDw3Xp0iVJUmpqqp566iktWbJEhw4d0qBBg/Thhx9q8eLFkqS+ffuqVatWatCg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AAAAAADAhpC0BQAAAAAAAAAbQtIWAAAA+Js6deqoZ8+ekiR/f39NmjQpV+N5mCwWi5YtW5bbYTzS6EMAAPCgkbQFAAAAHgEXL15UgwYN5OvrKycnJ/n5+SkiIkKJiYm5HZo6duyoZs2a5dj1fvzxRzVp0kS+vr73nTDdsmWLLBZLuu3s2bOZnpeUlKSIiAg99dRTcnFxUbly5TRjxowHdCcAAAAZc8jtAAAAAADcm52dnZo2barhw4ercOHCOnbsmHr06KFLly7pq6++yu3wctTVq1dVsWJFvfnmm3rllVeydG5cXJw8PDzM/SJFimRav3fv3tq0aZP+3//7f/L399e6devUvXt3+fr66uWXX85W/AAAAPfCTFsAAAAgCyZMmKAKFSoob9688vPzU/fu3ZWUlGQej4yMlKenp1auXKnAwEC5urqqZcuWunbtmubNmyd/f3/lz59f7777rm7fvm2et2DBAgUHB8vd3V3e3t5q27atzp8/bx7Pnz+/3n77bQUHB6t48eKqV6+eunfvrq1bt94z5rlz56p8+fJycnKSj4+PIiIirI5fuHBBzZs3l6urq0qVKqUVK1aYx27fvq1OnTopICBALi4uCgwM1OTJk83jQ4YM0bx587R8+XJz9uqWLVskSbt27VLlypXl7Oys4OBgfffdd7JYLIqNjb2vtu+mYcOGGj58uJo3b37Pun9XpEgReXt7m5udXeZDop9++kkdOnRQnTp15O/vry5duqhixYratWuXVb2EhAQ1bNhQLi4uKlGihL755pssxwYAAJCGpC0AAACQBXZ2dpoyZYoOHjyoefPmadOmTerXr59VnWvXrmnKlClauHCh1qxZoy1btqh58+ZavXq1Vq9erQULFmjmzJlWib2UlBQNGzZM+/fv17Jly3Ty5El17NjxrnGcOXNGS5cuVe3atTONd/r06erRo4e6dOmiAwcOaMWKFSpZsqRVnaFDh6pVq1b6+eef9dJLLyk8PFyXLl2SJKWmpuqpp57SkiVLdOjQIQ0aNEgffvihFi9eLEnq27evWrVqpQYNGighIUEJCQkKDQ1VUlKSGjdurHLlyikmJkZDhgxR3759ra57r7YfhkqVKsnHx0cvvPCCoqOj71k/NDRUK1as0B9//CHDMLR582b9+uuvevHFF63qDRw4UC1atND+/fsVHh6u1157TYcPH75r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CPXs2VOFChVSWFiY/P399e2332r+/PmyWCzq2LGjJOnUqVNq2rSp3Nzc5OHhoVatWuncuXNmW2lji9mzZysgIEDOzs6S7swGnjlzpho3bixXV1eVLVtW27dv17Fjx1SnTh3lzZtXoaGh5qQXSffVp/7+/ho5cqTefPNNubu7q1ixYvriiy+s6vz+++9q06aNChQooLx58yo4OFg7d+40jy9fvlxVqlSRs7OzSpQooaFDh9pEIh7IDSRtATwyXFxcdPPmTUVERGj79u1auHChfv75Z7366qtq0KCBjh49ata9du2axowZo9mzZ+vgwYOaMmWKmUBNSEhQQkKCJGny5MkaP368xo0bp59//llhYWF6+eWXrdqSpP79++u9997T4cOHFRYWJknatGmTzpw5ox9//FETJkzQ4MGD1bhxY+XPn187d+5Ut27d1LVrV/3+++9mO+7u7oqMjNShQ4c0efJkzZo1SxMnTrS6Vnx8vJYtW6aVK1dq5cqV+uGHHzR69Gjz+IABAzR69GgNHDhQhw4d0ldffSUvLy9Jdx50w8LC5O7urq1btyo6Olpubm5q0KAB/7IOAACAHLV48WKVKVNGgYGBev311zV37lwZhiFJCg8P17///W8lJSWZ9deuXatr166pefPmkqRRo0Zp/vz5mjFjhg4ePKhevXrp9ddf1w8//GB1nf79+2v06NE6fPiwgoKClJSUpJdeekkbN27Uvn371KBBAzVp0kSnTp0yz2nfvr3OnDmjLVu26Ntvv9UXX3yh8+fPW7X76quv6vz58/r+++8VExOjKlWqqF69erp06dJ994GLi4ukO7NcL1++rOeff16VK1fWnj17tGbNGp07d06tWrWyOmfevHlydHRUdHS0ZsyYod27d6tBgwZq1aqVEhISNHnyZKWmpqpp06a6dOmSfvjhB61fv17Hjx9Plwg/duyYvv32Wy1dulSxsbFm+bBhw9S+fXvFxsaqTJkyatu2rbp27aoBAwZoz549MgxDERERZv376VNJGj9+vIKDg7Vv3z51795db7/9tuLi4sw2ateurT/++EMrVqzQ/v371a9fP6WmpkqStm7dqvbt2+u9997ToUOHNHPmTEVGRlpNUAGeKAYA2KAOHToYTZs2NQzDMFJTU43169cbTk5ORseOHQ17e3vjjz/+sKpfr149Y8CAAYZhGMaXX35p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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Visualize performance distribution\n", + "import matplotlib.pyplot as plt\n", + "\n", + "fig, axes = plt.subplots(1, 2, figsize=(14, 5))\n", + "\n", + "# Performance histogram\n", + "axes[0].hist(train_df['performance'], bins=20, edgecolor='black')\n", + "axes[0].set_xlabel('Performance')\n", + "axes[0].set_ylabel('Frequency')\n", + "axes[0].set_title('Performance Distribution')\n", + "\n", + "# Performance by model\n", + "model_perf = train_df.groupby('model_name')['performance'].mean().sort_values()\n", + "model_perf.plot(kind='barh', ax=axes[1])\n", + "axes[1].set_xlabel('Average Performance')\n", + "axes[1].set_title('Average Performance by Model')\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Training" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[KNNRouterTrainer] Initialized with router.\n", + "Trainer initialized!\n", + "Training samples: 5608\n", + "Save path: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + } + ], + "source": [ + "# Initialize trainer\n", + "trainer = KNNRouterTrainer(router=router, device='cpu')\n", + "\n", + "print(\"Trainer initialized!\")\n", + "print(f\"Training samples: {len(trainer.query_embedding_list)}\")\n", + "print(f\"Save path: {trainer.save_model_path}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting training...\n", + "==================================================\n", + "Created directory: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter\n", + "Successfully saved pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "==================================================\n", + "Training completed!\n" + ] + } + ], + "source": [ + "# Train the model\n", + "print(\"Starting training...\")\n", + "print(\"=\" * 50)\n", + "\n", + "trainer.train()\n", + "\n", + "print(\"=\" * 50)\n", + "print(\"Training completed!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. Model Verification" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Model loaded successfully!\n", + "Model type: KNeighborsClassifier\n", + "Number of classes: 9\n", + "Classes: ['codegemma-7b' 'gemma-2-9b-it' 'llama-3.1-8b-instruct'\n", + " 'llama-3.1-nemotron-51b-instruct' 'llama-3.3-nemotron-super-49b-v1'\n", + " 'llama3-chatqa-1.5-70b' 'llama3-chatqa-1.5-8b' 'mistral-7b-instruct-v0.3'\n", + " 'qwen2.5-7b-instruct']\n" + ] + } + ], + "source": [ + "# Verify the trained model\n", + "from llmrouter.utils import load_model\n", + "\n", + "# Load the saved model\n", + "saved_model = load_model(trainer.save_model_path)\n", + "\n", + "print(\"Model loaded successfully!\")\n", + "print(f\"Model type: {type(saved_model).__name__}\")\n", + "print(f\"Number of classes: {len(saved_model.classes_)}\")\n", + "print(f\"Classes: {saved_model.classes_}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Test prediction: qwen2.5-7b-instruct\n", + "\n", + "Prediction probabilities:\n", + " codegemma-7b: 0.0000\n", + " gemma-2-9b-it: 0.0000\n", + " llama-3.1-8b-instruct: 0.2000\n", + " llama-3.1-nemotron-51b-instruct: 0.2000\n", + " llama-3.3-nemotron-super-49b-v1: 0.2000\n", + " llama3-chatqa-1.5-70b: 0.0000\n", + " llama3-chatqa-1.5-8b: 0.0000\n", + " mistral-7b-instruct-v0.3: 0.0000\n", + " qwen2.5-7b-instruct: 0.4000\n" + ] + } + ], + "source": [ + "# Quick prediction test\n", + "import numpy as np\n", + "\n", + "# Use first training sample for testing\n", + "test_embedding = trainer.query_embedding_list[0].reshape(1, -1)\n", + "prediction = saved_model.predict(test_embedding)\n", + "\n", + "print(f\"Test prediction: {prediction[0]}\")\n", + "\n", + "# Get prediction probabilities\n", + "proba = saved_model.predict_proba(test_embedding)\n", + "print(f\"\\nPrediction probabilities:\")\n", + "for model, prob in zip(saved_model.classes_, proba[0]):\n", + " print(f\" {model}: {prob:.4f}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7. Hyperparameter Tuning (Optional)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Feature shape: (5608, 768)\n", + "Labels shape: (5608,)\n", + "Unique labels: 9\n" + ] + } + ], + "source": [ + "from sklearn.model_selection import cross_val_score\n", + "from sklearn.neighbors import KNeighborsClassifier\n", + "import numpy as np\n", + "\n", + "# Prepare data\n", + "X = np.array(trainer.query_embedding_list)\n", + "y = np.array(trainer.model_name_list)\n", + "\n", + "print(f\"Feature shape: {X.shape}\")\n", + "print(f\"Labels shape: {y.shape}\")\n", + "print(f\"Unique labels: {len(np.unique(y))}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cross-validation for different K values:\n", + "========================================\n", + "K= 1: 0.3607 (+/- 0.0708)\n", + "K= 3: 0.4158 (+/- 0.0700)\n", + "K= 5: 0.4399 (+/- 0.0738)\n", + "K= 7: 0.4542 (+/- 0.0724)\n", + "K= 9: 0.4672 (+/- 0.0681)\n", + "K=11: 0.4725 (+/- 0.0701)\n", + "K=15: 0.4756 (+/- 0.0680)\n", + "\n", + "Best K: 15 with accuracy: 0.4756\n" + ] + } + ], + "source": [ + "# Grid search for optimal K\n", + "k_values = [1, 3, 5, 7, 9, 11, 15]\n", + "results = []\n", + "\n", + "print(\"Cross-validation for different K values:\")\n", + "print(\"=\" * 40)\n", + "\n", + "for k in k_values:\n", + " knn = KNeighborsClassifier(n_neighbors=k, n_jobs=-1)\n", + " scores = cross_val_score(knn, X, y, cv=5, scoring='accuracy')\n", + " mean_score = scores.mean()\n", + " std_score = scores.std()\n", + " results.append((k, mean_score, std_score))\n", + " print(f\"K={k:2d}: {mean_score:.4f} (+/- {std_score:.4f})\")\n", + "\n", + "# Find best K\n", + "best_k, best_score, _ = max(results, key=lambda x: x[1])\n", + "print(f\"\\nBest K: {best_k} with accuracy: {best_score:.4f}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA1YAAAIjCAYAAAAAxIqtAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAiddJREFUeJzs3Xd8U9X/x/F3ugdtKdAJZYPsWUBEBGUURBRBBEWB4g8VRJZfBw6WAyeCiuBCUBFwb3Gg+EVFQBBBGV9ElkIHo5uu5P7+KA2EtpCQliTt6/l49OHNuTc3n+S00nfPueeaDMMwBAAAAAA4b16uLgAAAAAAPB3BCgAAAACcRLACAAAAACcRrAAAAADASQQrAAAAAHASwQoAAAAAnESwAgAAAAAnEawAAAAAwEkEKwAAAABwEsEKAFCu3nzzTTVr1ky+vr6qXr26q8tBJWAymTRz5kxXlwEAZ0WwAgAnLFmyRCaTSb/++qtNe3p6ujp37qyAgACtWrVKkjRz5kyZTCZFRUUpJyenxLnq16+vq666yqbNZDLJZDLpmWeesfu1z7RmzRrreUwmk3x9fdWwYUONHDlSf//9t6Nv+ax27typ0aNHq1GjRnrllVf08ssvl+v5UfEc+Z4+08SJE2UymfTXX3+Vef4HHnhAJpNJW7duLde6AcDVCFYAUM4yMjLUt29fbd26VR9++KH69etnsz8lJUULFy506JxPPfVUqWHMERMnTtSbb76pl19+WQMGDNDKlSvVqVMnHTp0yKnznm7NmjWyWCyaP3++Ro8ereuvv77czg3XOdf3dLERI0ZIkt5+++0yz7V8+XK1bt1abdq0qZBaAcBVCFYAUI4yMzOVkJCgLVu26P3331f//v1LHNOuXTs99dRTOnHihF3nbNeunZKTk7Vo0SKnauvevbtuuukmJSYm6vnnn9fTTz+tY8eOaenSpU6dV5Kys7MlFYVGSeU6BdDZQAnn2PM9XaxLly5q3Lixli9fXur+devWae/evdYABgCVCcEKAMpJVlaW+vXrp82bN+v999/XgAEDSj1u+vTpSk5OtnvUqlu3brriiiv05JNP2h3G7HHFFVdIkvbu3Wtt+/LLL9W9e3cFBwcrJCREAwYM0J9//mnzvNGjR6tatWras2ePrrzySoWEhGjEiBGqX7++ZsyYIUmKiIgocV3Miy++qJYtW8rf31+xsbG64447lJaWZnPunj17qlWrVtq0aZMuu+wyBQUF6f7779e+fftkMpn09NNPa8GCBWrYsKGCgoLUt29fHTx4UIZh6OGHH1adOnUUGBioa665RseOHbM598cff6wBAwYoNjZW/v7+atSokR5++GGZzeZSa9i+fbsuv/xyBQUFqXbt2nryySdLfIa5ubmaOXOmmjZtqoCAAMXExGjw4MHas2eP9RiLxaJ58+apZcuWCggIUFRUlG677TYdP378rP3z9NNPy2Qyaf/+/SX2TZs2TX5+ftZz7N69W0OGDFF0dLQCAgJUp04dDR8+XOnp6Wd9jXOx93v6dCNGjNDOnTu1efPmEvvefvttmUwm3XDDDcrPz9f06dPVsWNHhYWFKTg4WN27d9f3339/ztcYPXq06tevX6K9eLrtmd566y117NhRgYGBqlGjhoYPH66DBw+e83UAwBEEKwAoB9nZ2erfv782btyod999t8S1Uqfr3r27w0Fp5syZDoUxexT/8l+zZk1JRYtODBgwQNWqVdMTTzyhhx56SNu3b9ell16qffv22Ty3sLBQCQkJioyM1NNPP60hQ4Zo3rx5uvbaayVJCxcu1JtvvqnBgwdb67/jjjsUGxurZ555RkOGDNFLL72kvn37qqCgwObcR48eVf/+/dWuXTvNmzdPl19+uXXfsmXL9OKLL+rOO+/UXXfdpR9++EHXX3+9HnzwQa1atUr33nuvbr31Vn366af6z3/+Y3PeJUuWqFq1apo6darmz5+vjh07avr06brvvvtKfDbHjx9Xv3791LZtWz3zzDNq1qyZ7r33Xn355ZfWY8xms6666irNmjVLHTt21DPPPKNJkyYpPT1df/zxh/W42267TXfffbe6deum+fPnKzExUcuWLVNCQkKJ936666+/XiaTSe+8806Jfe+884769u2r8PBw5efnKyEhQb/88ovuvPNOLViwQLfeeqv+/vvvEsHVEY58T5+urOmAZrNZ77zzjrp37666desqIyNDr776qnr27KknnnhCM2fOVGpqqnV0rLw8+uijGjlypJo0aaK5c+dq8uTJWr16tS677DKnPh8AKMEAAJy3119/3ZBk1KtXz/D19TU++uijMo+dMWOGIclITU01fvjhB0OSMXfuXOv+evXqGQMGDLB5jiTjjjvuMAzDMC6//HIjOjrayMnJsXntjRs3nrXG77//3pBkLF682EhNTTUOHTpkfP7550b9+vUNk8lkbNy40cjMzDSqV69ujB071ua5SUlJRlhYmE37qFGjDEnGfffdd9b3WCwlJcXw8/Mz+vbta5jNZmv7Cy+8YK2rWI8ePQxJxqJFi2zOu3fvXkOSERERYaSlpVnbp02bZkgy2rZtaxQUFFjbb7jhBsPPz8/Izc21thV/bqe77bbbjKCgIJvjimt44403rG15eXlGdHS0MWTIEGvb4sWLS/RhMYvFYhiGYaxdu9aQZCxbtsxm/6pVq0ptP1PXrl2Njh072rRt2LDBpr7ffvvNkGS8++67Zz2XvRz5ni5Lp06djDp16tj0d/F7fumllwzDMIzCwkIjLy/P5nnHjx83oqKijDFjxti0SzJmzJhhfTxq1CijXr16JV63+Puv2L59+wxvb2/j0UcftTlu27Ztho+PT4l2AHAGI1YAUA6Sk5MVEBCguLg4u46/7LLLdPnllzs8apWUlHTe11qNGTNGERERio2N1YABA5Sdna2lS5cqPj5e33zzjdLS0nTDDTfoyJEj1i9vb2916dKl1OlZ48aNs+t1v/32W+Xn52vy5Mny8jr1z87YsWMVGhqqzz//3OZ4f39/JSYmlnquoUOHKiwszPq4S5cukqSbbrpJPj4+Nu35+fn6999/rW2BgYHW7czMTB05ckTdu3dXTk6Odu7cafM61apV00033WR97Ofnp86dO9usovj++++rVq1auvPOO0vUWTwd7d1331VYWJj69Olj87l27NhR1apVO+e0t2HDhmnTpk02UwtXrlwpf39/XXPNNZJk/Ty++uqrcr0ezdHv6dPddNNN+ueff/Tf//7X2vb222/Lz89PQ4cOlSR5e3vLz89PUtF0yWPHjqmwsFDx8fGlTiM8Hx988IEsFouuv/56m88/OjpaTZo0sWvaIQDYi2AFAOXgpZdekp+fn/r166ddu3bZ9RxHg9L5hLHTTZ8+Xd98842+++47bd26VYcOHdLNN98sqegaHanouquIiAibr6+//tq6KEUxHx8f1alTx67XLb5G6KKLLrJp9/PzU8OGDUtcQ1S7dm3rL9xnqlu3rs3j4lBx5i//xe2nX8f0559/6tprr1VYWJhCQ0MVERFhDU9nXotUp06dEtfqhIeH25xvz549uuiii2wC3Zl2796t9PR0RUZGlvhcs7KySnyuZxo6dKi8vLy0cuVKSZJhGHr33XfVv39/hYaGSpIaNGigqVOn6tVXX1WtWrWUkJCgBQsWOH191fl8TxcbPny4vL29rdMBc3Nz9eGHH6p///4KDw+3Hrd06VK1adNGAQEBqlmzpiIiIvT55587XXux3bt3yzAMNWnSpMTnv2PHjnN+/gDgiLL/NQAA2K1Fixb64osv1KtXL/Xp00c//fTTOf/Sf9lll6lnz5568skndfvtt9v1OjNmzFDPnj310ksvObzyXuvWrdW7d+9S91ksFklF11lFR0eX2H9mePD397cZfSpPp48sncnb29uhdsMwJElpaWnq0aOHQkNDNXv2bDVq1EgBAQHavHmz7r33Xuv7t/d89rJYLIqMjNSyZctK3R8REXHW58fGxqp79+565513dP/99+uXX37RgQMH9MQTT9gc98wzz2j06NH6+OOP9fXXX2vixImaM2eOfvnlF7sD8JnO53u6WGRkpPr06aP3339fCxYs0KeffqrMzEyb1QDfeustjR49WoMGDdLdd9+tyMhIeXt7a86cOTYjdKUpbYEKSSUWIrFYLDKZTPryyy9L7dNq1arZ9X4AwB4EKwAoJ507d9ZHH32kAQMGqE+fPlq7du05f3GeOXOmNSjZo0ePHtaL/adPn14eZUuSGjVqJKnoF+Kywtf5qlevniRp165datiwobU9Pz9fe/fuLffXK82aNWt09OhRffDBB7rsssus7aeviOioRo0aaf369SooKJCvr2+Zx3z77bfq1q3bWQPj2QwbNkzjx4/Xrl27tHLlSgUFBWngwIEljmvdurVat26tBx98UD///LO6deumRYsW6ZFHHjmv15XO73u62IgRI7Rq1Sp9+eWXevvttxUaGmpT93vvvaeGDRvqgw8+sAlKxStLnk14eHipC0+cOfrZqFEjGYahBg0aqGnTpnbVDQDni6mAAFCOevXqpeXLl+uvv/5Sv379lJGRcdbjTw9Kubm5dr1G8RTCl19+uTxKliQlJCQoNDRUjz32WKkr1aWmpp73uXv37i0/Pz8999xzNiM+r732mtLT0+1awttZxaMVp79+fn6+XnzxxfM+55AhQ3TkyBG98MILJfYVv871118vs9mshx9+uMQxhYWFdq1KN2TIEHl7e2v58uXW1fmCg4Ot+zMyMlRYWGjznNatW8vLy0t5eXnWtgMHDpS4lswejn5PFxs0aJCCgoL04osv6ssvv9TgwYMVEBBg3V9an6xfv17r1q0757kbNWqk9PR0bd261dp2+PBhffjhhzbHDR48WN7e3po1a1aJ0UbDMHT06FG73gsA2IMRKwAoZ9dee61eeeUVjRkzRldffbVWrVpl8wvlmWbMmGGzpPi59OjRQz169NAPP/xQHuVKkkJDQ7Vw4ULdfPPN6tChg4YPH66IiAgdOHBAn3/+ubp161ZqgLBHRESEpk2bplmzZqlfv366+uqrtWvXLr344ovq1KmTzSIRFeWSSy5ReHi4Ro0apYkTJ8pkMunNN990eGrf6UaOHKk33nhDU6dO1YYNG9S9e3dlZ2fr22+/1fjx43XNNdeoR48euu222zRnzhxt2bJFffv2la+vr3bv3q13331X8+fP13XXXXfW14mMjNTll1+uuXPnKjMzU8OGDbPZ/91332nChAkaOnSomjZtqsLCQr355pvy9vbWkCFDbOr94Ycfzus9O/o9LRVNsxs0aJD1Oqszbwp81VVX6YMPPtC1116rAQMGaO/evVq0aJFatGihrKyss557+PDhuvfee3Xttddq4sSJysnJ0cKFC9W0aVObhS8aNWqkRx55RNOmTdO+ffs0aNAghYSEaO/evfrwww916623lliWHwDOF8EKACpAYmKijh07pv/85z8aOnRoib+kn65nz54OB6WZM2c6FMbsceONNyo2NlaPP/64nnrqKeXl5al27drq3r17mav02WvmzJmKiIjQCy+8oClTpqhGjRq69dZb9dhjj5U5ja481axZU5999pnuuusuPfjggwoPD9dNN92kXr16KSEh4bzO6e3trS+++EKPPvqo3n77bb3//vuqWbOmLr30UrVu3dp63KJFi9SxY0e99NJLuv/+++Xj46P69evrpptuUrdu3ex6rWHDhunbb79VSEiIrrzySpt9bdu2VUJCgj799FP9+++/CgoKUtu2bfXll1/q4osvPq/3VprSvqfPtnCHVBSm3n77bcXExFhvSF1s9OjRSkpK0ksvvaSvvvpKLVq00FtvvaV3331Xa9asOet5a9asqQ8//FBTp07VPffcowYNGmjOnDnavXt3iRUF77vvPjVt2lTPPvusZs2aJalosZO+ffvq6quvdvyDAIAymAxn/lwHAAAAAOAaKwAAAABwFsEKAAAAAJxEsAIAAAAAJxGsAAAAAMBJBCsAAAAAcBLBCgAAAACcxH2sSmGxWHTo0CGFhITIZDK5uhwAAAAALmIYhjIzMxUbGysvr7LHpQhWpTh06JDi4uJcXQYAAAAAN3Hw4EHVqVOnzP0Eq1KEhIRIKvrwQkNDXVxN5WCxWJSamqqIiIizJn1cOPSJ+6FP3Av94X7oE/dDn7iXStMfBQXS668XbScmSr6+Li0nIyNDcXFx1oxQFoJVKYqn/4WGhhKsyonFYlFubq5CQ0M9+we9EqFP3A994l7oD/dDn7gf+sS9VJr+yM6W7r67aHvcOCk42LX1nHSuS4Q8+BMHAAAAAPdAsAIAAAAAJxGsAAAAAMBJLr/GasGCBXrqqaeUlJSktm3b6vnnn1fnzp1LPXbJkiVKTEy0afP391dubq718ejRo7V06VKbYxISErRq1apyrdswDBUWFspsNpfreSsri8WigoIC5ebmesScX29vb/n4+LDcPgAAAOzi0mC1cuVKTZ06VYsWLVKXLl00b948JSQkaNeuXYqMjCz1OaGhodq1a5f1cWm/+Pbr10+vF68koqLwVZ7y8/N1+PBh5eTklOt5KzPDMGSxWJSZmekxYSUoKEgxMTHy8/NzdSkAAABwcy4NVnPnztXYsWOto1CLFi3S559/rsWLF+u+++4r9Tkmk0nR0dFnPa+/v/85jzlfFotFe/fulbe3t2JjY+Xn5+cxQcGVikf4PGEUyDAM5efnKzU1VXv37lWTJk08YpQNAAAAruOyYJWfn69NmzZp2rRp1jYvLy/17t1b69atK/N5WVlZqlevniwWizp06KDHHntMLVu2tDlmzZo1ioyMVHh4uK644go98sgjqlmzZpnnzMvLU15envVxRkaGpKIQZbFYbI7Nzc2V2WxWnTp1FBQU5NB7ruoKCgrk6+L7ENgrICBAPj4+2r9/v/Ly8sp91NMdWCwW60gi3AN94l7oD/dDn7gf+sS9VJr+8PWVPvnk1LaL34+9n6fLgtWRI0dkNpsVFRVl0x4VFaWdO3eW+pyLLrpIixcvVps2bZSenq6nn35al1xyif7880/rXZD79eunwYMHq0GDBtqzZ4/uv/9+9e/fX+vWrZO3t3ep550zZ45mzZpVoj01NdXm+i2pKBwUB67CwsLzeetVkmEY1uvR3H3EqlhxPx85csRjAqEjLBaL0tPTZRgGI3Jugj5xL/SH+6FP3A994l4qVX906lT032PHXFuHpMzMTLuOc/niFY7o2rWrunbtan18ySWXqHnz5nrppZf08MMPS5KGDx9u3d+6dWu1adNGjRo10po1a9SrV69Szztt2jRNnTrV+rj47soRERElbhCcm5urzMxM+fj4yMfHoz4+t+BJAcXHx0deXl6qWbOmAgICXF1OubNYLDKZTJ5/d/ZKhD5xL/SH+6FP3A994l7oj4ph7++BLksGtWrVkre3t5KTk23ak5OT7b4+ytfXV+3bt9dff/1V5jENGzZUrVq19Ndff5UZrPz9/Uud6uXl5VXim9LLy0smk8n6BfsYhmH9vDzlcyvu49K+DyqLyv7+PBF94l7oD/dDn7gf+sS9VIr+KCiQli0r2h4xomg6oAvZ+1m67BP38/NTx44dtXr1amubxWLR6tWrbUalzsZsNmvbtm2KiYkp85h//vlHR48ePesxAAAAANxEfr6UmFj0lZ/v6mrs5tIoO3XqVL3yyitaunSpduzYoXHjxik7O9u6SuDIkSNtFreYPXu2vv76a/3999/avHmzbrrpJu3fv1//93//J6loYYu7775bv/zyi/bt26fVq1frmmuuUePGjZWQkOCS9+guRo8ebTPSVrNmTfXr109bt24tt9eYOXOm2rVrd17HrV27VtWrV9fkyZNlGIbDr/3yyy+rZ8+eCg0NlclkUlpaWolj6tevb/MZmEwmPf744w6/FgAAAHAmlwarYcOG6emnn9b06dPVrl07bdmyRatWrbIuaHHgwAEdPnzYevzx48c1duxYNW/eXFdeeaUyMjL0888/q0WLFpKKbuq6detWXX311WratKluueUWdezYUWvXrq2Uq7o5ql+/fjp8+LAOHz6s1atXy8fHR1dddZWry9Lnn3+uhIQETZ06VfPmzTuvqYI5OTnq16+f7r///rMeN3v2bOtncPjwYd15553nWzYAAABg5fLVFyZMmKAJEyaUum/NmjU2j5999lk9++yzZZ4rMDBQX331VXmWZ7/s7LL3eXtLp1/0drZjvbykwMBzHxsc7Fh9sr2/V3R0tO677z51795dqampioiIkCQdPHhQd911l77++mt5eXmpe/fumj9/vurXry+pqE/uuece/fnnn/L19VXLli319ttv6/vvv7eurFgcjF5//XWNHj36rDW9/fbbSkxM1DPPPFPm94E9Jk+ebK3vbEJCQirsHmcAAACoujz4qjY3U61a2V9DhtgeGxlZ9rH9+9seW79+6cc5KSsrS2+99ZYaN25svcdXQUGBEhISFBISorVr1+qnn35StWrV1K9fP+Xn56uwsFCDBg1Sjx49tHXrVq1bt0633nqrTCaThg0bprvuukstW7a0jgYNGzbsrDUsWLBAiYmJWrx4cYlQtWzZMlWrVu2sX2vXrnX4fT/++OOqWbOm2rdvr6eeeool8wEAAFAuXD5ihQvns88+U7WToSw7O1sxMTH67LPPrCudrFy5UhaLRa+++qrNqFP16tW1Zs0axcfHKz09XVdddZUaNWokSWrevLn1/NWqVZOPj49dI0I7duzQhAkT9Nprr2nEiBEl9l999dXq0qXLWc9Ru3Zt+974SRMnTlSHDh1Uo0YN/fzzz5o2bZoOHz6suXPnOnQeAAAA4EwEq/KSlVX2vjNvTJySUvaxZy7nuG/feZd0pssvv1wLFy6UVHS92osvvqj+/ftrw4YNqlevnn7//Xf99ddfCgkJsXlebm6u9uzZo759+2r06NFKSEhQnz591Lt3b11//fXnteJinTp1VL16dT311FPq379/iXOEhISUqMNZp9+rrE2bNvLz89Ntt92mOXPmcA0eAAAAnEKwKi+OXPNUUcee81TBaty4sfXxq6++qrCwML3yyit65JFHlJWVpY4dO2pZ8X0DTlN8Ddbrr7+uiRMnatWqVVq5cqUefPBBffPNN7r44osdqiUkJETffvut+vTpo8svv1zff/+9TbhatmyZbrvttrOe48svv1T37t0det3TdenSRYWFhdq3b58uuuii8z4PAAAAzk9KRq5SMvNsGwsLFbpoiSQp40iulFby0o3IEH9Fhtp3494LhWBVhRXfQO7EiROSpA4dOmjlypWKjIxUaGhomc9r37692rdvr2nTpqlr1656++23dfHFF8vPz09ms9nu1w8PD9e3336rvn37qmfPnvr+++8VGxsrqWKmAp5py5Yt8vLyUmRkpFPnAQCgNKX+wmgHd/yFEagoy9Yf0PzVu0vZU6voPwt/KfV5k3o10ZQ+TSuusPNAsKpC8vLylJSUJKloKuALL7ygrKwsDRw4UJI0YsQIPfXUU7rmmms0e/Zs1alTR/v379cHH3yge+65RwUFBXr55Zd19dVXKzY2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wxVdL+CQI1l5uuHlX/TjX0cU5OetxaM7EaoAAABQ7hy+Yv+hhx7S888/r4suusjadtFFF+nZZ5/Vgw8+WK7FAc44cDRH1y38Wdv+TVeNYD8tH3uxLmsa4eqyAAAAUAk5vHjF4cOHVVhYWKLdbDYrOTm5XIoCnPXnoXSNfn2jUjPzVLt6oN68pbMaRlRzdVkAAACopBweserVq5duu+02bd682dq2adMmjRs3Tr179y7X4oDzsW7PUQ1/6RelZuapWXSIPhh/CaEKAADAUwQESBs2FH0FuNeS6mfj8IjV4sWLNWrUKMXHx8vX11eSVFhYqISEBL366qvlXiDgiC+3HdakFVuUb7aoc4MaemVkvMICfV1dFgCgCirt/jy5BWbr9vZDGQrwLXlzene8Pw9wQXl7S506uboKhzkcrCIiIvTFF1/of//7n3bu3ClJatasmZo2Pb915BcsWKCnnnpKSUlJatu2rZ5//nl17tz5nM9bsWKFbrjhBl1zzTX66KOPrO2GYWjGjBl65ZVXlJaWpm7dumnhwoVq0qTJedUHz/HWL/v10Md/yDCkhJZRmj+8fan/YAEAcCGUdn+e0123aF2p7e54fx4A53beNwhu2rTpeYepYitXrtTUqVO1aNEidenSRfPmzVNCQoJ27dqlyMjIMp+3b98+/ec//1H37t1L7HvyySf13HPPaenSpWrQoIEeeughJSQkaPv27QrwoKFE2M8wDM37drf1H68bOtfVI4NayduLG/8CAFyntPvz2MMd788DXFD5+dL8+UXbkyZJfn6urcdOdgWrqVOn6uGHH1ZwcLCmTp161mPnzp1r94vPnTtXY8eOVWJioiRp0aJF+vzzz7V48WLdd999pT7HbDZrxIgRmjVrltauXau0tDTrPsMwNG/ePD344IO65pprJElvvPGGoqKi9NFHH2n48OF21wbPYLYYeujjP/T2+gOSpIm9mmhK7yYymQhVAADXKvX+PADOraBAuueeou3x4ytXsPrtt99UUFBg3S6LI7/M5ufna9OmTZo2bZq1zcvLS71799a6daUPjUvS7NmzFRkZqVtuuUVr16612bd3714lJSXZLKIRFhamLl26aN26dWUGq7y8POXlnZoDnZGRIUmyWCyyWCx2v6fydvpru7oWZ1ksFhmGUa7vIa/ArMnv/K6v/kyWySTNHNhCN19cT4Zh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uBaLVwAAAACAkxixAgAAAAAnEawAAAAAwEkEKwAAAABwEsEKAAAAAJxEsEKFmjNnjjp16qSQkBBFRkZq0KBB2rVrl6vLwkmPP/64TCaTJk+e7OpSqrR///1XN910k2rWrKnAwEC1bt1av/76q6vLqrLMZrMeeughNWjQQIGBgWrUqJEefvhhsdbThfPf//5XAwcOVGxsrEwmkz766COb/YZhaPr06YqJiVFgYKB69+6t3bt3u6bYKuBs/VFQUKB7771XrVu3VnBwsGJjYzVy5EgdOnTIdQVXAef6GTnd7bffLpPJpHnz5l2w+qoqghUq1A8//KA77rhDv/zyi7755hsVFBSob9++ys7OdnVpVd7GjRv10ksvqU2bNq4upUo7fvy4unXrJl9fX3355Zfavn27nnnmGYWHh7u6tCrriSee0MKFC/XCCy9ox44deuKJJ/Tkk0/q+eefd3VpVUZ2drbatm2rBQsWlLr/ySef1HPPPadFixZp/fr1Cg4OVkJCgnJzcy9wpVXD2fojJydHmzdv1kMPPaTNmzfrgw8+0K5du3T11Ve7oNKq41w/I8U+/PBD/fLLL4qNjb1AlVVtLLeOCyo1NVWRkZH64YcfdNlll7m6nCorKytLHTp00IsvvqhHHnlE7dq14y9ZLnLffffpp59+0tq1a11dCk666qqrFBUVpddee83aNmTIEAUGBuqtt95yYWVVk8lk0ocffqhBgwZJKhqtio2N1V133aX//Oc/kqT09HRFRUVpyZIlGj58uAurrfzO7I/SbNy4UZ07d9b+/ftVt27dC1dcFVVWn/z777/q0qWLvvrqKw0YMECTJ09mhkoFY8QKF1R6erokqUaNGi6upGq74447NGDAAPXu3dvVpVR5n3zyieLj4zV06FBFRkaqffv2euWVV1xdVpV2ySWXaPXq1frf//4nSfr999/1448/qn///i6uDJK0d+9eJSUl2fz/KywsTF26dNG6detcWBmKpaeny2QyqXr16q4upcqyWCy6+eabdffdd6tly5auLqfK8HF1Aag6LBaLJk+erG7duqlVq1auLqfKWrFihTZv3qyNGze6uhRI+vvvv7Vw4UJNnTpV999/vzZu3KiJEyfKz89Po0aNcnV5VdJ9992njIwMNWvWTN7e3jKbzXr00Uc1YsQIV5cGSUlJSZKkqKgom/aoqCjrPrhObm6u7r33Xt1www0KDQ11dTlV1hNPPCEfHx9NnDjR1aVUKQQrXDB33HGH/vjjD/3444+uLqXKOnjwoCZNmqRvvvlGAQEBri4HKvqDQ3x8vB577DFJUvv27fXHH39o0aJFBCsXeeedd7Rs2TK9/fbbatmypbZs2aLJkycrNjaWPgHOoqCgQNdff70Mw9DChQtdXU6VtWnTJs2fP1+bN2+WyWRydTlVClMBcUFMmDBBn332mb7//nvVqVPH1eVUWZs2bVJKSoo6dOggHx8f+fj46IcfftBzzz0nHx8fmc1mV5dY5cTExKhFixY2bc2bN9eBAwdcVBHuvvtu3XfffRo+fLhat26tm2++WVOmTNGcOXNcXRokRUdHS5KSk5Nt2pOTk637cOEVh6r9+/frm2++YbTKhdauXauUlBTVrVvX+m/9/v37ddddd6l+/fquLq9SY8QKFcowDN1555368MMPtWbNGjVo0MDVJVVpvXr10rZt22zaEhMT1axZM917773y9vZ2UWVVV7du3UrcguB///uf6tWr56KKkJOTIy8v2787ent7y2KxuKginK5BgwaKjo7W6tWr1a5dO0lSRkaG1q9fr3Hjxrm2uCqqOFTt3r1b33//vWrWrOnqkqq0m2++ucQ11AkJCbr55puVmJjooqqqBoIVKtQdd9yht99+Wx9//LFCQkKs89/DwsIUGBjo4uqqnpCQkBLXtwUHB6tmzZpc9+YiU6ZM0SWXXKLHHntM119/vTZs2KCXX35ZL7/8sqtLq7IGDhyoRx99VHXr1lXLli3122+/ae7cuRozZoyrS6sysrKy9Ndff1kf7927V1u2bFGNGjVUt25dTZ48WY888oiaNGmiBg0a6KGHHlJsbOxZV6rD+Ttbf8TExOi6667T5s2b9dlnn8lsNlv/ra9Ro4b8/PxcVXaldq6fkTPDra+vr6Kjo3XRRRdd6FKrFgOoQJJK/Xr99dddXRpO6tGjhzFp0iRXl1Glffrpp0arVq0Mf39/o1mzZsbLL7/s6pKqtIyMDGPSpElG3bp1jYCAAKNhw4bGAw88YOTl5bm6tCrj+++/L/XfjlGjRhmGYRgWi8V46KGHjKioKMPf39/o1auXsWvXLtcWXYmdrT/27t1b5r/133//vatLr7TO9TNypnr16hnPPvvsBa2xKuI+VgAAAADgJBavAAAAAAAnEawAAAAAwEkEKwAAAABwEsEKAAAAAJxEsAIAAAAAJxGsAAAAAMBJBCsAAAAAcBLBCgAAAACcRLACAOjmm2/WY4895uoyrNasWSOTyaS0tDRXl2L1008/qXXr1vL19dWgQYMq/PVMJpM++ugju4+35zObOXOm2rVr53Rtjtq+fbvq1Kmj7OzsC/7aAHChEKwAoJIYPXp0iV/433vvPQUEBOiZZ54p83m///67vvjiC02cONHa1rNnT5lMJq1YscLm2Hnz5ql+/frlWbbHmDp1qtq1a6e9e/dqyZIlpR5Tnp/b4cOH1b9///Os1r20aNFCF198sebOnevqUgCgwhCsAKCSevXVVzVixAgtXLhQd911V5nHPf/88xo6dKiqVatm0x4QEKAHH3xQBQUFFV3qBZOfn3/ez92zZ4+uuOIK1alTR9WrVy/zuPL63KKjo+Xv7+/UOS4Ue95rYmKiFi5cqMLCwgtQEQBceAQrAKiEnnzySd15551asWKFEhMTyzzObDbrvffe08CBA0vsu+GGG5SWlqZXXnmlzOeXNko2efJk9ezZ0/q4Z8+euvPOOzV58mSFh4crKipKr7zyirKzs5WYmKiQkBA1btxYX375ZYnz//TTT2rTpo0CAgJ08cUX648//rDZ/+OPP6p79+4KDAxUXFycJk6caDPdrH79+nr44Yc1cuRIhYaG6tZbby31feTl5WnixImKjIxUQECALr30Um3cuFGStG/fPplMJh09elRjxoyRyWQqc8TK3s9Nkj7++GN16NBBAQEBatiwoWbNmmUTOs6cCvjzzz+rXbt2CggIUHx8vD766COZTCZt2bLF5rybNm1SfHy8goKCdMkll2jXrl0lXvull15SXFycgoKCdP311ys9Pd26z2KxaPbs2apTp478/f3Vrl07rVq1yrq/+PNYuXKlevTooYCAAC1btkz79+/XwIEDFR4eruDgYLVs2VJffPGF9Xl9+vTRsWPH9MMPP5z1cwEAT0WwAoBK5t5779XDDz+szz77TNdee+1Zj926davS09MVHx9fYl9oaKgeeOABzZ492+lrY5YuXapatWppw4YNuvPOOzVu3DgNHTpUl1xyiTZv3qy+ffvq5ptvVk5Ojs3z7r77bj3zzDPauHGjIiIiNHDgQOvoyJ49e9SvXz8NGTJEW7du1cqVK/Xjjz9qwoQJNud4+umn1bZt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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Visualize K selection\n", + "import matplotlib.pyplot as plt\n", + "\n", + "k_values_plot = [r[0] for r in results]\n", + "scores_plot = [r[1] for r in results]\n", + "stds_plot = [r[2] for r in results]\n", + "\n", + "plt.figure(figsize=(10, 6))\n", + "plt.errorbar(k_values_plot, scores_plot, yerr=stds_plot, marker='o', capsize=5)\n", + "plt.axvline(x=best_k, color='r', linestyle='--', label=f'Best K={best_k}')\n", + "plt.xlabel('K (Number of Neighbors)')\n", + "plt.ylabel('Cross-Validation Accuracy')\n", + "plt.title('KNN Performance vs. K Value')\n", + "plt.legend()\n", + "plt.grid(True, alpha=0.3)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 8. Save Final Model" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Retraining with optimal K=15...\n", + "Successfully saved pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter_optimal.pkl\n", + "Optimal model saved to: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter_optimal.pkl\n" + ] + } + ], + "source": [ + "# Retrain with best K if different from original\n", + "if best_k != router.cfg['hparam']['n_neighbors']:\n", + " print(f\"Retraining with optimal K={best_k}...\")\n", + " \n", + " # Create new KNN model with best K\n", + " from sklearn.neighbors import KNeighborsClassifier\n", + " from llmrouter.utils import save_model\n", + " \n", + " best_knn = KNeighborsClassifier(\n", + " n_neighbors=best_k,\n", + " weights='uniform',\n", + " algorithm='auto',\n", + " n_jobs=-1\n", + " )\n", + " \n", + " # Train with best K\n", + " best_knn.fit(X, y)\n", + " \n", + " # Save optimal model\n", + " optimal_model_path = trainer.save_model_path.replace('.pkl', '_optimal.pkl')\n", + " save_model(best_knn, optimal_model_path)\n", + " print(f\"Optimal model saved to: {optimal_model_path}\")\n", + "else:\n", + " print(f\"Original K={best_k} is already optimal!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "In this notebook, we:\n", + "\n", + "1. **Loaded Configuration**: Set up KNNRouter with YAML configuration\n", + "2. **Explored Data**: Analyzed training data distribution\n", + "3. **Trained Model**: Used KNNRouterTrainer to fit the KNN classifier\n", + "4. **Verified Model**: Loaded and tested the saved model\n", + "5. **Tuned Hyperparameters**: Found optimal K value using cross-validation\n", + "\n", + "**Next Steps**:\n", + "- Use the next part of notebook to perform inference with the trained model\n", + "- Experiment with different distance metrics (cosine, euclidean)\n", + "- Try weighted voting with `weights='distance'`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# KNNRouter - Inference\n", + "\n", + "This part of notebook demonstrates how to use a trained **KNNRouter** for inference.\n", + "\n", + "## Overview\n", + "\n", + "We will cover:\n", + "1. Loading a trained KNNRouter model\n", + "2. Single query routing\n", + "3. Batch query routing\n", + "4. Full inference with API calls\n", + "5. Performance evaluation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Environment Setup" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Environment setup complete!\n" + ] + } + ], + "source": [ + "# Import required modules\n", + "from llmrouter.models.knnrouter import KNNRouter\n", + "from llmrouter.utils import setup_environment, load_model, get_longformer_embedding\n", + "\n", + "setup_environment()\n", + "print(\"Environment setup complete!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Configuration" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Configuration loaded!\n", + "Model path: saved_models/knnrouter/knnrouter.pkl\n" + ] + } + ], + "source": [ + "import yaml\n", + "\n", + "# Use inference configuration\n", + "# The inference config should have load_model_path set\n", + "CONFIG_PATH = \"configs/model_config_train/knnrouter.yaml\"\n", + "\n", + "# Load configuration\n", + "with open(CONFIG_PATH, 'r') as f:\n", + " config = yaml.safe_load(f)\n", + "\n", + "# Add load_model_path for inference\n", + "config['model_path']['load_model_path'] = config['model_path'].get(\n", + " 'load_model_path', \n", + " config['model_path']['save_model_path']\n", + ")\n", + "\n", + "print(\"Configuration loaded!\")\n", + "print(f\"Model path: {config['model_path']['load_model_path']}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Inference config saved to: configs/model_config_test/knnrouter_inference.yaml\n" + ] + } + ], + "source": [ + "# Create inference config file\n", + "INFERENCE_CONFIG_PATH = \"configs/model_config_test/knnrouter_inference.yaml\"\n", + "\n", + "os.makedirs(os.path.dirname(INFERENCE_CONFIG_PATH), exist_ok=True)\n", + "\n", + "inference_config = config.copy()\n", + "inference_config['model_path']['load_model_path'] = 'saved_models/knnrouter/knnrouter.pkl'\n", + "\n", + "with open(INFERENCE_CONFIG_PATH, 'w') as f:\n", + " yaml.dump(inference_config, f)\n", + "\n", + "print(f\"Inference config saved to: {INFERENCE_CONFIG_PATH}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Load Trained Router" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "✅ MetaRouter initialized successfully (YAML + data loaded).\n", + "Router loaded successfully!\n", + "Number of LLM candidates: 14\n", + "LLM candidates: ['qwen2.5-7b-instruct', 'codegemma-7b', 'gemma-2-9b-it', 'llama-3.1-8b-instruct', 'llama3-chatqa-1.5-8b', 'mistral-7b-instruct-v0.3', 'llama-3.3-nemotron-super-49b-v1', 'llama-3.1-nemotron-51b-instruct', 'llama3-chatqa-1.5-70b', 'llama3-70b-instruct', 'mixtral-8x7b-instruct-v0.1', 'mixtral-8x22b-instruct-v0.1', 'palmyra-creative-122b', 'mistral-nemo-12b-instruct']\n" + ] + } + ], + "source": [ + "# Initialize router for inference\n", + "router = KNNRouter(yaml_path=INFERENCE_CONFIG_PATH)\n", + "\n", + "print(\"Router loaded successfully!\")\n", + "print(f\"Number of LLM candidates: {len(router.llm_data)}\")\n", + "print(f\"LLM candidates: {list(router.llm_data.keys())}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Loaded model from: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Model classes: ['codegemma-7b' 'gemma-2-9b-it' 'llama-3.1-8b-instruct'\n", + " 'llama-3.1-nemotron-51b-instruct' 'llama-3.3-nemotron-super-49b-v1'\n", + " 'llama3-chatqa-1.5-70b' 'llama3-chatqa-1.5-8b' 'mistral-7b-instruct-v0.3'\n", + " 'qwen2.5-7b-instruct']\n" + ] + } + ], + "source": [ + "from pathlib import Path\n", + "PROJECT_ROOT = Path(os.getcwd()).parent.parent\n", + "project_root = os.getcwd()\n", + "# Load trained model\n", + "model_path = os.path.join(PROJECT_ROOT, config['model_path']['load_model_path'])\n", + "\n", + "\n", + "if os.path.exists(model_path):\n", + " knn_model = load_model(model_path)\n", + " print(f\"Loaded model from: {model_path}\")\n", + " print(f\"Model classes: {knn_model.classes_}\")\n", + "else:\n", + " model_path_alt = os.path.join(\n", + " project_root,\n", + " \"llmrouter\",\n", + " config['model_path']['load_model_path']\n", + " )\n", + " if os.path.exists(model_path_alt):\n", + " knn_model = load_model(model_path_alt)\n", + " print(f\"Loaded model from: {model_path_alt}\")\n", + " print(f\"Model classes: {knn_model.classes_}\")\n", + " else:\n", + " print(f\"Model not found at: {model_path}\")\n", + " print(\"Please run the training notebook first!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Single Query Routing" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Prepared 5 example queries\n" + ] + } + ], + "source": [ + "# Example queries for different task types\n", + "EXAMPLE_QUERIES = [\n", + " {\n", + " \"query\": \"What is the capital of France?\",\n", + " \"task_type\": \"world_knowledge\"\n", + " },\n", + " {\n", + " \"query\": \"Solve the equation: 2x + 5 = 15\",\n", + " \"task_type\": \"math\"\n", + " },\n", + " {\n", + " \"query\": \"Write a Python function to check if a number is prime.\",\n", + " \"task_type\": \"code\"\n", + " },\n", + " {\n", + " \"query\": \"If all roses are flowers and some flowers fade quickly, can we conclude that some roses fade quickly?\",\n", + " \"task_type\": \"reasoning\"\n", + " },\n", + " {\n", + " \"query\": \"Explain the theory of relativity in simple terms.\",\n", + " \"task_type\": \"explanation\"\n", + " }\n", + "]\n", + "\n", + "print(f\"Prepared {len(EXAMPLE_QUERIES)} example queries\")" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Input ids are automatically padded to be a multiple of `config.attention_window`: 512\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Query: What is the capital of France?\n", + "Task Type: world_knowledge\n", + "Routed to: gemma-2-9b-it\n" + ] + } + ], + "source": [ + "# Route a single query\n", + "def route_single_query(query_dict):\n", + " \"\"\"Route a single query and return the result.\"\"\"\n", + " result = router.route_single(query_dict)\n", + " return result\n", + "\n", + "# Test with first example\n", + "query = EXAMPLE_QUERIES[0]\n", + "result = route_single_query(query)\n", + "\n", + "print(f\"Query: {query['query']}\")\n", + "print(f\"Task Type: {query['task_type']}\")\n", + "print(f\"Routed to: {result['model_name']}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Routing Results:\n", + "================================================================================\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "\n", + "1. Query: What is the capital of France?...\n", + " Task: world_knowledge\n", + " Routed to: gemma-2-9b-it\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "\n", + "2. Query: Solve the equation: 2x + 5 = 15...\n", + " Task: math\n", + " Routed to: qwen2.5-7b-instruct\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "\n", + "3. Query: Write a Python function to check if a number is prime....\n", + " Task: code\n", + " Routed to: llama-3.1-8b-instruct\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "\n", + "4. Query: If all roses are flowers and some flowers fade quickly, can ...\n", + " Task: reasoning\n", + " Routed to: qwen2.5-7b-instruct\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "\n", + "5. Query: Explain the theory of relativity in simple terms....\n", + " Task: explanation\n", + " Routed to: qwen2.5-7b-instruct\n" + ] + } + ], + "source": [ + "# Route all example queries\n", + "print(\"Routing Results:\")\n", + "print(\"=\" * 80)\n", + "\n", + "for i, query in enumerate(EXAMPLE_QUERIES, 1):\n", + " result = route_single_query(query)\n", + " print(f\"\\n{i}. Query: {query['query'][:60]}...\")\n", + " print(f\" Task: {query['task_type']}\")\n", + " print(f\" Routed to: {result['model_name']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Get Routing Probabilities" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Query: What is the capital of France?\n", + "\n", + "Routing Probabilities:\n", + " gemma-2-9b-it 0.4000 ####################\n", + " qwen2.5-7b-instruct 0.4000 ####################\n", + " codegemma-7b 0.2000 ##########\n", + " llama-3.1-8b-instruct 0.0000 \n", + " llama-3.1-nemotron-51b-instruct 0.0000 \n", + " llama-3.3-nemotron-super-49b-v1 0.0000 \n", + " llama3-chatqa-1.5-70b 0.0000 \n", + " llama3-chatqa-1.5-8b 0.0000 \n", + " mistral-7b-instruct-v0.3 0.0000 \n" + ] + } + ], + "source": [ + "import numpy as np\n", + "\n", + "def get_routing_probabilities(query_text):\n", + " \"\"\"Get routing probabilities for all LLM candidates.\"\"\"\n", + " # Generate embedding\n", + " embedding = get_longformer_embedding(query_text).numpy().reshape(1, -1)\n", + " \n", + " # Get probabilities\n", + " proba = knn_model.predict_proba(embedding)[0]\n", + " \n", + " # Create results dictionary\n", + " results = dict(zip(knn_model.classes_, proba))\n", + " \n", + " # Sort by probability\n", + " results = dict(sorted(results.items(), key=lambda x: x[1], reverse=True))\n", + " \n", + " return results\n", + "\n", + "# Test with first query\n", + "query_text = EXAMPLE_QUERIES[0]['query']\n", + "probabilities = get_routing_probabilities(query_text)\n", + "\n", + "print(f\"Query: {query_text}\")\n", + "print(\"\\nRouting Probabilities:\")\n", + "for model, prob in probabilities.items():\n", + " bar = \"#\" * int(prob * 50)\n", + " print(f\" {model:30} {prob:.4f} {bar}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABKYAAAXSCAYAAAAi2ueFAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3Xlcjen/P/DXaV9OK2mTQqRQUpYiMWamLFmGsaswFLKvzQzKbsg6M5axlCaTnYbGLpPGGmXPmnxGtlASlc79+8Ov++vWThw+n9fz8TiPR/d1X3unM3Peruu6ZYIgCCAiIiIiIiIiIvrIVJTdASIiIiIiIiIi+t/EwBQRERERERERESkFA1NERERERERERKQUDEwREREREREREZFSMDBFRERERERERERKwcAUEREREREREREpBQNTRERERERERESkFAxMERERERERERGRUjAwRURERERERERESsHAFBEREX3S/P39IZfLldZ+amoqZDIZwsPDP3gbCxYs+GBtKFNcXBxkMhni4uIqtV6ZTIaQkJBKqy87OxvfffcdzMzMIJPJMHr06Eqrm8onOzsb1apVQ1RUlLK7AgCwsbGBv79/mfnCw8Mhk8mQmppa7rpDQkIgk8nw6NGjd+/gR/QuYyyLv78/bGxsKq2+4uzZswdyuRwPHz78oO0Q0btjYIqIiKiCLl68iH79+sHS0hKampqwsLBAv379cOnSJWV37b2kpKRgzJgxcHd3h5aW1nt/ASkoKIC+vj46d+5c5N6iRYsgk8ng5+dX5N7UqVMhk8lw9erVd267LLNnz8aOHTs+WP0liY2NrdRAyudsw4YNWLx4sbK7ITF79myEh4dj6NChiIyMRP/+/UvMa2NjA5lMVuzr5cuXH7HXlUuhUCA6Ohpt2rSBiYkJqlatim+++aZSgxGlWbJkCfT09NCrV6+P0h799/P29oatrS3mzJmj7K4QUQnUlN0BIiKiz8m2bdvQu3dvGBsbY9CgQahZsyZSU1OxZs0abNmyBRs3biw2EPM5OHbsGJYuXQoHBwfY29sjKSnpvepTVVVF8+bN8c8//xS5l5CQADU1NSQkJBR7r1q1aqhbt+57tV+a2bNno3v37ujSpUuZea2trfHixQuoq6u/d7uxsbH45Zdf/ueCU61atcKLFy+goaEhpm3YsAEXLlz4pFYlHTp0CM2bN8e0adPKlb9Ro0YYN25ckfQ3x/m5SUtLg6+vL3r06IHevXvj7t27WLRoEby9vZGcnAxNTc0P1nZ+fj6WLFmCMWPGQFVV9YO1Q/97AgICMH78eISGhkJPT0/Z3SGitzAwRUREVE43btxA//79UatWLfz9998wMTER740aNQoeHh7o168fzp07h5o1a37UvuXk5EBHR+e96ujUqROePn0KPT09LFiw4L0DUwDQsmVL7N+/H5cvX4a9vb2YnpCQgB49emDDhg24d+8ezMzMAACvXr3CiRMn8PXXX79325VFJpNBS0tL2d34rKmoqHwWc/jgwQM4ODiUO7+lpSX69etX7vyV8Xf6oRkZGeHcuXOoV6+emGZhYYGAgACcPn0aLVq0+GBt79q1Cw8fPkSPHj0+WBvlIQgCXr58CW1tbaX2gypPt27dMGLECGzevBkDBw5UdneI6C3cykdERFRO8+fPR05ODlatWiUJSgFA1apVsXLlSmRnZ2P+/PlieknnZxSeLfK233//HS4uLtDW1oaxsTF69eqFO3fuSPK0bt0aDRo0QGJiIlq1agUdHR18//338PPzQ9WqVZGfn1+k3q+//hp2dnaljs/Y2Ljc/5Kcnp6OK1euFNvWm1q2bAkAkpVRN2/exL179xAUFAQtLS3JvaSkJDx//lws96Z///0XXbp0gVwuh4mJCcaPH4+CggJJngULFsDd3R1VqlSBtrY2XFxcsGXLFkkemUyG58+fIyIiQtx6VdoZMsWdMXXv3j0MGDAA1atXh6amJszNzdG5c+dStzv5+/vjl19+EftQ+HrbqlWrULt2bWhqaqJJkyY4depUkTxXrlxB9+7dYWxsDC0tLbi6uiImJqbEtt+kUCiwZMkSNGzYEFpaWjAxMYG3tzdOnz4t5lm3bh2++OILVKtWDZqamnBwcMDy5cuL1GVjY4OOHTti3759aNSoEbS0tODg4IBt27ZJ8r19xlTr1q2xe/du3L59W5yHwr+TvLw8TJ06FS4uLjAwMICuri48PDxw+PDhco2vOA8ePMCgQYNgamoKLS0tODk5ISIiokj/bt26hd27d4t9ep/tayX9nQLAzp070aFDB1hYWEBTUxO1a9fGjBkziryfC+u4dOkS2rRpAx0dHVhaWuKnn34q0t7Lly8REhKCunXrQktLC+bm5vjmm29w48YNMY9CocDixYtRv359aGlpwdTUFAEBAXjy5ImYx8DAQBKUAiAGFfPy8t55Pspjx44dsLGxQe3atcW0mJgYyGQynDt3TkzbunUrZDIZvvnmG0l5e3t79OzZU7x+9eoVZsyYIf492djY4Pvvv0dubq6kXOH7eO/evXB1dYW2tjZWrlxZYj8vXryIL774Atra2qhevTpmzpwJhULxvsMHANy+fRu2trZo0KAB7t+/D6Bi74Oy3usA0Lhx4yJz17BhwyLzvHHjRshkMly+fLnUPv/111/w8PCArq4u9PT00KFDB1y8eLFIvh07dqBBgwbQ0tJCgwYNsH379mLry8jIQP/+/aGvrw9DQ0P4+fkhOTm52LP+yvtZWK1aNTg6OmLnzp2ljoWIlIOBKSIionL6888/YWNjAw8Pj2Lvt2rVCjY2Nvjzzz/fqf5Zs2bB19cXderUwcKFCzF69GgcPHgQrVq1wtOnTyV5MzIy0K5dOzRq1AiLFy9GmzZt0L9/f2RkZGDv3r2SvPfu3cOhQ4cqtLKjLMHBwbC3t8e///5bar7mzZtDTU0NR48eFdMSEhKgq6uLJk2awNXVVRKYKvz57cBUQUEBvLy8UKVKFSxYsACenp4ICwvDqlWrJPmWLFkCZ2dnTJ8+HbNnz4aamhq+/fZb7N69W8wTGRkJTU1NeHh4IDIyEpGRkQgICKjQ+Lt164bt27djwIAB+PXXXzFy5Eg8e/YMaWlpJZYJCAjAV199Jfah8PWmDRs2YP78+QgICMDMmTORmpqKb775RhIAvHjxIpo3b47Lly9j8uTJCAsLg66uLrp06VLiF703DRo0CKNHj4aVlRXmzZuHyZMnQ0tLC8ePHxfzLF++HNbW1vj+++8RFhYGKysrDBs2TAysvenatWvo2bMn2rVrhzlz5ohzvn///hL78MMPP6BRo0aoWrWqOA+F501lZWVh9erVaN26NebNm4eQkBA8fPgQXl5e77SK78WLF2jdujUiIyPRt29fzJ8/HwYGBvD398eSJUsAvA5oREZGomrVqmjUqJHYp7cD0G/Lz8/Ho0ePJK+cnBzxfnF/p8DrQ6TlcjnGjh2LJUuWwMXFBVOnTsXkyZOLtPHkyRN4e3vDyckJYWFhqFevHiZNmoS//vpLzFNQUICOHTsiNDQULi4uCAsLw6hRo5CZmYkLFy6I+QICAjBhwgS0aNECS5YswYABAxAVFQUvL68Sg8wZGRmYNWsW6tatW2zAuDL9888/aNy4sSStZcuWkMlk+Pvvv8W0+Ph4qKioSD5XHj58iCtXrqBVq1Zi2nfffYepU6eicePGWLRoETw9PTFnzpxiz69KSUlB79698dVXX2HJkiVo1KhRsX28d+8e2rRpg6SkJEyePBmjR4/G+vXrxffS+7hx4wZatWoFPT09xMXFwdTUVLxXnvdBed7rAODh4SGZu8ePH+PixYtQUVFBfHy8mB4fHw8TExPJate3RUZGokOHDpDL5Zg3bx6mTJmCS5cuoWXLlpLA7r59+9CtWzfIZDLMmTMHXbp0wYABAyQBceB18NTHxwd//PEH/Pz8MGvWLKSnpxd7HmFFPwtdXFyK3VpORJ8AgYiIiMr09OlTAYDQuXPnUvN16tRJACBkZWUJgiAIfn5+grW1dZF806ZNE978z3BqaqqgqqoqzJo1S5Lv/PnzgpqamiTd09NTACCsWLFCkregoECoXr260LNnT0n6woULBZlMJty8ebM8QxUEQRDmz58vABBu3bpV7H0/P79S77+pSZMmQu3atcXrgIAAoU2bNoIgCMLEiROFJk2aiPe6d+8u6OjoCPn5+UXamj59uqReZ2dnwcXFRZKWk5Mjuc7LyxMaNGggfPHFF5J0XV1dwc/Pr8y+C4Ig3Lp1SwAgrFu3ThAEQXjy5IkAQJg/f365yr9p+PDhQnH/+1XYRpUqVYTHjx+L6Tt37hQACH/++aeY1rZtW6Fhw4bCy5cvxTSFQiG4u7sLderUKbX9Q4cOCQCEkSNHFrmnUCjEn9+eR0EQBC8vL6FWrVqSNGtrawGAsHXrVjEtMzNTMDc3F5ydncW0w4cPCwCEw4cPi2kdOnQo9m/j1atXQm5uriTtyZMngqmpqTBw4EBJOgBh2rRpxY610OLFiwUAwu+//y6m5eXlCW5uboJcLhf/VgvH06FDh1LrezMvgCKvwv6U9HcqCMXPb0BAgKCjoyP5vRbWsX79ejEtNzdXMDMzE7p16yamrV27VgAgLFy4sEi9hb/X+Ph4AYAQFRUlub9nz55i0wVBEJ49eya4uLgIxsbGwsWLF8uYkfeTn58vyGQyYdy4cUXu1a9fX+jRo4d43bhxY+Hbb78VAAiXL18WBEEQtm3bJgAQkpOTBUEQhKSkJAGA8N1330nqGj9+vABAOHTokJhW+Lvcs2dPkbatra0lnxWjR48WAAgnTpwQ0x48eCAYGBiU+zOxUOF/Bx4+fChcvnxZsLCwEJo0aSL5DBCE8r8Pyvte37x5swBAuHTpkiAIghATEyNoamoKnTp1kvz3w9HRUejatat4vW7dOskYnz17JhgaGgqDBw+W9PfevXuCgYGBJL1Ro0aCubm58PTpUzFt3759AgDJ58DWrVsFAMLixYvFtIKCAuGLL76QfA4LQsU/C2fPni0AEO7fv1/kHhEpF1dMERERlcOzZ88AoMytboX3C/OX17Zt26BQKNCjRw/J6gszMzPUqVOnyDYmTU1NDBgwQJKmoqKCvn37IiYmRtJ+VFQU3N3dK/Xcq/DwcAiCUK7HfLds2RI3btzAvXv3ALxeFeXu7g4AaNGiBc6ePSuuMklISECzZs2gplb0GMzAwEDJtYeHB27evClJe/NMmCdPniAzMxMeHh44c+ZMhcZXGm1tbWhoaCAuLk6yBaoy9OzZE0ZGRuJ14eq8wnE+fvwYhw4dQo8ePfDs2TPxfZKRkQEvLy9cu3at1FVshVugijvc+81thW/OY2ZmJh49egRPT0/cvHkTmZmZknIWFhbo2rWreK2vrw9fX1+cPXtW/J1XhKqqqnh4uEKhwOPHj/Hq1Su4urq+0+8xNjYWZmZm6N27t5imrq6OkSNHIjs7G0eOHKlwnYWaNWuG/fv3S16+vr7i/eL+TgHp/Bb+Hj08PJCTk4MrV65I8srlcslqRw0NDTRt2lTy3t+6dSuqVq2KESNGFGmr8Pe6efNmGBgY4KuvvpJ8xri4uEAulxe7VXLo0KG4cOECdu3aVaGzt97F48ePIQiC5P1fyMPDQ1zJ8+zZMyQnJ2PIkCGoWrWqmB4fHw9DQ0M0aNAAwOvfOwCMHTtWUlfhYfVvrqIEgJo1a8LLy6vMfsbGxqJ58+Zo2rSpmGZiYoK+ffuWd6hFXLhwAZ6enrCxscGBAweKnYPyvA/K+14v/FwpXIUWHx+PJk2a4KuvvhLn8+nTp7hw4UKJK4QBYP/+/Xj69Cl69+4teU+pqqqiWbNm4nsqPT0dSUlJ8PPzg4GBgVj+q6++KvK+2rNnD9TV1TF48GAxTUVFBcOHD5fke5fPwsJ5ffToUYljIiLlYGCKiIioHMobcHr27BlkMhmqVq1aofqvXbsGQRBQp04dmJiYSF6XL1/GgwcPJPktLS2LffKXr68vXrx4IW5jSElJQWJiYqmPvf/Q3jxn6unTp7h48aJ4gLK7uztevXqFkydP4tatW0hPTy92u1DhWUhvMjIyKhIY2rVrF5o3bw4tLS0YGxvDxMQEy5cvLxJMeR+ampqYN28e/vrrL5iamqJVq1b46aef3ikI87YaNWpIrgu/SBWO8/r16xAEAVOmTCnyPikMNr39XnnTjRs3YGFhAWNj41L7kZCQgC+//BK6urowNDSEiYmJeD7S23Npa2tb5KyswicqvusZTREREXB0dISWlhaqVKkCExMT7N69+51+j7dv30adOnWgoiL9397C7Um3b99+pz4Cr8+W+/LLLyWvWrVqifdL+ju9ePEiunbtCgMDA+jr68PExEQMOrw9xurVqxeZ37ff+zdu3ICdnV2xAd1C165dQ2ZmJqpVq1bkvZOdnV3kfZOfn4+NGzfCz88Pbm5u5Z+U9yQIQpE0Dw8PpKen4/r16/jnn38gk8ng5uYmCVjFx8ejRYsW4u/59u3bUFFRga2traQuMzMzGBoaFvm9lzdwX/h+eltZZ/iVxsfHB3p6eti7dy/09fWLzVOe90F53+umpqaoU6eOZO48PDzQqlUr3L17Fzdv3kRCQgIUCkWpgalr164BAL744osi76l9+/aJ76nCdsszb7dv34a5uXmRhwS8/Xt8l8/CwvdWcWf7EZFy8al8RERE5WBgYAALCwvJwbDFOXfuHKpXry5+GS3pf4DfPuRYoVBAJpPhr7/+KvYx6XK5XHJd0tOiHBwc4OLigt9//x2+vr74/fffoaGhodSnXBUGmo4ePSp+2Sj8olu1alXUqVMHR48eFQ95Ly4wVZ5Hx8fHx6NTp05o1aoVfv31V5ibm0NdXR3r1q3Dhg0bKms4AIDRo0fDx8cHO3bswN69ezFlyhTMmTMHhw4dgrOz8zvXW9I4C79QFR6wPH78+BJXd7z9Ba6ibty4gbZt26JevXpYuHAhrKysoKGhgdjYWCxatKjSDnkuye+//w5/f3906dIFEyZMQLVq1aCqqoo5c+ZIDvL+HBT3d/r06VN4enpCX18f06dPR+3ataGlpYUzZ85g0qRJRea3rPdEeSkUClSrVg1RUVHF3n878JuVlYX8/HyYm5tXqJ13ZWxsDJlMVuwqxMLPhL///hs3b95E48aNxUPxly5diuzsbJw9exazZs0qUra8QQhlPoGvW7duiIiIQFRUVInn3VXW+6BQy5YtcfDgQbx48QKJiYmYOnUqGjRoAENDQ8THx+Py5cuQy+Wlfp4VvlcjIyPFJ6u+qbRA6ft6l8/CwvdWRf/hiIg+PAamiIiIysnHxwcrV67E0aNHiw2exMfHIzU1VbJ1xMjIqMjB5UDRVRq1a9eGIAioWbOmuNrkXfn6+mLs2LFIT0/Hhg0b0KFDh2K3hnws1apVE4NPurq6cHBwgKGhoXjf3d0dCQkJ+M9//gNVVdV3Xp2xdetWaGlpYe/evdDU1BTT161bVyRvZfyLee3atTFu3DiMGzcO165dQ6NGjRAWFobff/+9xDLv227hahx1dXV8+eWXFS5fu3Zt7N27F48fPy5x1dSff/6J3NxcxMTESFZwlfRUvMKVC2+O7erVqwBQ6lbPkuZiy5YtqFWrFrZt2ybJU9z2w/KwtrbGuXPnoFAoJCtJCrfMWVtbv1O97youLg4ZGRnYtm2b5KDuW7duvXOdtWvXxokTJ5Cfnw91dfUS8xw4cAAtWrQoVxBGS0sLw4cPl2xZ+5DU1NRQu3btYuehRo0aqFGjBuLj43Hz5k1xFU+rVq0wduxYbN68GQUFBZL5tLa2hkKhwLVr1ySHd9+/fx9Pnz5959+7tbW1uFLoTSkpKe9UH/D6ia9qamoYNmwY9PT00KdPn3fuW3nf6x4eHli3bh2io6NRUFAAd3d3qKiooGXLlmJgyt3dvdR/FCh8emK1atVK/TwqbLc882ZtbY3Dhw8jJydHsmrq+vXrknzv8ll469YtVK1atcyHGhDRx8etfEREROU0fvx46OjoICAgABkZGZJ7jx8/RmBgIPT19REUFCSm165dG5mZmZKVVunp6UWeGPTNN99AVVUVoaGhRf4FXBCEIu2Vpnfv3pDJZBg1ahRu3rxZqU/jK5Seno4rV66U+CSvt7Vs2RJJSUnYt2+feL5UIXd3dxw7dgzx8fFwdHQs8xyvkqiqqkImk0lWo6WmpmLHjh1F8urq6hYbMCyPnJwcvHz5UpJWu3Zt6OnpFXkMfXHtAnjntqtVq4bWrVtj5cqVSE9PL3L/4cOHpZbv1q0bBEFAaGhokXuF77vCL6Jvvg8zMzOLDfABwN27dyXv56ysLKxfvx6NGjUqdhVFIV1d3WK35hXX/okTJ3Ds2LHShlai9u3b4969e9i4caOY9urVKyxbtgxyuRyenp7vVO+7Km58eXl5+PXXX9+5zm7duuHRo0f4+eefi9wrbKdHjx4oKCjAjBkziuR59epVkfekhoYGgoKCSgxMpaWlFTkP69GjR7hy5YrkyYSF52aV51wfNze3Ik9pK+Th4YFDhw7h5MmTYmCqUaNG0NPTw9y5c6GtrQ0XFxcxf/v27QFAfNpjoYULFwIAOnToUGZ/itO+fXscP34cJ0+eFNMePnxY4kq08pDJZFi1ahW6d+8OPz8/xMTEvHPfyvteL5zDefPmwdHRUTz7ycPDAwcPHsTp06dL3cYHAF5eXtDX18fs2bOL/W9B4eeRubk5GjVqhIiICMnf/P79+3Hp0qUidebn5+O3334T0xQKRZEngr7LZ2FiYuJH3ZZKROXHFVNERETlZGtri/Xr16N3795o2LAhBg0ahJo1ayI1NRVr1qzBkydPEB0dLTmrpFevXpg0aRK6du2KkSNHIicnB8uXL0fdunUlBznXrl0bM2fORHBwMFJTU9GlSxfo6enh1q1b2L59O4YMGYLx48eXq58mJibw9vbG5s2bYWhoWO4vYJmZmVi2bBmA12cMAcDPP/8MQ0NDGBoaSgJuwcHBiIiIwK1bt8p9APq6detw6tSpIofYuru7IzMzE5mZmcUe3lxeHTp0wMKFC+Ht7Y0+ffrgwYMH+OWXX2Bra1tkC6aLiwsOHDiAhQsXwsLCAjVr1kSzZs3K1c7Vq1fRtm1b9OjRAw4ODlBTU8P27dtx//79Yh9D/3a7ADBy5Eh4eXlBVVW1zDJv++WXX9CyZUs0bNgQgwcPRq1atXD//n0cO3YM//nPf5CcnFxi2TZt2qB///5YunQprl27Bm9vbygUCsTHx6NNmzYICgrC119/DQ0NDfj4+CAgIADZ2dn47bffUK1atWK/ANatWxeDBg3CqVOnYGpqirVr1+L+/fslBrLenIuNGzdi7NixaNKkCeRyOXx8fNCxY0ds27YNXbt2RYcOHXDr1i2sWLECDg4OyM7OrtBcAcCQIUOwcuVK+Pv7IzExETY2NtiyZQsSEhKwePHidw6Evit3d3cYGRnBz88PI0eOhEwmQ2Rk5DtvyQJer5Jcv349xo4dKwZunj9/jgMHDmDYsGHo3LkzPD09ERAQgDlz5iApKQlff/011NXVce3aNWzevBlLlixB9+7dxTr//fdf2NvbY9q0aQgJCSm2zSNHjkj6/fPPPyM0NBSHDx9G69atAQAnT55EmzZtSqznTZ07d0ZkZCSuXr1aZOWoh4cHoqKiIJPJxBWrqqqqcHd3x969e9G6dWvJeV5OTk7w8/PDqlWrxO2TJ0+eREREBLp06YI2bdpUcJZfmzhxIiIjI+Ht7Y1Ro0ZBV1cXq1atElcrvSsVFRX8/vvv6NKlC3r06IHY2Fh88cUXFaqjIu91W1tbmJmZISUlRfK526pVK0yaNAkAygxM6evrY/ny5ejfvz8aN26MXr16wcTEBGlpadi9ezdatGghBkvnzJmDDh06oGXLlhg4cCAeP36MZcuWoX79+pK/6y5duqBp06YYN24crl+/jnr16iEmJgaPHz8GIF1pWZHPwgcPHuDcuXNF/vtDRJ+Ij/kIQCIiov8G58+fF/r06SOYmZkJKioqAgBBS0urxMep79u3T2jQoIGgoaEh2NnZCb///rv4mPC3bd26VWjZsqWgq6sr6OrqCvXq1ROGDx8upKSkiHk8PT2F+vXrl9rHTZs2CQCEIUOGlHtct27dkjz2/s3Xm4/zFgRB8PPzq9Cj0VNSUsS6rl69KrmnUCgEQ0NDAYCwcePGImX9/PwEXV3dIunFzeGaNWuEOnXqCJqamkK9evWEdevWFZvvypUrQqtWrQRtbW0BgORx8G8rnJfCx5Q/evRIGD58uFCvXj1BV1dXMDAwEJo1ayZs2rSpzHl49eqVMGLECMHExESQyWRivwrbmD9/fpEyAIRp06ZJ0m7cuCH4+voKZmZmgrq6umBpaSl07NhR2LJlS7n6MH/+fKFevXqChoaGYGJiIrRr105ITEwU88TExAiOjo6ClpaWYGNjI8ybN09Yu3Ztkd+5tbW10KFDB2Hv3r2Co6OjOO+bN2+WtHn48GEBgHD48GExLTs7W+jTp4/4uy98jykUCmH27NmCtbW1oKmpKTg7Owu7du0S/Pz8irwPi5ub4ty/f18YMGCAULVqVUFDQ0No2LCh5LHzb4+nPMrKW9rfaUJCgtC8eXNBW1tbsLCwECZOnCjs3bu3yByVVEdxc5GTkyP88MMPQs2aNQV1dXXBzMxM6N69u3Djxg1JvlWrVgkuLi6Ctra2oKenJzRs2FCYOHGicPfuXUm+wvdkSfPr6elZ5O+q8G/tzTEU/u7L83vKzc0VqlatKsyYMaPIvYsXLwoABHt7e0n6zJkzBQDClClTipTJz88XQkNDxTmxsrISgoODhZcvX0rylfa7tLa2LvL5cO7cOcHT01PQ0tISLC0thRkzZghr1qyp0GeiIPzffD18+FBMy8nJETw9PQW5XC4cP35cEISKvQ/K+14XBEH49ttvi3zu5uXlCTo6OoKGhobw4sULSf5169YVO8bDhw8LXl5egoGBgaClpSXUrl1b8Pf3F06fPi3Jt3XrVsHe3l7Q1NQUHBwchG3bthU7hocPHwp9+vQR9PT0BAMDA8Hf319ISEgQAAjR0dGSvOX9LFy+fLmgo6MjZGVlFTsXRKRcMkF4j3+eISIiIqxfvx7+/v7o168f1q9fr+zuAAB27tyJLl264O+//y7zX72J3pWNjQ0aNGiAXbt2Kbsr9F9ixowZWLduHa5du1auhx7Q/4YdO3aga9euOHr0qPhU14pwdnZG69atsWjRog/QOyJ6XzxjioiI6D35+vpizpw5iIyMxPfff6/s7gAAfvvtN9SqVavYQ9qJiD5VY8aMQXZ2NqKjo5XdFVKSFy9eSK4LCgqwbNky6Ovro3HjxhWub8+ePbh27RqCg4Mrq4tEVMl4xhQREVElmDRpknguhzJFR0fj3Llz2L17N5YsWVIpT58jIvpY5HI5Hjx4oOxuvJfs7Owyz0MzMTHhirASjBgxAi9evICbmxtyc3Oxbds2/PPPP5g9e3a5nij5Nm9v73c6n46IPh4GpoiIiP6L9O7dG3K5HIMGDcKwYcOU3R0iov85CxYsKPbJl28q74Mj/hd98cUXCAsLw65du/Dy5UvY2tpi2bJlkgdwENF/F54xRUREREREVElu3ryJmzdvlpqnZcuW0NLS+kg9IiL6tDEwRURERERERERESsHDz4mIiIiIiIiISCl4xhTR/xCFQoG7d+9CT0+PByITERERERFRuQmCgGfPnsHCwgIqKpW3zomBKaL/IXfv3oWVlZWyu0FERERERESfqTt37qB69eqVVh8DU0T/Q/T09AC8/iDR19dXcm+IiIiIiIjoc5GVlQUrKyvxe2VlYWCK6H9I4fY9fX19BqaIiIiIiIiowir7WBgefk5ERERERERERErBwBQRERERERERESkFA1NERERERERERKQUDEwREREREREREZFSMDBFRERERERERERKwcAUEREREREREREpBQNTRERERERE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GBgYIDMzk+dNERERERERUbl9qO+TXDFFRERERERERERKwcAUEREREREREREpBQNTRERERERERESkFAxMERERERERERGRUjAwRURERERERERESsHAFBERERERERERKQUDU0REREREREREpBQMTBERERERERERkVIwMEVERERERERERErBwBQRERERERERESkFA1NK1Lp1a4wePRoAYGNjg8WLFyu1Px+STCbDjh07lN2NzxrnkIiIiIiIiP7bMDBFEhkZGfD29oaFhQU0NTVhZWWFoKAgZGVlKbtr8Pf3R5cuXT5ae3///Td8fHxgYWFR7qBQXFwcZDJZkde9e/dKLZednY2goCBUr14d2tracHBwwIoVKyppJERERERERESfJjVld4A+LSoqKujcuTNmzpwJExMTXL9+HcOHD8fjx4+xYcMGZXfvo3r+/DmcnJwwcOBAfPPNNxUqm5KSAn19ffG6WrVqpeYfO3YsDh06hN9//x02NjbYt28fhg0bBgsLC3Tq1Omd+k9ERERERET0qeOKqU/UwoUL0bBhQ+jq6sLKygrDhg1Ddna2eD88PByGhobYtWsX7OzsoKOjg+7duyMnJwcRERGwsbGBkZERRo4ciYKCArFcZGQkXF1doaenBzMzM/Tp0wcPHjwQ7xsZGWHo0KFwdXWFtbU12rZti2HDhiE+Pr7MPq9duxb169eHpqYmzM3NERQUJLn/6NEjdO3aFTo6OqhTpw5iYmLEewUFBRg0aBBq1qwJbW1t2NnZYcmSJeL9kJAQREREYOfOneIqpLi4OADAyZMn4ezsDC0tLbi6umL79u2QyWRISkoqV90ladeuHWbOnImuXbuWmfdt1apVg5mZmfhSUSn9T+2ff/6Bn58fWrduDRsbGwwZMgROTk44efKkJF96ejratWsHbW1t1KpVC1u2bKlw34iIiIiIiIg+FQxMfaJUVFSwdOlSXLx4ERERETh06BAmTpwoyZOTk4OlS5ciOjoae/bsQVxcHLp27YrY2FjExsYiMjISK1eulAQv8vPzMWPGDCQnJ2PHjh1ITU2Fv79/if24e/cutm3bBk9Pz1L7u3z5cgwfPhxDhgzB+fPnERMTA1tbW0me0NBQ9OjRA+fOnUP79u3Rt29fPH78GACgUChQvXp1bN68GZcuXcLUqVPx/fffY9OmTQCA8ePHo0ePHvD29kZ6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X331FV6+fIl//vmnSPCybdu2qFOnDlq1aoWePXuiU6dOCAkJKbP+vn37Ijk5GV27di2yNXXFihVwdnbG4MGDAQCtWrWCs7MzYmJiKmt4RERERERERB+VTHjzgB+iT0x4eDhGjx6Np0+fKrsr/xWysrJgYGCAzMxM6OvrK7s7RERERERE9Jn4UN8nK+2pfERERERERERERBXBwBQRERERERERESkFA1P0SfP39+c2PiIiIiIiIqL/UgxMERERERERERGRUjAwRURERERERERESsHAFBERERERERERKQUDU0REREREREREpBQMTBERERERERERkVIwMEVERERERERERErBwBQRERERERERESlFpQWmWrdujdGjRwMAbGxssHjx4sqqmqhUISEhaNSoUYXLvfmeJSIiIiIiIqKP739uxVRKSgratGkDU1NTaGlpoVatWvjxxx+Rn59farmRI0fCxcUFmpqa7xQE+dTExcVBJpPh6dOnSu2HjY0NZDKZ5DV37lzx/suXL+Hv74+GDRtCTU0NXbp0qbS2t23bhhkzZlRafTKZDDt27Ki0+koTHh4OQ0PDj9IWERERERER0YeipuwOfGzq6urw9fVF48aNYWhoiOTkZAwePBgKhQKzZ88utezAgQNx4sQJnDt37iP1Vvny8vKgoaHxQduYPn06Bg8eLF7r6emJPxcUFEBbWxsjR47E1q1bK7VdY2PjSq2vPD7GfBIRERERERF9Lj7KiqmFCxeiYcOG0NXVhZWVFYYNG4bs7GzxfuHqj127dsHOzg46Ojro3r07cnJyEBERARsbGxgZGWHkyJEoKCgQy0VGRsLV1RV6enowMzNDnz598ODBg1L7UqtWLQwYMABOTk6wtrZGp06d0LdvX8THx5dabunSpRg+fDhq1apV7nEXbjGLjIyEjY0NDAwM0KtXLzx79kzMo1AoMGfOHNSsWRPa2tpwcnLCli1bxPuFK5v27t0LZ2dnaGtr44svvsCDBw/w119/wd7eHvr6+ujTpw9ycnLEcrm5uRg5ciSqVasGLS0ttGzZEqdOnQIApKamok2bNgAAIyMjyGQy+Pv7A3i9vS0oKAijR49G1apV4eXlBQA4cuQImjZtCk1NTZibm2Py5Ml49eqV2F7r1q0xcuRITJw4EcbGxjAzM0NISEi55qnw91f40tXVFe/p6upi+fLlGDx4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IiIioo9K3759oaOjA19fX1y4cAGHDx/GiBEj0L9/f5iZmYn18vLy4O/vj0uXLiEmJgbBwcEIDAyEmpoajI2NUalSJaxcuRJXr17FoUOHMGbMmGLH++mnn7B9+3YkJydj+PDhePjw4RsnjmxtbfHXX38hJSUF9+/fR35+vnjum2++QVhYGARBQPfu3d+ofyIioo8NE1NERERE9FGRy+XYt28fHjx4gEaNGqFHjx5o27YtfvzxR0m9tm3bolatWmjRogW8vb3RpUsXcd8qNTU1REdHIz4+HnXr1sXo0aMxb968YscLCwtDWFgYXFxccPToUezatQuVK1d+o9gHDRoEe3t7uLm5wdTUFHFxceK53r17Q0NDA71794aOjs4b9U9ERPSxkQmCIKg6CCIiIiKi/7rU1FTUrFkTp0+fRoMGDVQdDhER0XvBxBQRERERkQrl5+cjIyMD48aNw40bNySzqIiIiD51XMpHRERERKRCcXFxsLCwwOnTp7F8+XJVh0NERPReccYUERERERERERGpBGdMERERERERERGRSjAxRUREREREREREKsHEFBERERERERERqQQTU0REREREREREpBJMTBERERERERERkUowMUVERERERERERCrBxBQREREREREREakEE1NERERERERERKQSTEwREREREREREZFKMDFFREREREREREQqwcQUERERERERERGpBBNTRERERERERESkEkxMERERERERERGRSjAxRUREREREREREKsHEFBEREVEFCg8Ph0wmQ2pqqqpDEclkMgQGBqo6jA+eTCZDSEiIqsOg/6Ds7GxUqVIFUVFRqg5FolWrVqhbt+5r6126dAkaGhq4cOHCe4iKiD41TEwRERF9Yi5evIh+/frBysoK2trasLS0RL9+/XDp0iVVh/ZWtm3bBm9vb9SoUQNyuRz29vYYO3YsHj169Fb9ZmdnIzg4GHXr1oWenh4qVaqE+vXr49tvv8Xt27crJvj34NixYwgJCXnr+/Gpi4mJ+SiST0qlEuHh4ejSpQusra2hp6eHunXrYsaMGXj27JmqwytRq1atIJPJinzat2//3mPZuHEj+vXrh1q1akEmk6FVq1bF1ouNjS02ZplMhhMnTryXWBcvXgx9fX306tXrvYz3stu3byMkJAQJCQlv3IejoyM6deqEqVOnVlxgRPSfoaHqAIiI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ffs2rK2tVR0GERERERERfaRu3bqFqlWrVlh/TEwR/Yfo6+sDePGHxMDAQMXREBERERER0cciKysL1tbW4vfKisLEFNF/SOHyPQMDAyamiIiIiIiIqNwqelsYbn5OREREREREREQqwcQUERERERERERGpBBNTRERERERERESkEkxMERERERERERGRSjAxRUREREREREREKsHEFBERERERERERqQQTU0REREREREREpBJMTBERERERERERkUowMUVERERERERERCrBxBQREREREREREakEE1NERERERERERKQSTEypSGxsLGQyGR49evTexw4PD4eRkdFb92Nra4tFixaVq01qaipkMhkSEhLeenwiIiIiIiIi+rgxMaUiHh4eSE9Ph6Gh4WvrqiKJ1apVK8hksiKfTp06vVW/1tbWSE9PR926dSskzopKspVVq1atMGrUqArpKzY2Fg0aNIC2tjbs7OwQHh5eav2UlBS0bt0aZmZm0NHRQY0aNTB58mTk5+dXSDxERERERERE75uGqgP4r9LS0oK5uXmF9pmXlwctLa0K6Wvbtm3Iy8sTjzMyMuDi4oKvv/76rfpVV1ev8Osui4q8NxXhxo0b6NSpEwICAhAVFYWDBw/im2++gYWFBTw9PYtto6mpCR8fHzRo0ABGRkZITEzEoEGDoFQqMWvWrPd8BURERERERERvjzOmKkirVq0wYsQIjBo1CsbGxjAzM8OqVavw5MkTDBgwAPr6+rCzs8Nvv/0GoOgsqJs3b8LLywvGxsbQ09ODk5MTYmJikJqaitatWwMAjI2NIZPJ4OfnJ44ZGBiIUaNGoXLlymJCY+HChahXrx709PRgbW2NYcOGITs7u1zXY2JiAnNzc/Gzf/9+yOXyIompx48fo3fv3tDT04OVlRV++umnUvt9dSlf4X04ePAg3NzcIJfL4eHhgZSUFLFNYmIiWrduDX19fRgYGKBhw4Y4c+YMYmNjMWDAAGRmZoozukJCQgC8WGYYGhoKHx8fGBgYYPDgwcXOPEtISIBMJkNqaqpYFhcXh1atWkEul8PY2Bienp54+PAh/Pz88Mcff2Dx4sXieC+3K7Ry5UpYWlpCqVRKyrt27YqBAwcCAJYvX47q1atjwYIFcHBwQGBgIHr06IEffvihxHtXo0YNDBgwAC4uLrCxsUGXLl3Qt29fHDlypNR7TkRE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7e0NU1PTIhvBv0xTUxO//vorkpOT4ezsjDlz5mDGjBmSOrVr18bvv/+OxMRENG7cGO7u7ti5cyc0NF5MLhw3bhzU1dXh6OgIU1NTpKWllThemzZtYGJigpSUFPTp00dyrnr16tizZw/2798PFxcXLFiwAKtXr5bcu/v370v209LQ0MCcOXPQuHFjODs7Y9q0aQgMDBSXDxIRERERERF9bGTCyxvuENEnLSsrC4aGhsjMzOR+U0RERERERFRm7+r7JGdMERERERERERGRSjAxRUREREREREREKsHEFBERERERERERqQQTU0REREREREREpBJMTBERERERERERkUowMUVERERERERERCrBxBQREREREREREakEE1NERERERERERKQSTEwREREREREREZFKMDFFREREREREREQqwcTUO9SqVSuMGjUKAGBra4tFixapNJ53SSaTYceOHaoO45MWHh4OIyMjVYdBREREREREVGGYmPqPycjIQPv27WFpaQltbW1YW1sjMDAQWVlZqg4Nfn5+6Nat23sb788//4SXlxcsLS3LnFiLjY2FTCYr8rlz506p7YprI5PJMG/ePLHOgwcP0LdvXxgYGMDIyAj+/v7Izs5+28skIiIiIiIi+mAxMfUfo6amhq5du2LXrl24fPkywsPDceDAAQQEBKg6tPfuyZMncHFxwU8//VTutikpKUhPTxc/VapUKbX+y3XT09Oxdu1ayGQyfPXVV2Kdvn374uLFi9i/fz92796NP//8E4MHDy53bEREREREREQfCyamVGThwoWoV68e9PT0YG1tjWHDhklmxxQu29q9ezfs7e0hl8vRo0cP5OTkICIiAra2tjA2NsbIkSNRUFAgtouMjISbmxv09fVhbm6OPn364N69e+J5Y2NjDB06FG5ubrCxsUHbtm0xbNgwHDly5LUxr127Fk5OTtDW1oaFhQUCAwMl5+/fv4/u3btDLpejVq1a2LVrl3iuoKAA/v7+qF69OnR1dWFvb4/FixeL50NCQhAREYGdO3eKs4liY2MBAKdOnYKrqyt0dHTg5uaG7du3QyaTISEhoUx9l6RDhw6YMWMGunfv/tq6r6pSpQrMzc3Fj5pa6b9KL9c1NzfHzp070bp1a9SoUQMAkJSUhL1792L16tVo0qQJmjVrhqVLlyI6Ohq3b9+W9LVjxw7UqlULOjo6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y5cvQxAE2NjYIDAwEKNHjxbrZGZmim9iK/TNN99I3pTn6uoKALhx44b4ZrY3ERoaClNTU8yePRvXr1+HkZERGjRogO++++6N+wRevOlOqVSif//+ePz4Mdzc3LBv3z4xWWJlZYVp06Zh0qRJGDBgAHx8fBAeHl5sX1ZWVoiJicH48ePh4uICExMT+Pv7Y/LkyW8Vo7a2NqKjoxESEoLc3FxUr14do0ePxpgxYyT1OnbsiJs3b4rHhff+5aVwdnZ2+PLLL9GxY0c8ePAAnTt3xs8///xW8b1KX18fc+fOxZUrV6Curo5GjRohJiZGTM4tWLAAY8aMwapVq2BlZYXU1NQS+woKCsKNGzfQuXNnGBoaIjQ0VDJjCgC2bt2KcePGoXfv3njy5Ans7OzENzp6eHggICAA3t7eyMjIQHBwMEJCQir0eomIiIiIiIjeNZnw6kY3RPTJysrKgqGhITIzMyV7oRERERERERGV5l19n3wvb+UjIiIiIiIiIiJ6FRNTRERERERERESkEkxMERERERERERGRSjAxRUREREREREREKsHEFBERERERERERqQQTU0REREREREREpBJMTBERERERERERkUowMUVERERERERERCrBxBQREREREREREakEE1NERERERERERKQSTEx9QFq1aoVRo0YBAGxtbbFo0SKVxvOx8PPzQ7du3crdjveYiIiIiIiISLWYmKJyS0lJQevWrWFmZgYdHR3UqFEDkydPRn5+fqntRo4ciYYNG0JbWxv169cv83hRUVFwcXGBXC6HhYUFBg4ciIyMjLe8CuD06dMYPHjwW/cDAKmpqZDJZEhISKiQ/l4nJCSkXPeQiIiIiIiI6EPExBSVm6amJnx8fPD7778jJSUFixYtwqpVqxAcHPzatgMHDoS3t3eZx4qLi4OPjw/8/f1x8eJFbN68GadOncKgQYPe5hIAAKamppDL5W/dT3nk5eW91/GIiIiIiIiIPmRMTH0kFi5ciHr16kFPTw/W1tYYNmwYsrOzxfPh4eEwMjLC7t27YW9vD7lcjh49eiAnJwcRERGwtbWFsbExRo4ciYKCArFdZGQk3NzcoK+vD3Nzc/Tp0wf37t0rNZYaNWpgwIABcHFxgY2NDbp06YK+ffviyJEjpbZbsmQJhg8fjho1apT5uo8fPw5bW1uMHDkS1atXR7NmzTBkyBCc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CldXV9y+fbvEsfz8/LBw4UL8+OOPqFu3Lr7++mvY29tj27Ztknpt27ZFrVq10KJFC3h7e6NLly4ICQkp3w15DS0tLQQFBcHZ2RktWrSAuro6oqOjAby4J0uWLMGKFStgaWmJrl27ltrXggULYG1tjebNm6NPnz4YN26cZI8sLS0t/P7776hSpQo6duyIevXqISwsTJwt9tVXX6F9+/Zo3bo1TE1N8euvv1botRIRERERERG9DzLh1Y1uiOiTlZWVBUNDQ2RmZor7eBERERERERG9zrv6PskZU0REREREREREpBJMTBERERERERERkUowMUVERERERERERCrBxBQREREREREREakEE1NERERERERERKQSTEwREREREREREZFKMDFFREREREREREQqwcQUERERERERERGpBBNTRERERERERESkEkxMERERERERERGRSjAxRe+Vn58funXrpuowPkq8d0RERERERPSpYWKK6AMQEhICmUxW5KOnp6fq0IiIiIiIiIjeGQ1VB0BEwLhx4xAQECApa9u2LRo1aqSiiIiIiIiIiIjePc6YolIplUrMnTsXdnZ20NbWRrVq1TBz5kwAwPnz59GmTRvo6uqiUqVKGDx4MLKzs8W2BQUFGDNmDIyMjFCpUiVMmDABgiAU6X/27NmoXr06dHV14eLigi1btkjq7Nq1C7Vq1YKOjg5at26NiIgIyGQyPHr0SKxz9OhRNG/eHLq6urC2tsbIkSPx5MkT8bytrS1mzJgBHx8fKBQK2NjYYNeuXfj333/RtWtXKBQKODs748yZM2Kb8PBwGBkZYffu3bC3t4dcLkePHj2Qk5ODiIgI2NrawtjYGCNHjkRBQYHYLjIyEm5ubtDX14e5uTn69OmDe/fulXqfFQoFzM3Nxc/du3dx6dIl+Pv7F6k7bdo0mJqawsDAAAEBAcjLyyu1byIiIiIiIqIPFRNTVKqgoCCEhYVhypQpuHTpEjZs2AAzMzM8efIEnp6eMDY2xunTp7F582YcOHAAgYGBYtsFCxYgPDwca9euxdGjR/HgwQNs375d0v/s2bOxfv16LF++HBcvXsTo0aPRr18//PHHHwCAGzduoEePHujWrRsSExMxZMgQfP/995I+rl27hvbt2+Orr77CX3/9hY0bN+Lo0aOSWADghx9+QNOmTXHu3Dl06tQJ/fv3h4+PD/r1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kJWVhWrVqimsQ/WlDBgwADVq1MD//vc/bNiwAc2bN4e1tfUXj4OIiOhL44wpIiIiIlI6XV1d2Nraoly5cgpJqdzY29vj4sWLeP36tVQWGRkJFRUVaXF0ALh48SLevHkjbZ8+fRpyuRxWVlZITExEbGwsJk+ejBYtWsDe3h4vXrzI9XinT5+Wfn737h2io6Nhb29frHFqaGggMzMzR3n16tXh7OyMtWvXYtOmTejXr1+x+iciIvrWMDFFRERERN+Unj17QktLC3369MGVK1dw9OhRDB8+HL1790aZMmWkeunp6ejfvz+uXbuG8PBw+Pr6wtvbGyoqKjAyMkKpUqWwZs0a3L59G0eOHMGYMWNyPd6KFSuwc+dO3LhxA8OGDcOLFy+KnTiysbHBpUuXEBsbi2fPniEjI0PaN2DAAMydOxdCCHTq1KlY/RMREX1rmJgiIiIiom+Kjo4O9u/fj+fPn6NOnTro3LkzWrRogV9//VWhXosWLVCpUiU0adIEXbt2Rfv27aV1q1RUVBAaGoro6GhUq1YNo0ePxoIFC3I93ty5czF37lw4OTnhxIkT2LNnD0qXLl2s2AcOHAg7Ozs4OzvDxMQEkZGR0r7u3btDTU0N3bt3h5aWVrH6JyIi+tbIhBBC2UEQEREREf3XxcXFoWLFijh37hxq1aql7HCIiIi+CCamiIiIiIiUKCMjA4mJiRg3bhzu3bunMIuKiIjo34638hERERERKVFkZCTMzc1x7tw5rFq1StnhEBERfVGcMUVERERERERERErBGVNERERERERERKQUTEwREREREREREZFSMDFFRERERERERERKwcQUEREREREREREpBRNTRERERERERESkFExMERERERERERGRUjAxRURERERERERESsHEFBERERERERERKQUTU0REREREREREpBRMTBERERERERERkVIwMUVERERERERERErBxBQRERERERERESkFE1NERERERERERKQUTEwREREREREREZFSMDFFRERExRYYGAiZTIa4uDhlh/LVkMlk8Pb2VnYYCvbt24caNWpAS0sLMpkML1++VHZIuZLJZPDz81N2GF+Up6cn5HL5Fz9u06ZNUa1atWK19fT0hI2NjUJZSkoKBgwYADMzM8hkMowaNerTg/xGZH8ORkVFKT2Gf/NncUpKCkxNTRESEvJFj/sp53bi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nz64evUqIiIiMGnSJAQGBkJNjR9lIiIiIiIi+vzw2ywVasKECRg1ahQmTpwIR0dHdOnSBQ8ePICenh727duHx48fo169eujYsSM8PT3x888/S8eOGjUKPXv2hJ+fHxo2bAgDAwN06NBB1v60adMwYcIEzJo1C46OjmjRogX27NmDChUqAAAqVKiArVu3Ytu2bXB2dkZwcLD0q3y5U9ScnZ1x9OhRXL9+He7u7qhduzYmTpwIa2vrUrpK/8fc3BwhISHYsmULqlevjtmzZ2P+/PnFOjYnJwchISHw9/eHurp6vnU8PT1RpUoVNGnSBF26dEHbtm0xefLkD3gGRERERERERKVHIYQQqg6CqCRmzJiBFStWcCHvd5CamgojIyOkpKTA0NBQ1eEQERERERHRZ+JjfZ/kGlP0yfvll19Qr149mJmZITo6GvPmzVPJND0iIiIiIiIi+rCYmKJP3o0bNzB9+nQ8fvwY5cuXx6hRozB+/HhVh0VERERERERE74lT+Yj+h3AqHxEREREREb2Lj/V9koufExERERERERGRSjAxRUREREREREREKsHEFBERERERERERqQQTU0REREREREREpBL8VT6i/0Ed5uyDho6eqsMg+qD2TWit6hCIiIiIiKiEOGKKiIiIiIiIiIhUgokpIiIiIiIiIiJSCSam6IvQtGlTDB8+vMTHKRQK7Nix44PHQ0RERERERERFY2KK3smFCxfg4+MDW1tb6OrqwtHREUuWLCnyOHt7eygUCtlr9uzZhR7j7++f5xiFQgEnJ6f3Po+kpCS0bNnyvdsBgMjISCgUCjx9+vSDtFcUf39/tG/fvlT6IiIiIiIiIvoYuPg5vZOYmBiULVsWv/76K2xtbXH8+HH0798f6urqCAwMLPTYqVOnol+/ftK2gYFBofWXLFkiS169evUKLi4u6NSp0/udBABLS8v3bqOkMjMzoaWlVer9EhEREREREX1qOGLqM/H8+XP4+vpCqVTCysoKCxYskKav/fzzz6hRo4ZUd8eOHVAoFFixYoVU1rx5c/z000/S9s6dO1GnTh3o6OigYsWKmDJlCl69eiXtVygUWLNmDTp06AA9PT1UqVIFu3btkvb37t0bS5YsgYeHBypWrIgePXqgV69e2LZtW5HnYmBgAEtLS+mlr69faH0jIyNZ/bNnz+LJkyfo1auXrN6rV68Q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3LixbN0ihUIBd3d3KBQKNG7cGMDrZJWhoSFcXV1lU8m8vLywe/du7N+/H/Xq1cNXX32FRYsW5fmVu8Js3boVDx8+xK+//gorKyvpVa9ePalOeno64uPjpYSJtrY2wsPD4eHhAScnJ8yYMQMjRozAqlWriuwvJSUFf/zxR4GjpQDA19cXL168QP369TF48GAMGzYM/fv3L/Y5FUd2djYGDx4MR0dHtGjRAlWrVsUvv/wCALCxsZEWlrewsEBgYGChba1btw6vXr1C3bp1MXz4cEyfPl2238zMDIcPH0ZaWho8PDxQt25drF69Wppq2K9fPzg4OMDV1RXm5uaIjo7+oOdKRERERERE9LEpxJuL3NBnpWnTpqhVqxYWL16s6lDoM5GamgojIyN8/cNmaOjoqTocog9q34TWqg6BiIiIiOiLlft9MiUl5YMuE8MRU0REREREREREpBJMTBERERERERERkUq8+0+TkcpFRkaqOgQiIiIiIiIionfGxBTR/6DtY70+6JxgIiIiIiIionfBqXxERERERERERKQSTEwREREREREREZFKcCof0f+g2HuxUD5XqjoMIiIiIqIvUhm9MihvVF7VYRB9FpiYIvof5LHeA9BRdRRERERERF8mHQ0dxAfGMzlFVAycykdERERERET0Ab189RKP0h+pOgyizwITU0REREREREREpBJMTH1CmjZtiuHDhwMA7O3tsXjxYpXG87nw9/dH+/btS3wcrzERERERERGRajExRSUWHx+PZs2awcLCAjo6OqhYsSJ++uknZGVlFXrc0KFDUbduXWhra6NWrVrF7m/jxo1wcXGBnp4erKys0Lt3byQnJ7/nWQBnzpxB//7937sdAEhISIBCoUBsbOwHaa8okydPLtE1JCIiIiIiIvoUMTFFJaapqQlfX1/s378f8fHxWLx4MVavXo1JkyYVeWzv3r3RpUuXYvcVHR0NX19f9OnTB1euXMGWLVtw+vRp9OvX731OAQBgbm4OPT29926nJDIzM0u1PyIiIiIiIqJPGRNTn4mFCxeiZs2a0NfXh62tLQYNGoS0tDRpf0hICIyNjbF79244ODhAT08PHTt2RHp6OkJDQ2Fvbw8TExMMHToU2dnZ0nFhYWFwdXWFgYEBLC0t0a1bNzx48KDQWCpWrIhevXrBxcUFdnZ2aNu2Lbp3746oqKhCj1u6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6tevD2dnZ0yZMgWBgYFYs2aNVCclJQXx8fGy4/r27YvatWtj5cqVuH79OmrXro3atWvj33//LbAvf39/LFy4ED///DNq1KiBTp06wcHBAdu2bZPV8/T0RJUqVdCkSRN06dIFbdu2xeTJk0t2QYqgpaWF8ePHw9nZGU2aNIG6ujrCw8MBvL4mS5cuxcqVK2FtbY127doV2taCBQtga2sLd3d3dOvWDUFBQbI1srS0tLB//36ULVsWrVq1Qs2aNTF79mxptNj333+PFi1aoFmzZjA3N8dvv/32Qc+ViIiIiIiIqDQoxNsL3RDRFys1NRVGRkbAOAA6qo6GiIiIiOjLFdM/BnWs6qg6DKIPJvf7ZEpKirQu9IfAEVNERERERERERKQSTEwREREREREREZFKMDFFREREREREREQqwcQUERERERER0Qeko6GDMnplVB0G0WdBQ9UBEFHpO9rrKJQGSlWHQURERET0RSqjVwbljcqrOgyizwITU0T/g2pZ1vqgv6JARERERERE9C44lY+IiIiIiIiIiFSCiSkiIiIiIiIiIlIJJqaIiIiIiIiIiEglmJgiIiIiIiIiIiKVYGKKiIiIiIiIiIhUgokpIiIiIiIiIiJSCQ1VB0BEpUcIAQBITU1VcSRERERERET0Ocn9Hpn7vfJDYWKK6H9IcnIyAMDW1lbFkRAREREREdHnKDk5GUZGRh+sPSamiP6HmJqaAgASExM/6IOESNVSU1Nha2uLv//+G4aGhqoOh+iD4v1NXyre2/Sl4r1NX6qUlBSUL19e+l75oTAxRfQ/RE3t9bJyRkZG/EuSvkiGhoa8t+mLxfubvlS8t+lLxXubvlS53ys/WHsftDUiIiIiIiIiIqJiYmKKiIiIiIiIiIhUgokpov8h2tramDRpErS1tVUdCtEHxXubvmS8v+lLxXubvlS8t+lL9bHubYX40L/zR0REREREREREVAwcMUVERERERERERCrBxBQREREREREREakEE1NERERERERERKQSTEwRfWGWL18Oe3t76OjooEGDBjh9+nSh9bds2YJq1apBR0cHNWvWRERERClFSlQyJbm3V69eDXd3d5iYmMDExATNmzcv8rNApEolfXbnCg8Ph0KhQPv27T9ugETvqKT39tOnTzF48GBYWVlBW1sbVatW5b9N6JNU0nt78eLFcHBwgK6uLmxt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VGwf6/skR0wREREREREREZFKMDFFREREREREREQqwcQUERERERERERGpBBNTRERERERERESkEkxMERERERERERGRSjAxRUREREREREREKsHEFBERERERERERqQQTU0REREREREREpBJMTBERERERERERkUowMUVERERERERERCrBxJQKNW3aFMOHDwcA2NvbY/HixSqN52NSKBTYsWOHqsP4rPEaEhERERER0ZeGiSmSSU5ORosWLWBtbQ1tbW3Y2toiMDAQqampqg4N/v7+aN++fan199dff8Hb2xvW1tbFTgpFRkZCoVDked27d6/Q49LS0hAYGIhy5cpBV1cX1atXx4oVKz7QmRARERERERF9mjRUHQB9WtTU1NCuXTtMnz4d5ubmuHnzJgYPHozHjx9j06ZNqg6vVD1//hwuLi7o3bs3vvvuuxIdGx8fD0NDQ2m7bNmyhdYfOXIkDh8+jF9//RX29vbYv38/Bg0aBGtra7Rt2/ad4iciIiIiIiL61HHE1Cdq4cKFqFmzJvT19WFra4tBgwYhLS1N2h8SEgJjY2Ps3r0bDg4O0NPTQ8eOHZGeno7Q0FDY29vDxMQEQ4cORXZ2tnRcWFgYXF1dYWBgAEtLS3Tr1g0PHjyQ9puYmGDgwIFwdXWFnZ0dPD09MWjQIERFRRUZ87p16+Dk5ARtbW1YWVkhMDBQtv/Ro0fo0KED9PT0UKVKFezatUval52djT59+qBChQrQ1dWFg4MDlixZIu2fPHkyQkNDsXPnTmkUUmRkJADg9OnTqF27NnR0dODq6ort27dDoVAgNja2WG0XpGXLlpg+fTo6dOhQZN23lS1bFpaWltJLTa3wj9rx48fh5+eHpk2bwt7eHv3794eLiwtOnz4tq5eUlISWLVtCV1cXFStWxNatW0scGxEREREREdGngompT5SamhqWLl2KK1euIDQ0FIcPH8aYMWNkddLT07F06VKEh4dj7969iIyMRIcOHRAREYGIiAiEhYVh5cqVsuRFVlYWpk2bhgsXLmDHjh1ISEiAv79/gXH8+++/2LZtGzw8PAqNNzg4GIMHD0b//v1x6dIl7Nq1C5UrV5bVmTJlCjp37oyLFy+iVatW6N69Ox4/fgwAyMnJQbly5bBlyxZcvXoVEydOxA8//IDNmzcDAIKCgtC5c2e0aNECSUlJSEpKgpubG9LS0tCm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EZFKMDFFREREREREREQqwcQUERERERERERGpBBNTH1HTpk0xfPhwAIC9vT0WL16s0ng+JoVCgR07dqg6jC9aSEgIjI2NVR0GERERERER0QfDxNT/mOTkZLRo0QLW1tbQ1taGra0tAgMDkZqaqurQ4O/vj/bt25daf3/99Re8vb1hbW1d7MRaZGQkFApFnte9e/cKPS6/YxQKBebNmyfVefz4Mbp37w5DQ0MYGxujT58+SEtLe9/TJCIiIiIiIvpkMTH1P0ZNTQ3t2rXDrl27cP36dYSEhODgwYMICAhQdWil7vnz53BxccHy5ctLfGx8fDySkpKkV9myZQut/2bdpKQkrFu3DgqFAt9//71Up3v37rhy5QoOHDiA3bt346+//kL//v1LHBsRERERERHR54KJKRVZuHAhatasCX19fdja2mLQoEGy0TG507Z2794NBwcH6OnpoWPHjkhPT0doaCjs7e1hYmKCoUOHIjs7WzouLCwMrq6uMDAwgKWlJbp164YHDx5I+01MTDBw4EC4urrCzs4Onp6eGDRoEKKiooqMed26dXBycoK2tjasrKwQGBgo2//o0SN06NABenp6qFKlCnbt2iXty87ORp8+fVChQgXo6urCwcEBS5YskfZPnjwZoaGh2LlzpzSaKDIyEgBw+vRp1K5dGzo6OnB1dcX27duhUCgQGxtbrLYL0rJlS0yfPh0dOnQosu7bypYtC0tLS+mlplb4R+nNupaWlti5cyeaNWuGihUrAgDi4uKwd+9erFmzBg0aNEDjxo2xbNkyhIeH499//5W1tWPHDlSpUgU6Ojrw8vLC33//XeL4iYiIiIiIiD4FTEypiJqaGpYuXYorV64gNDQUhw8fxpgxY2R10tPTsXTpUoSHh2Pv3r2IjIxEhw4dEBERgYiICISFhWHlypXYunWrdExWVhamTZuGCxcuYMeOHUhISIC/v3+Bcfz777/Ytm0bPDw8Co03ODgYgwcPRv/+/XHp0iXs2rULlStXltWZMmUKOnfujIsXL6JVq1bo3r07Hj9+DADIyclBuXLlsGXLFly9ehUTJ07EDz/8gM2bNwMAgoKC0LlzZ7Ro0UIaVeTm5oa0tDS0adMG1atXR0xMDCZPnoygoCBZv0W1/THUqlULVlZW+OabbxAdHV2iY+/fv489e/agT58+UtmJEydgbGwMV1dXqax5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6dYzAPjmm29w9+5daTvn3L99K5ydnR2+/fZbfPPNN3j69Cnat2+PJUuWvFd87zIwMMCMGTNw48YNqKuro379+oiOjpaSc7Nnz8bw4cOxYsUKWFlZITExMd++goKCcOfOHbRv3x5GRkaYNGmS0owpANi8eTNGjhwJb29vvHjxAnZ2dtITHRs1aoSAgAB4eXnhyZMnCA4ORkhISKmOl4iIiIiIiKisycS7C90Q0WcrNTUVRkZGSElJUVoLjYiIiIiIiKggZfV98oM8lY+IiIiIiIiIiOhdTEwREREREREREZFKMDFFREREREREREQqwcQUERERERERERGpBBNTRERERERERESkEkxMERERERERERGRSjAxRUREREREREREKsHEFBERERERERERqQQTU0REREREREREpBJMTBERERERERERkUowMfURad68OYYOHQoAsLW1xbx581Qaz6fCz88PnTp1KnY7nmMiIiIiIiIi1WJiiootISEBLVq0QMWKFaGjo4OqVati3LhxyMrKKrDd4MGDUa9ePWhra6NOnTpFPl5kZCScnZ0hl8thYWGBH374AU+ePHnPUQCnT59Gv3793rsfAEhMTIRMJkN8fHyp9FeYkJCQYp1DIiIiIiIioo8RE1NUbJqamvDx8cHevXuRkJCAefPmYcWKFQgODi607Q8//AAvL68iHys2NhY+Pj7w9/fH5cuXsXHjRpw6dQp9+/Z9nyEAAMzMzCCXy9+7n+LIzMz8oMcjIiIiIiIi+pgxMfWJmDNnDmrXrg09PT1YW1tjwIABSEtLk/aHh4fD2NgYO3fuhL29PeRyObp27Yr09HRERETA1tYWJiYmGDx4MLKzs6V2a9euhaurKwwMDGBubo7u3bvj4cOHBcZStWpV9O7dG87OzrCxsUGHDh3Qo0cPHDlypMB2CxYswMCBA1G1atUij/v48eOwtbXF4MGDUaVKFTRp0gT9+/fHqVOnctUNDQ2FmZkZDA0NERAQUGgS6N1b+WQyGVauXInOnTtDLpejevXq2LFjh7T/33//RY8ePWBmZgZdXV1Ur14dq1evBgBUqVIFAODi4gKZTIbmzZsD+L/bDKdMmQJLS0vY29tLx9q2bZtSPMbGxggPD5e2//rrL3h7e8PU1BR6enpwdXXFyZMnER4ejtDQUJw/fx4ymQwymUypHREREREREdGnQkPV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aAEzMzP8/vvvpTpWIiIiIiIiog9BJt5d6IaIPlupqakwMjJCSkqKtI4XERERERERUWHK6vskZ0wREREREREREZFKMDFFREREREREREQqwcQUERERERERERGpBBNTRERERERERESkEkxMERERERERERGRSjAxRUREREREREREKsHEFBERERERERERqQQTU0REREREREREpBJMTBERERERERERkUowMUVERERERERERCrBxBQREREREREREakEE1NEH1h4eDiMjY2L3c7Pzw+dOnUq9XiIiIiIiIiIVIWJKaI8ZGVlYfTo0ahduzb09PRgaWkJHx8f/PPPP4W2PXDgABo1agQDAwOYm5tj9OjReP369XvHNH/+fISHh0vbzZs3x9ChQ9+7XyIiIiIiIiJVYWKKKA/p6ek4e/Ysxo8fj7Nnz2LLli1ISEhAhw4dCmx3/vx5fPPNN2jTpg3OnTuH9evXY8eOHRgzZsx7x2RkZFSimVZEREREREREHysmpqhEnj9/jh49ekBPTw8WFhaYO3eu0gyejIwMjBw5ElZWVtDT00PDhg0RExMjtc+5nW3nzp2wt7eHXC5H165dkZ6ejoiICNja2sLExASDBw9Gdna21M7W1haTJ0+Gj48P9PX1YWNjgx07duDRo0fo2LEj9PX14eTkhDNnzkhtnjx5Am9vb1hZWUEul6N27dr4/fffCxyfkZER9u3bh27dusHe3h5ffvklFi1ahLi4OCQlJeXbbv369XBycsKECRNgZ2cHd3d3zJgxA4sXL8bz58+V6m7btg3Vq1eHjo4OPDw8cO/evQJjevtWPj8/Pxw+fBjz58+HTCaDTCZDYmJige2JiIiIiIiIPjZMTFGJDB8+HLGxsdixYwf27duHI0eO4OzZs9L+wMBAHD9+HFFRUbhw4QK+++47tGnTBjdu3JDqpKenY8GCBYiKisLu3bsRExODzp07Izo6GtHR0Vi7di2WL1+OTZs2KR177ty5aNy4Mc6dO4d27dqhV69e8PHxQc+ePXH27FlUq1YNPj4+EEIAAF69eoV69eph165duHTpEvr164devXrh1KlTxRpzSkoKZDJZgbOWMjIyoKOjo1Smq6uLV69eIS4uTmnsU6ZMwZo1axAbG4tnz57h+++/L3Is8+fPh5ubG/r27Yvk5GQkJyfD2to6z3hSU1OVXkREREREREQf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59Dn7fHjx7CwsMCECRMwfvx4VYfzn/dffj9CQkIQGhqKR48ecdHmj1yrVq1gaWmJtWvXltkx+LeoZFxcXNC8eXPMnTtX1aEQlSquMUVERCrl4+ODadOmYe3atRg7dqyqwwEArFixAlWrVs1zkXaiT0l4eDiys7PRq1cvVYdC4PtBn4apU6di/fr1uR40Qqq1e/du3LhxA0FBQaoOhajUcY0pIiJSudGjR2P06NGqDgNRUVG4cOECdu3ahfnz55fK0+CIVOHgwYO4cuUKpkyZgk6dOhX4xDMqe3w/6FPSsGFDZGZmqjoMekebNm3yfEgE0eeAiSkiIqL/z9vbG/r6+vD398eAAQNUHQ5RiU2cOBHHjh1D48aNpaeekerw/SAiIsof15giIiIiIiIiIiKV4BpTRERERERERESkEkxMERERERERERGRSnCNKaL/EIVCgX/++QcGBgZc1JmIiIiIiIiKTAiB58+fw9LSEmpqpTfPiYkpov+Qf/75B9bW1qoOg4iIiIiIiD5R9+7dQ6VKlUqtPyamiP5DDAwMALz5RWJoaKjiaIiIiIiIiOhTkZqaCmtra+l7ZWlhYoroPyTn9j1DQ0MmpoiIiIiIiKjYSntZGC5+TkREREREREREKsHEFBERERERERERqQQTU0REREREREREpBJMTBERERERERERkUowMUVERERERERERCrBxBQREREREREREakEE1NERERERERERKQSTEwREREREREREZFKMDFFREREREREREQqwcQUERERERERERGpBBNTRERERERERESkEkxMqUhMTAxkMhmePXv2wY8dHh4OY2Pj9+7H1tYW8+bNK1abxMREyGQyxMfHv/fxiYiIiIiIiOjTxsSUijRq1AjJyckwMjIqtK4qkljNmzeHTCbL9WrXrt179WttbY3k5GTUqlWrVOIsrSRbUTVv3hxDhw4tlb5iYmJQt25daGtrw87ODuHh4QXWT0hIQIsWLVCxYkXo6OigatWqGDduHLKyskolHiIiIiIiIqIPTUPVAfxXaWlpwdzcvFT7zMzMhJaWVqn0tWXLFmRmZkrbT548gbOzM7777rv36lddXb3Ux10UpXluSsOdO3fQrl07BAQEIDIyEgcOHECfPn1gYWEBDw+PPNtoamrCx8cHdevWhbGxMc6fP4++fftCoVBg6tSpH3gERERERERE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woW4ffs21q5dKy2KXhKrVq1Cp06dUK5cuTz3x8bGYsaMGbh+/ToWL16MjRs3YsiQISU+3rtevnyJwMBAxMTE4O7du4iNjcXp06elc2Nra4u0tDQcOHAAjx8/zvN2xRx2dnawtrZGSEgIbty4gV27dmH27NlKdYKCgnD69GkMGDAAFy5cwLVr17B06VI8fvxYOt7JkyeRmJiIx48f5zn7LUePHj2wa9cu/Prrr0qzpYA3tznevn0bP/30E65du4YlS5Zgw4YNGDZsmFRn0aJFSrcVRkZGYsOGDbh69Spu376NDRs2ICgoCF5eXrlm6xERERERERF9CpiY+khkZ2dj4MCBcHBwQJs2bVCjRg1pAW4rKyuEhoZizJgxqFixIgIDA/Ptx9nZGXPmzMH06dNRq1YtREZGYtq0aSWKKSEhAUePHi3wNr4RI0bgzJkzcHFxweTJkzFnzhx4eHiU6Hh5UVdXx5MnT+Dj44MaNWqgW7duaNu2rTSjqVGjRggICICXlxfMzMxyLQT/Nk1NTfz++++4du0anJycMH36dEyePFmpTo0aNbB3716cP38eDRo0gJubG7Zv3w4NjTeTC0eOHAl1dXU4OjrCzMwMSUlJ+R6vZcuWMDU1RUJCArp37660r0qVKti1axf27dsHZ2dnzJ49GytXrlQ6d48fP1ZaT0tDQwPTp09HgwYN4OTkhNDQUAQGBkq3DxIRERERERF9amTi7QV3iOizlpqaCiMjI6SkpHC9KSIiIiIiIiqysvo+yRlTRERERERERESkEkxMERERERERERGRSjAxRUREREREREREKsHEFBERERERERERqQQTU0REREREREREpBJMTBERERERERERkUowMUVERERERERERCrBxBQREREREREREakEE1NERERERERERKQSTEwREREREREREZFKMDGlQs2bN8fQoUMBALa2tpg3b55K4ylLMpkM27ZtU3UYnzSeQyIiIiIiIvrcMDFFSp48eYI2bdrA0tIS2trasLa2RmBgIFJTU1UdGvz8/NCpU6cPdrw///wTnp6esLS0LHJSKCYmBjKZLNfr/v37BbZLS0tDYGAgKlWqBF1dXTg6OmLZsmWlNBIiIiIiIiKij5OGqgOgj4uamho6duyIyZMnw8zMDDdv3sTAgQPx9OlTrFu3TtXhfVAvXryAs7MzfvjhB3z77bfF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0r6DgoKwYsUKhIWF4fr160hOTsbatWurHVvz5s2RmJiIw4cP49q1a5g7dy4uXLigUMfS0hK//fYbMjIy8M8//6CkpAQjRoyARCLBuHHjkJaWhri4OCxfvrzGfVekoKAAKSkp4myqW7duISUlRWF5YUBAADw8PMTj1atX48CBA8jMzMTly5cxdepUHDt2DJMnT37l9fLz87F79258+eWXL52zsbGBq6srxo0bh/PnzyMhIQG+vr4YNmwYGjduLNZTU1PDV199hXPnziEpKQleXl7o0qWLmKgiIiIiIiIiep8wMaUEdnZ2WLlyJZYsWYI2bdogKioKixcvfu1+jYyMEBERgd27d6NVq1YIDQ19KYmjpaWFTZs2oVu3brCxscG0adMwYMAAxMbGVtm3p6cnVq9eje+//x6tW7dG//79cf369WrHNmHCBHz22Wdwd3dH586dkZubqzDDCQDGjRsHa2trODg4wMjICAkJCZDJZPi///s/XLp0Cfb29vj222+xZMmSGvddkcTERNjb28Pe3h4A4OfnB3t7e8ybN0+sk5OTo5CoKi4uxvTp09G2bVv07NkTqamp4n5hrxIdHQ1BEDB8+PAKz0dFRaFly5ZwdnZGv3790K1bN2zcuFGhjlQqxaxZszBixAg4OTlBJpO9tCyQiIiIiIiI6H0hEQRBUHYQRDWRlZWFJk2a4OLFi2jXrp2yw3mv5OfnQ09PD3l5edDV1VV2OERERERERPSeeFO/JzljioiIiIiIiIiIlIKJKSIiIiIiIiIiUgq+lY/eO5aWluAKVCIiIiIiIqL3H2dMERERERERERGRUjAxRURERERERERESsHEFBERERERERERKQUTU0REREREREREpBRMTBERERERERERkVIwMUVERERERERERErBxBQRERERERERESkFE1NERERERERERKQUNUpM9erVC1OnTgUAWFpaYvXq1W8gJKL3m5eXFwYNGqTsMIiIiIiIiIjeeR/kjKmMjAz07t0bjRo1gqamJpo2bYo5c+agpKSk0ja5ublwdXVF48aNoaGhATMzM/j6+iI/P/8tRl63IiIioK+vr+ww3ku5ubn46KOPIJFI8PDhQ4Vz3333HWxsbKClpQVra2ts27btrcS0ceNG9OrVC7q6uhXGRURERERERPS+UVV2AG+CmpoaPDw80L59e+jr6yM1NRXj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XhXbZWZmoqCgAHfu3MGTJ0/EGTitWrUSkzw1paOjA39/f0ybNg1yuRzdunVDXl4eEhISoKurC09Pz1r1q62tjYkTJ2LGjBkwNDSEubk5li5disLCQnh7ewN4lrSTSCSIjY1Fv379oKWlpbDE7nmTJk3C6tWr8dVXX8HX1xcZGRkIDAyEn5+fmGysjTNnzuDcuXPo3bs3dHR0cObMGXHzdwMDA7FeWloaiouLcf/+fTx69Ei89+3atRPraGpqwtPTE8uXL0d+fj6mTJmCoUOHvvJNfjWxcuVKmJiYwN7eHioqKti9ezeMjY2hr68P4NmyzaNHj8LJyQkaGhoKY3hRnz59EBERATc3N+jr62PevHkKSdGePXuiR48e+Pzzz7Fy5UpYWVnh6tWrkEgkcHV1haWlJQoKCnD06FHY2dlBKpWKsw+JiIiIiIiI3hdvPDFlZ2eHlStXYsmSJQgICECPHj2wePFieHh4vFa/RkZGiIiIwDfffIOwsDC0b98ey5cvx4ABA6psp6qqiiVLluDatWsQBAEWFhbw9fXFtGnTxDp5eXnim9jKffnllwpvyrO3twcA3Lp1S3wzW22EhITAyMgIixcvxs2bN6Gvr4/27dvjm2++qXWfwLM33cnlcowePRqPHj2Cg4MDDh8+LCZLTE1NERwcjNmzZ2PMmDHw8PBAREREhX2ZmpoiLi4OM2bMgJ2dHQwNDeHt7Y05c+a8VowaGhqIjo5GUFAQioqK0KRJE0ybNk1h6RkA9OvXD7///rt4XH7vn18KZ2Vlhc8++wz9+vXD/fv30b9/f3z//fevFd+LdHR0sHTpUly/fh316tVDx44dERcXJybnVqxYAT8/P2zatAmmpqbIysqqtK+AgADcunUL/fv3h56eHkJCQhRmTAHATz/9BH9/fwwfPhyPHz+GlZWV+EbHrl27wsfHB+7u7sjNzUVgYCCCgoLqdLxEREREREREb5pEeHGjGyL6YOXn50NPTw95eXkKe6ERERERERERVeVN/Z58K2/lIyIiIiIiIiIiehETU0REREREREREpBRMTBERERERERERkVIwMUVERERERERERErBxBQRERERERERESkFE1NERERERERERKQUTEwREREREREREZFSMDFFRERERERERERKwcQUEREREREREREpBRNTRERERERERESkFExM0Vvl5eWFQYMG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+fnhzp07uHPnjrgBO/Bsb6uWLVvizz//BACcOXMGq1evRmpqKm7evImoqChMmzYNo0aNgoGBwSuv+cMPP6Bz584KbyB8XnZ2Nvz8/JCRkYEff/wRa9euxddff13tMVXH1KlTcfjwYdy6dQvJyck4fvw4bGxsAAAWFhaQSCSIjY3F33//jYKCgkr70dLSQpcuXRAaGor09HScOHHipfvv6+uL/Px8DBs2DImJibh+/Tq2b9+OjIwMAIClpSV+++03ZGRk4J9//qk0gUVERERERET0rmJi6j2xbNkydO/eHW5ubvj444/RrVs3hX2LJBIJunfvDolEgm7dugF4lqzS1dWFg4ODwlIyFxcXxMbG4pdffkHHjh3RpUsXrFq16qW33FVlz549+Pvvv/Hf//4XJiYm4qdjx45incLCQmRkZIgJEw0NDURHR6Nnz55o3bo1Fi5ciGnTpmHjxo2vvF5eXh5++umnSmdLAYCHhweePHmCTp06YfLkyfj6668xfvz4ao+pOsrKyjB58mTY2NjA1dUVLVq0wPfffw8AMDU1FTeWb9SoEXx9favsa8uWLSgtLUWHDh0wdepULFiwQOF8/fr1cezYMRQUFKBnz57o0KEDNm3aJC41HDduHKytreHg4AAjIyMkJCTU6ViJiIiIiIiI3jSJ8PwmN/Re6dWrF9q1a4fVq1crOxR6T+Tn50NPTw+YDUDzldWJiIiI3rik8Ulob9Je2WEQEdErlP+ezMvLq9NtYjhjioiIiIiIiIiIlIKJKSIiIiIiIiIiUorav5qMlC4+Pl7ZIRARERERERER1RpnTBERERERkVJoqmqigbSBssMgIiIl4owpon+hE2NOQKYjU3YYRERE9C/XQNoA5nrmyg6DiIiUiIkpon+hdsbt6vQtCkRERERERES1waV8RERERERERESkFExMERERERERERGRUjAxRURERERERERESsHEFBERERERERERKQUTU0REREREREREpBRMTBERERERERERkVKoKjsAInp7BEEAAOTn5ys5EiIiIiIiInqflP+OLP9dWVeYmCL6F8nNzQUAmJmZKTkSIiIiIiIieh/l5uZCT0+vzvpjYoroX8TQ0BAAkJ2dXaf/ICFStvz8fJiZmeH27dvQ1dVVdjhEdYrPN32o+GzTh4rPNn2o8vLyYG5uLv6urCtMTBH9i6ioPNtW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xo4dO/D06VNs27YNbdq0QZ8+fbBu3Tps374dd+/eFftSV1fHli1b0Lp1a3z66aeYP38+wsLCxMTT559/js8++wxWVlZo164dtmzZgkuXLiEtLU0hJl9fX3z++eewsbFBeHg49PT08MMPP9R4bDKZDFpaWuLsMGNjY6irq+Ozzz4DABw4cECsGxERIe69RURE9CFjYoqIiIiIlC42NhYymQyampro27cv3N3dERQUBABo27atwr5S6enpsLOzg7a2tljm5OQEuVyOjIwMsczOzg5SqVQ8dnR0REFBAW7fvg0AuH79OoYPH46mTZtCV1cXlpaWAIDs7GyF2BwdHcXvqqqqcHBwQHp6ep2NXVNTE6NHj8aWLVsAAMnJybh8+TK8vLzq7BpERETvKm5+TkRERERK17t3b4SHh0NdXR2NGzeGqur//jP1+QRUXXJzc4OFhQU2bdqExo0bQy6Xo02bNgr7UL0tX375Jdq1a4c//vgDW7duRZ8+fWBhYfHW4yAiInrbOGOKiIiIiJROW1sbVlZWMDc3V0hKVcTGxgapqal4/PixWJaQkAAVFRVxc3QASE1NxZMnT8Tjs2fPQiaTwczMDLm5ucjIyMCcOXPg7OwMGxsbPHjwoMLrnT17VvxeWlqKpKQk2NjY1Gqc6urqKCsre6m8bdu2cHBwwKZNm7Bjxw6MHTu2Vv0TERG9b5iYIiIiIqL3ysiRI6GpqQlPT09cvnwZx48fx1dffYXRo0ejUaNGYr3i4mJ4e3sjLS0NcXFxCAwMhK+vL1RUVGBgYID69etj48aNyMzMxLFjx+Dn51fh9b777jvs27cPV69exeTJk/HgwYNaJ44sLS3x22+/ISMjA//88w9KSkrEc19++SVCQ0MhCAIGDx5cq/6JiIjeN0xMEREREdF7RSqV4vDhw7h//z46duyIIUOGwNnZGevWrVOo5+zsjObNm6NHjx5wd3fHgAEDxH2rVFRUEB0djaSkJLRp0wbTpk3DsmXLKrxeaGgoQkNDYWdnh1OnTiEmJgYNGjSoVezjxo2DtbU1HBwcYGRkhISEBPHc8OHDoaqqiuHDh0NTU7NW/RMREb1vJIIgCMoOgoiIiIjo3y4rKwvNmjXDhQsX0L59e2WHQ0RE9FYwMUVEREREpEQlJSXIzc2Fv78/bt26pTCLioiI6EPHpXxEREREREqUkJAAExMTXLhwAevXr1d2OERERG8VZ0wREREREREREZFScMYUEREREREREREpBRNTRERERERERESkFExMERERERERERGRUjAxRURERERERERESsHEFBERERERERERKQUTU0REREREREREpBRMTBERERERERERkVIwMUVERERERERERErBxBQRERERERERESnF/wf2BvIdFbpLtQAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Visualize routing probabilities for all queries\n", + "import matplotlib.pyplot as plt\n", + "\n", + "fig, axes = plt.subplots(len(EXAMPLE_QUERIES), 1, figsize=(12, 3*len(EXAMPLE_QUERIES)))\n", + "\n", + "for idx, query in enumerate(EXAMPLE_QUERIES):\n", + " probs = get_routing_probabilities(query['query'])\n", + " \n", + " ax = axes[idx] if len(EXAMPLE_QUERIES) > 1 else axes\n", + " models = list(probs.keys())\n", + " values = list(probs.values())\n", + " \n", + " bars = ax.barh(models, values, color='steelblue')\n", + " ax.set_xlim(0, 1)\n", + " ax.set_xlabel('Probability')\n", + " ax.set_title(f\"Query {idx+1}: {query['query'][:50]}... ({query['task_type']})\")\n", + " \n", + " # Highlight the chosen model\n", + " max_idx = values.index(max(values))\n", + " bars[max_idx].set_color('green')\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. Batch Query Routing\n", + "\n", + "### Option 1: Route queries from configuration file\n", + "\n", + "The router automatically loads test data from the path specified in `query_data_test` in the YAML config." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loaded 20 test samples\n" + ] + } + ], + "source": [ + "# Load test data for batch routing\n", + "if router.query_data_test is not None:\n", + " test_data = router.query_data_test[:20] # Use first 20 samples\n", + " print(f\"Loaded {len(test_data)} test samples\")\n", + "else:\n", + " print(\"No test data available. Using example queries.\")\n", + " test_data = EXAMPLE_QUERIES" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Routed 20 queries\n", + "\n", + "Routing Distribution:\n", + " qwen2.5-7b-instruct: 12 (60.0%)\n", + " gemma-2-9b-it: 6 (30.0%)\n", + " llama-3.1-nemotron-51b-instruct: 2 (10.0%)\n" + ] + } + ], + "source": [ + "# Batch routing (route-only, no API calls)\n", + "def batch_route_only(queries):\n", + " \"\"\"Route multiple queries without calling APIs.\"\"\"\n", + " results = []\n", + " for query in queries:\n", + " result = router.route_single(query)\n", + " results.append(result)\n", + " return results\n", + "\n", + "# Route batch\n", + "batch_results = batch_route_only(test_data)\n", + "\n", + "print(f\"Routed {len(batch_results)} queries\")\n", + "\n", + "# Show routing distribution\n", + "from collections import Counter\n", + "model_counts = Counter(r['model_name'] for r in batch_results)\n", + "\n", + "print(\"\\nRouting Distribution:\")\n", + "for model, count in model_counts.most_common():\n", + " percentage = count / len(batch_results) * 100\n", + " print(f\" {model}: {count} ({percentage:.1f}%)\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Option 2: Load queries from your own file\n", + "\n", + "You can also load queries from a custom JSONL file and pass them to the router." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "\n", + "# Method 1: Load from custom JSONL file\n", + "def load_queries_from_file(file_path):\n", + " \"\"\"Load queries from a JSONL file.\"\"\"\n", + " queries = []\n", + " with open(file_path, 'r', encoding='utf-8') as f:\n", + " for line in f:\n", + " if line.strip():\n", + " queries.append(json.loads(line))\n", + " return queries\n", + "\n", + "# Example: Load from the default query test file\n", + "QUERY_FILE = \"data/example_data/query_data/default_query_test.jsonl\"\n", + "\n", + "if os.path.exists(QUERY_FILE):\n", + " file_queries = load_queries_from_file(QUERY_FILE)\n", + " print(f\"Loaded {len(file_queries)} queries from file\")\n", + " print(f\"\\nSample query: {file_queries[0]}\")\n", + " \n", + " # Route queries from file\n", + " file_results = router.route_batch(batch=file_queries[:10])\n", + " \n", + " print(f\"\\nRouted {len(file_results)} queries from file:\")\n", + " for i, result in enumerate(file_results[:3], 1):\n", + " print(f\" {i}. Query: {result.get('query', '')[:50]}...\")\n", + " print(f\" Routed to: {result['model_name']}\")\n", + "else:\n", + " print(f\"File not found: {QUERY_FILE}\")\n", + " print(\"You can create your own JSONL file with format:\")\n", + " print(' {\"query\": \"Your question here\"}')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Method 2: Save routing results to file\n", + "def save_results_to_file(results, output_path):\n", + " \"\"\"Save routing results to a JSONL file.\"\"\"\n", + " with open(output_path, 'w', encoding='utf-8') as f:\n", + " for result in results:\n", + " f.write(json.dumps(result, ensure_ascii=False) + '\\n')\n", + " print(f\"Results saved to: {output_path}\")\n", + "\n", + "# Example: Save results\n", + "OUTPUT_FILE = \"outputs/knnrouter_results.jsonl\"\n", + "os.makedirs(os.path.dirname(OUTPUT_FILE), exist_ok=True)\n", + "\n", + "if 'file_results' in dir() and file_results:\n", + " save_results_to_file(file_results, OUTPUT_FILE)\n", + " \n", + " # Verify saved file\n", + " print(f\"\\nVerifying saved file:\")\n", + " with open(OUTPUT_FILE, 'r') as f:\n", + " first_line = json.loads(f.readline())\n", + " print(f\"First result: {first_line}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Visualize routing distribution\n", + "import matplotlib.pyplot as plt\n", + "\n", + "models = list(model_counts.keys())\n", + "counts = list(model_counts.values())\n", + "\n", + "plt.figure(figsize=(10, 6))\n", + "plt.pie(counts, labels=models, autopct='%1.1f%%', startangle=90)\n", + "plt.title('Query Routing Distribution')\n", + "plt.axis('equal')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7. Full Inference with API Calls (Optional)" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "API keys available: True\n" + ] + } + ], + "source": [ + "# Check if API keys are available\n", + "api_available = bool(\n", + " os.environ.get('OPENAI_API_KEY') or \n", + " os.environ.get('ANTHROPIC_API_KEY') or\n", + " os.environ.get('API_KEYS')\n", + ")\n", + "\n", + "print(f\"API keys available: {api_available}\")\n", + "\n", + "if not api_available:\n", + " print(\"\\nTo enable full inference with API calls, set one of:\")\n", + " print(\" - OPENAI_API_KEY\")\n", + " print(\" - ANTHROPIC_API_KEY\")\n", + " print(\" - API_KEYS (JSON array of keys)\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Full inference (with API calls) - only if API keys are available\n", + "if api_available:\n", + " # Use route_batch which includes API calls\n", + " full_results = router.route_batch(batch=test_data[:5]) # Limit to 5 for demo\n", + " \n", + " print(\"Full Inference Results:\")\n", + " print(\"=\" * 80)\n", + " \n", + " for result in full_results:\n", + " print(f\"\\nQuery: {result['query'][:60]}...\")\n", + " print(f\"Routed to: {result['model_name']}\")\n", + " print(f\"Response: {result.get('response', 'N/A')[:100]}...\")\n", + " print(f\"Success: {result.get('success', 'N/A')}\")\n", + "else:\n", + " print(\"Skipping full inference - no API keys configured\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 8. Performance Evaluation" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Test set: 706 unique queries\n", + "\n", + "Oracle model distribution:\n", + "oracle_model\n", + "qwen2.5-7b-instruct 278\n", + "gemma-2-9b-it 153\n", + "llama-3.1-8b-instruct 104\n", + "llama-3.1-nemotron-51b-instruct 51\n", + "codegemma-7b 32\n", + "llama3-chatqa-1.5-8b 30\n", + "llama-3.3-nemotron-super-49b-v1 25\n", + "mistral-7b-instruct-v0.3 24\n", + "llama3-chatqa-1.5-70b 9\n", + "Name: count, dtype: int64\n" + ] + } + ], + "source": [ + "# Evaluate routing accuracy on test data\n", + "# This compares the router's choice with the oracle (best performing model)\n", + "\n", + "if router.routing_data_test is not None:\n", + " test_df = router.routing_data_test\n", + " \n", + " # Find best model for each query (oracle)\n", + " oracle_best = test_df.loc[\n", + " test_df.groupby('query')['performance'].idxmax()\n", + " ][['query', 'model_name', 'performance']]\n", + " oracle_best.columns = ['query', 'oracle_model', 'oracle_performance']\n", + " \n", + " print(f\"Test set: {len(oracle_best)} unique queries\")\n", + " print(f\"\\nOracle model distribution:\")\n", + " print(oracle_best['oracle_model'].value_counts())\n", + "else:\n", + " print(\"No test routing data available for evaluation\")" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 0%| | 3/706 [00:00<00:26, 26.62it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 1%|▏ | 9/706 [00:00<00:25, 26.86it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 2%|▏ | 15/706 [00:00<00:24, 27.72it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 3%|▎ | 21/706 [00:00<00:24, 27.51it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 4%|▍ | 27/706 [00:00<00:24, 27.84it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 5%|▍ | 33/706 [00:01<00:24, 27.78it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 6%|▌ | 39/706 [00:01<00:23, 28.00it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 6%|▋ | 45/706 [00:01<00:23, 27.89it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 7%|▋ | 51/706 [00:01<00:24, 27.24it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 8%|▊ | 57/706 [00:02<00:23, 27.77it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 9%|▉ | 63/706 [00:02<00:22, 28.24it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 10%|▉ | 69/706 [00:02<00:23, 27.51it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 11%|█ | 75/706 [00:02<00:23, 27.31it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 11%|█▏ | 81/706 [00:02<00:22, 27.82it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 12%|█▏ | 87/706 [00:03<00:22, 27.82it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 13%|█▎ | 93/706 [00:03<00:22, 27.69it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 14%|█▍ | 99/706 [00:03<00:21, 27.92it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 15%|█▍ | 105/706 [00:03<00:22, 27.05it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 16%|█▌ | 111/706 [00:04<00:21, 27.50it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 17%|█▋ | 117/706 [00:04<00:20, 28.18it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 17%|█▋ | 123/706 [00:04<00:21, 26.82it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 18%|█▊ | 129/706 [00:04<00:21, 26.54it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 19%|█▉ | 135/706 [00:04<00:21, 26.96it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 20%|█▉ | 141/706 [00:05<00:21, 26.76it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 21%|██ | 148/706 [00:05<00:20, 27.62it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 22%|██▏ | 154/706 [00:05<00:19, 27.70it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 23%|██▎ | 160/706 [00:05<00:19, 27.64it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 24%|██▎ | 166/706 [00:06<00:19, 28.12it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 24%|██▍ | 172/706 [00:06<00:18, 28.34it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 25%|██▌ | 178/706 [00:06<00:18, 27.83it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 26%|██▌ | 184/706 [00:06<00:18, 27.65it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 27%|██▋ | 190/706 [00:06<00:18, 28.09it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 28%|██▊ | 196/706 [00:07<00:18, 27.39it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 29%|██▊ | 202/706 [00:07<00:18, 26.99it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 29%|██▉ | 208/706 [00:07<00:18, 27.29it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 30%|███ | 214/706 [00:07<00:17, 27.71it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 31%|███ | 220/706 [00:07<00:17, 27.70it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 32%|███▏ | 226/706 [00:08<00:17, 27.54it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 33%|███▎ | 232/706 [00:08<00:17, 27.83it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 34%|███▎ | 238/706 [00:08<00:17, 27.08it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 35%|███▍ | 244/706 [00:08<00:17, 27.04it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 35%|███▌ | 250/706 [00:09<00:16, 27.17it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 36%|███▋ | 256/706 [00:09<00:16, 27.52it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 37%|███▋ | 262/706 [00:09<00:16, 27.26it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 38%|███▊ | 268/706 [00:09<00:16, 27.14it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 39%|███▉ | 274/706 [00:09<00:15, 27.66it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 40%|███▉ | 280/706 [00:10<00:15, 27.61it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 41%|████ | 286/706 [00:10<00:15, 27.41it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 41%|████▏ | 292/706 [00:10<00:15, 27.13it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 42%|████▏ | 298/706 [00:10<00:14, 27.65it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 43%|████▎ | 304/706 [00:11<00:14, 27.67it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 44%|████▍ | 310/706 [00:11<00:14, 27.85it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 45%|████▍ | 316/706 [00:11<00:13, 27.96it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 46%|████▌ | 322/706 [00:11<00:13, 27.72it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 46%|████▋ | 328/706 [00:11<00:13, 27.65it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 47%|████▋ | 334/706 [00:12<00:13, 27.78it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 48%|████▊ | 340/706 [00:12<00:13, 26.84it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 49%|████▉ | 346/706 [00:12<00:13, 27.44it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 50%|████▉ | 352/706 [00:12<00:13, 26.38it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 51%|█████ | 358/706 [00:13<00:13, 26.67it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 52%|█████▏ | 364/706 [00:13<00:12, 26.49it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 52%|█████▏ | 370/706 [00:13<00:12, 26.68it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 53%|█████▎ | 376/706 [00:13<00:12, 26.65it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 54%|█████▍ | 382/706 [00:13<00:11, 27.18it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 55%|█████▍ | 388/706 [00:14<00:11, 27.18it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 56%|█████▌ | 394/706 [00:14<00:11, 26.88it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 57%|█████▋ | 400/706 [00:14<00:10, 28.01it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 58%|█████▊ | 406/706 [00:14<00:10, 28.69it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 58%|█████▊ | 413/706 [00:14<00:09, 29.46it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 59%|█████▉ | 420/706 [00:15<00:09, 29.80it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 60%|██████ | 424/706 [00:15<00:09, 29.92it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 61%|██████ | 432/706 [00:15<00:09, 30.16it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 62%|██████▏ | 440/706 [00:15<00:08, 30.30it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 63%|██████▎ | 448/706 [00:16<00:08, 30.37it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 64%|██████▍ | 452/706 [00:16<00:08, 30.53it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 65%|██████▌ | 460/706 [00:16<00:08, 30.43it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 66%|██████▋ | 468/706 [00:16<00:07, 30.56it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 67%|██████▋ | 476/706 [00:17<00:07, 30.63it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 68%|██████▊ | 480/706 [00:17<00:07, 30.61it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 69%|██████▉ | 488/706 [00:17<00:07, 30.68it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 70%|███████ | 496/706 [00:17<00:06, 30.69it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 71%|███████▏ | 504/706 [00:17<00:06, 30.71it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 72%|███████▏ | 508/706 [00:18<00:06, 30.80it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 73%|███████▎ | 516/706 [00:18<00:06, 30.91it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 74%|███████▍ | 524/706 [00:18<00:05, 31.02it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 75%|███████▌ | 532/706 [00:18<00:05, 30.98it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 76%|███████▌ | 536/706 [00:19<00:05, 30.98it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 77%|███████▋ | 544/706 [00:19<00:05, 31.10it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 78%|███████▊ | 552/706 [00:19<00:04, 31.16it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 79%|███████▉ | 560/706 [00:19<00:04, 31.30it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 80%|███████▉ | 564/706 [00:19<00:04, 31.22it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 81%|████████ | 572/706 [00:20<00:04, 31.34it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 82%|████████▏ | 580/706 [00:20<00:04, 31.23it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 83%|████████▎ | 588/706 [00:20<00:03, 31.33it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 84%|████████▍ | 592/706 [00:20<00:03, 31.24it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 85%|████████▍ | 600/706 [00:21<00:03, 31.03it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 86%|████████▌ | 608/706 [00:21<00:03, 31.04it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 87%|████████▋ | 616/706 [00:21<00:02, 31.01it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 88%|████████▊ | 620/706 [00:21<00:02, 31.03it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 89%|████████▉ | 628/706 [00:21<00:02, 31.01it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 90%|█████████ | 636/706 [00:22<00:02, 30.96it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 91%|█████████ | 644/706 [00:22<00:02, 30.94it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 92%|█████████▏| 648/706 [00:22<00:01, 31.00it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 93%|█████████▎| 656/706 [00:22<00:01, 31.33it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 94%|█████████▍| 664/706 [00:23<00:01, 31.45it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 95%|█████████▌| 672/706 [00:23<00:01, 31.66it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 96%|█████████▌| 676/706 [00:23<00:00, 31.72it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 97%|█████████▋| 684/706 [00:23<00:00, 31.76it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 98%|█████████▊| 692/706 [00:24<00:00, 31.86it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 99%|█████████▉| 700/706 [00:24<00:00, 31.93it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Evaluating: 100%|██████████| 706/706 [00:24<00:00, 28.87it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/knnrouter/knnrouter.pkl\n", + "\n", + "Routing Accuracy: 0.4674 (330/706)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "# Compare router predictions with oracle\n", + "if router.routing_data_test is not None:\n", + " from tqdm import tqdm\n", + " \n", + " correct = 0\n", + " total = 0\n", + " results_comparison = []\n", + " \n", + " for _, row in tqdm(oracle_best.iterrows(), total=len(oracle_best), desc=\"Evaluating\"):\n", + " query = row['query']\n", + " oracle_model = row['oracle_model']\n", + " \n", + " # Get router prediction\n", + " result = router.route_single({'query': query})\n", + " predicted_model = result['model_name']\n", + " \n", + " is_correct = predicted_model == oracle_model\n", + " correct += int(is_correct)\n", + " total += 1\n", + " \n", + " results_comparison.append({\n", + " 'query': query[:50],\n", + " 'oracle': oracle_model,\n", + " 'predicted': predicted_model,\n", + " 'correct': is_correct\n", + " })\n", + " \n", + " accuracy = correct / total if total > 0 else 0\n", + " print(f\"\\nRouting Accuracy: {accuracy:.4f} ({correct}/{total})\")" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Classification Report:\n", + " precision recall f1-score support\n", + "\n", + " codegemma-7b 0.16 0.28 0.20 32\n", + " gemma-2-9b-it 0.44 0.56 0.49 153\n", + " llama-3.1-8b-instruct 0.27 0.22 0.24 104\n", + "llama-3.1-nemotron-51b-instruct 0.09 0.02 0.03 51\n", + "llama-3.3-nemotron-super-49b-v1 0.33 0.04 0.07 25\n", + " llama3-chatqa-1.5-70b 0.00 0.00 0.00 9\n", + " llama3-chatqa-1.5-8b 0.43 0.10 0.16 30\n", + " mistral-7b-instruct-v0.3 0.20 0.04 0.07 24\n", + " qwen2.5-7b-instruct 0.60 0.74 0.66 278\n", + "\n", + " accuracy 0.47 706\n", + " macro avg 0.28 0.22 0.22 706\n", + " weighted avg 0.42 0.47 0.43 706\n", + "\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/conda/lib/python3.13/site-packages/sklearn/metrics/_classification.py:1833: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.\n", + " _warn_prf(average, modifier, f\"{metric.capitalize()} is\", result.shape[0])\n", + "/opt/conda/lib/python3.13/site-packages/sklearn/metrics/_classification.py:1833: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.\n", + " _warn_prf(average, modifier, f\"{metric.capitalize()} is\", result.shape[0])\n", + "/opt/conda/lib/python3.13/site-packages/sklearn/metrics/_classification.py:1833: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.\n", + " _warn_prf(average, modifier, f\"{metric.capitalize()} is\", result.shape[0])\n" + ] + }, + { + "data": { + "image/png": "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nqWyL730kt58802NGzdOr7zyiuzt7TV//vwstwUAeRFBJwAAVuTq6ipJunr1apbK//HHH7KxsVHp0qXNjnt5ecnd3V1//PGH2fFixYplqKNgwYL6559/ctjjjNq1a6datWrpjTfeUJEiRdS+fXstW7Ys0wA0vZ/+/v4ZzpUtW1b/+c9/dO3aNbPjd95LwYIFJSlb99KkSRO5uLjoq6++Unh4uKpWrZrhWaZLTU3VjBkzVKZMGdnZ2empp56Sp6enfvnlF125ciXLbRYtWjRbiwZNnTpVHh4eOnjwoGbNmqXChQtn+VoAyIsIOgEAsCJXV1f5+Pjo119/zdZ1dy7kcy/58uW76/G0tLQct5E+3zCdg4ODtm3bpg0bNui1117TL7/8onbt2unFF1/MUNYSltxLOjs7O73yyisKCwvTt99+e88spyS9//77GjhwoOrWrav/+7//0w8//KD169erfPnyWc7oSreeT3b8/PPPunDhgiTp8OHD2boWAPIigk4AAKysWbNmOnXqlHbt2nXfssWLF1dqaqpOnDhhdvz8+fO6fPmysRLtg1CwYEGzlV7T3ZlNlSQbGxs1bNhQ06dP19GjRzVx4kRt2rRJmzdvvmvd6f2MiorKcO748eN66qmn5OTkZNkN3EPHjh31888/6+rVq3ddfCnd119/rQYNGujzzz9X+/bt9dJLL+mFF17I8Eyy+gVAVly7dk2vv/66ypUrpx49eujDDz/Uvn37Hlj9APAoIugEAMDKhg4dKicnJ73xxhs6f/58hvOnTp3SRx99JOnW8FBJGVaYnT59uiSpadOmD6xfpUqV0pUrV/TLL78Yx2JjY/Xtt9+albt06VKGaytVqiRJGV7jks7b21uVKlVSWFiYWRD366+/6scffzTu0xoaNGig8ePHa86cOfLy8rpnuXz58mXIoi5fvlx//fWX2bH04PhuAXp2DRs2TDExMQoLC9P06dPl5+enLl263PM5AsDjIP/D7gAAAI+7UqVKacmSJWrXrp3Kli2rzp0767nnnlNSUpJ++uknLV++XCEhIZKkihUrqkuXLvr00091+fJl1atXT3v37lVYWJhatmx5z9dx5ET79u01bNgwtWrVSv369VNCQoLmzp2rZ555xmwhnXHjxmnbtm1q2rSpihcvrgsXLuiTTz7R008/rdq1a9+z/ilTpqhx48aqUaOG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t98CAE6cOIFLly5h8eLFAIC5c+di/fr1WL58OY4ePYoJEybA19cXSUlJAO6FYx8fH7z55ptIS0vDiBEjMHny5LL6thER0SOwvZaIiIggIkhMTMSOHTswduxYXL16FVZWVli9erXaVvvFF19Aq9Vi9erVUBQFALBmzRrY2dlhz5496NatGxYtWoQpU6bAx8cHALB8+XLs2LHjoV/35MmTiI2Nxa5du9ClSxcAQL169dTzulZcR0dH2NnZAbg3Mjpnzhzs3r0brVu3Vh+zb98+rFixAh07dkRUVBTq16+PiIgIAEDjxo1x+PBhzJ8//yl+14iI6O9g6CQiIqrA4uPjYW1tjaKiImi1WgwePBgzZ87E+++/jxYtWujN4/ztt99w+vRp2NjY6D1HQUEBzpw5gxs3buDSpUt45ZVX1HMajQZeXl73tdjqpKWlwcTEBB07dvzbNZ8+fRp3795F165d9Y4XFhbCw8MDAJCenq5XBwA1oBIR0bPF0ElERFSBeXt7IyoqCmZmZnBycoJG8+dbAysrK71rb9++jVatWuHLL7+873mqV6/+WF/fwsLiHz/m9u3bAICEhATUqlVL75y5uflj1UFERGWHoZOIiKgCs7KyQoMGDf7WtZ6enoiJiYGjoyOqVKnywGtq1qyJX375BR06dAAAFBcX4+DBg/D09Hzg9S1atIBWq0VSUpLaXluabqS1pKREPebm5gZzc3NkZmY+dIS0adOm2Lp1q96x/fv3/983SURETx0XEiIiIqK/ZciQIahWrRr69OmD5ORkZGRkYM+ePRg3bhwuXLgAAPjwww8xb948bNmyBcePH8eYMWMeucemq6sr/P39ERAQgC1btqjPGRsbCwBwcXGBoiiIj4/H1atXcfv2bdjY2CAoKAgTJkzAunXrcObMGaSkpGDJkiVYt24dAGDUqFE4deoUgoODceLECXz11VdYu3ZtWX+LiIjoARg6iYiI6G+xtLTE3r17UadOHfj4+KBp06YYPnw4CgoK1JHPjz/+GO+++y78/f3RunVr2NjYoG/fvo983qioKPTr1w9jxoxBkyZNMHLkSNy5cwcAUKtWLcyaNQuTJ09GjRo18MEHHwAAZs+ejdDQUMydOxdNmzZF9+7dkZCQgLp16wIA6tSpg2+//RZbtmxBy5YtsXz5csyZM6cMvztERPQwijxsZj8RERERERHRE+JIJxEREREREZUZhk4iIiIiIiIqMwydREREREREVGYYOomIiIiIiKjMMHQSERERERFRmWHoJCIiIiIiojLD0ElERERERERlhqGTiIiIiIiIygxDJxEREREREZUZhk4iIiIiIiIqMwydREREREREVGYYOomIiIiIiKjM/D9cqmJSpZ2VWgAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Show confusion matrix\n", + "if router.routing_data_test is not None:\n", + " import pandas as pd\n", + " from sklearn.metrics import confusion_matrix, classification_report\n", + " \n", + " comparison_df = pd.DataFrame(results_comparison)\n", + " \n", + " # Classification report\n", + " print(\"Classification Report:\")\n", + " print(classification_report(comparison_df['oracle'], comparison_df['predicted']))\n", + " \n", + " # Confusion matrix\n", + " labels = sorted(set(comparison_df['oracle']) | set(comparison_df['predicted']))\n", + " cm = confusion_matrix(comparison_df['oracle'], comparison_df['predicted'], labels=labels)\n", + " \n", + " plt.figure(figsize=(10, 8))\n", + " plt.imshow(cm, interpolation='nearest', cmap=plt.cm.Blues)\n", + " plt.title('Confusion Matrix')\n", + " plt.colorbar()\n", + " plt.xticks(range(len(labels)), labels, rotation=45, ha='right')\n", + " plt.yticks(range(len(labels)), labels)\n", + " plt.xlabel('Predicted')\n", + " plt.ylabel('Oracle')\n", + " plt.tight_layout()\n", + " plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 9. Using CLI for Inference" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# You can also use the CLI for inference\n", + "print(\"CLI Commands for Inference:\")\n", + "print(\"=\"*60)\n", + "print()\n", + "print(\"# Route a single query (route-only, no API call):\")\n", + "print('llmrouter infer --router knnrouter --config configs/model_config_test/knnrouter_inference.yaml --query \"What is AI?\" --route-only')\n", + "print()\n", + "print(\"# Route with full inference (API call):\")\n", + "print('llmrouter infer --router knnrouter --config configs/model_config_test/knnrouter_inference.yaml --query \"What is AI?\"')\n", + "print()\n", + "print(\"# Batch inference from file:\")\n", + "print('llmrouter infer --router knnrouter --config configs/model_config_test/knnrouter_inference.yaml --input queries.txt --output results.json')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "In this notebook, we:\n", + "\n", + "1. **Loaded Trained Model**: Set up KNNRouter with trained model\n", + "2. **Single Query Routing**: Routed individual queries to LLMs\n", + "3. **Routing Probabilities**: Analyzed routing confidence\n", + "4. **Batch Routing**: Processed multiple queries efficiently\n", + "5. **Full Inference**: Called LLM APIs (when available)\n", + "6. **Performance Evaluation**: Compared with oracle performance\n", + "\n", + "**Key Findings**:\n", + "- KNNRouter provides interpretable routing decisions\n", + "- Routing probabilities show model confidence\n", + "- Performance can be tuned via K and distance metric\n", + "\n", + "**Next Steps**:\n", + "- Try different routers (SVMRouter, MLPRouter, etc.)\n", + "- Experiment with ensemble routing\n", + "- Deploy as API service" + ] + } + ], + "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.13.11" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/colab_notebooks/largest_llm/01_largest_llm_inference.ipynb b/colab_notebooks/largest_llm/01_largest_llm_inference.ipynb new file mode 100644 index 0000000..9081647 --- /dev/null +++ b/colab_notebooks/largest_llm/01_largest_llm_inference.ipynb @@ -0,0 +1,569 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# LargestLLM Router - Inference\n", + "\n", + "This notebook demonstrates the **LargestLLM** baseline router.\n", + "\n", + "## Overview\n", + "\n", + "LargestLLM is a simple baseline router that always routes queries to the largest model in the candidate pool.\n", + "This serves as an upper bound for routing performance and a lower bound for cost efficiency.\n", + "\n", + "**Key Characteristics**:\n", + "- No training required (deterministic baseline)\n", + "- Always selects the largest model by parameter size\n", + "- Useful for performance benchmarking\n", + "- Highest expected quality, highest cost" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Environment Setup" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# For Google Colab: Clone repository and install dependencies\n", + "import os\n", + "\n", + "!git clone https://github.com/ulab-uiuc/LLMRouter.git\n", + "%cd LLMRouter\n", + "!pip install -e .\n", + "!pip install pyyaml transformers torch" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Environment setup complete!\n" + ] + } + ], + "source": [ + "from llmrouter.models.largest_llm import LargestLLM\n", + "from llmrouter.utils import setup_environment\n", + "import yaml\n", + "\n", + "setup_environment()\n", + "print(\"Environment setup complete!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Configuration\n", + "\n", + "LargestLLM router requires only data paths - no hyperparameters.\n", + "\n", + "| Parameter | Description |\n", + "|-----------|-------------|\n", + "| `llm_data` | Path to LLM candidate metadata |\n", + "| `routing_data_test` | Path to test routing data |" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Current Configuration:\n", + "==================================================\n", + "data_path:\n", + " llm_data: data/example_data/llm_candidates/default_llm.json\n", + " llm_embedding_data: data/example_data/llm_candidates/default_llm_embeddings.json\n", + " query_data_test: data/example_data/query_data/default_query_test.jsonl\n", + " query_data_train: data/example_data/query_data/default_query_train.jsonl\n", + " query_embedding_data: data/example_data/routing_data/query_embeddings_longformer.pt\n", + " routing_data_test: data/example_data/routing_data/default_routing_test_data.jsonl\n", + " routing_data_train: data/example_data/routing_data/default_routing_train_data.jsonl\n", + "metric:\n", + " weights:\n", + " cost: 0\n", + " llm_judge: 0\n", + " performance: 1\n", + "\n" + ] + } + ], + "source": [ + "CONFIG_PATH = \"configs/model_config_test/largest_llm.yaml\"\n", + "\n", + "with open(CONFIG_PATH, 'r') as f:\n", + " config = yaml.safe_load(f)\n", + "\n", + "print(\"Current Configuration:\")\n", + "print(\"=\" * 50)\n", + "print(yaml.dump(config, default_flow_style=False))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Initialize Router" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "✅ MetaRouter initialized successfully (YAML + data loaded).\n", + "✅ LargestLLM initialized successfully\n", + "Router initialized successfully!\n", + "Number of LLM candidates: 7\n" + ] + } + ], + "source": [ + "router = LargestLLM(yaml_path=CONFIG_PATH)\n", + "\n", + "print(\"Router initialized successfully!\")\n", + "print(f\"Number of LLM candidates: {len(router.llm_data)}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Available LLM Candidates (by size):\n", + "============================================================\n", + "1. qwen2.5-7b-instruct: 7BB parameters <- LARGEST\n", + "2. llama-3.1-8b-instruct: 8BB parameters\n", + "3. mistral-7b-instruct-v0.3: 7BB parameters\n", + "4. llama-3.3-nemotron-super-49b-v1: 49BB parameters\n", + "5. llama3-70b-instruct: 70BB parameters\n", + "6. mixtral-8x7b-instruct-v0.1: 45BB parameters\n", + "7. mixtral-8x22b-instruct-v0.1: 141BB parameters\n" + ] + } + ], + "source": [ + "# Display available LLM candidates sorted by size\n", + "print(\"Available LLM Candidates (by size):\")\n", + "print(\"=\" * 60)\n", + "\n", + "llm_list = [(name, info.get('size', 'unknown')) for name, info in router.llm_data.items()]\n", + "llm_list_sorted = sorted(llm_list, key=lambda x: float(x[1]) if isinstance(x[1], (int, float)) else 0, reverse=True)\n", + "\n", + "for i, (name, size) in enumerate(llm_list_sorted, 1):\n", + " marker = \" <- LARGEST\" if i == 1 else \"\"\n", + " print(f\"{i}. {name}: {size}B parameters{marker}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Query Routing\n", + "\n", + "LargestLLM always routes to the largest model, regardless of query complexity." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Routing Results:\n", + "============================================================\n", + "1. What is 2 + 2?...\n", + " Routed to: mixtral-8x22b-instruct-v0.1\n", + "\n", + "2. Explain the theory of general relativity....\n", + " Routed to: mixtral-8x22b-instruct-v0.1\n", + "\n", + "3. Prove P != NP....\n", + " Routed to: mixtral-8x22b-instruct-v0.1\n", + "\n" + ] + } + ], + "source": [ + "EXAMPLE_QUERIES = [\n", + " {\"query\": \"What is 2 + 2?\"}, # Simple\n", + " {\"query\": \"Explain the theory of general relativity.\"}, # Medium\n", + " {\"query\": \"Prove P != NP.\"}, # Complex\n", + "]\n", + "\n", + "print(\"Routing Results:\")\n", + "print(\"=\" * 60)\n", + "\n", + "for i, query in enumerate(EXAMPLE_QUERIES, 1):\n", + " result = router.route_single(query)\n", + " print(f\"{i}. {query['query'][:50]}...\")\n", + " print(f\" Routed to: {result['model_name']}\")\n", + " print()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Batch Routing" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Routing 10 test queries...\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "\n", + "Routing Distribution:\n", + " mixtral-8x22b-instruct-v0.1: 10 (100.0%)\n" + ] + } + ], + "source": [ + "# Route test data\n", + "test_queries = router.routing_data_test[:10]\n", + "\n", + "print(f\"Routing {len(test_queries)} test queries...\")\n", + "results = router.route_batch(test_queries.to_dict('records'))\n", + "\n", + "print(f\"\\nRouting Distribution:\")\n", + "from collections import Counter\n", + "model_counts = Counter([r['model_name'] for r in results])\n", + "for model, count in model_counts.most_common():\n", + " print(f\" {model}: {count} ({100*count/len(results):.1f}%)\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. Evaluation" + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Evaluation Metrics:\n", + "--------------------------------------------------\n", + "Total queries: 10\n", + "Evaluated queries: 10\n", + "Average score (em_mc): 0.0000\n", + "\n", + "Detailed Scores:\n", + "--------------------------------------------------\n", + " 1. Score: 0.0000 | pred: Let's analyze the clues step | ground truth: C\n", + " 2. Score: 0.0000 | pred: From the clues, we can deduce | ground truth: A\n", + " 3. Score: 0.0000 | pred: The person who likes yellow l | ground truth: A\n", + " 4. Score: 0.0000 | pred: To solve this problem, we nee | ground truth: D\n", + " 5. Score: 0.0000 | pred: To solve this problem, we nee | ground truth: D\n" + ] + } + ], + "source": [ + "from llmrouter.evaluation import evaluate_batch\n", + "from collections import Counter\n", + "\n", + "eval_data = []\n", + "for r in results:\n", + " if r.get('ground_truth'):\n", + " eval_data.append({\n", + " 'prediction': r.get('response', ''),\n", + " 'ground_truth': r.get('ground_truth'),\n", + " 'metric': r.get('metric', 'em')\n", + " })\n", + "\n", + "if eval_data:\n", + " eval_results = evaluate_batch(eval_data)\n", + " scores = [r['score'] for r in eval_results]\n", + " avg_score = sum(scores) / len(scores) if scores else 0\n", + "else:\n", + " eval_results = []\n", + " avg_score = 0\n", + "\n", + "largest_metrics = {\n", + " 'avg_performance': sum(r.get('score', 0) for r in eval_results) / len(eval_results) if eval_results else 0,\n", + " 'total_queries': len(eval_results),\n", + " 'success_count': sum(1 for r in eval_results if r.get('score', 0) > 0)\n", + "}\n", + "\n", + "routing_dist = Counter(r['model_name'] for r in results)\n", + "\n", + "print(\"\\nEvaluation Metrics:\")\n", + "print(\"-\" * 50)\n", + "print(f\"Total queries: {len(results)}\")\n", + "print(f\"Evaluated queries: {len(eval_data)}\")\n", + "print(f\"Average score ({eval_data[0]['metric'] if eval_data else 'N/A'}): {avg_score:.4f}\")\n", + "\n", + "if eval_results:\n", + " print(\"\\nDetailed Scores:\")\n", + " print(\"-\" * 50)\n", + " for i, r in enumerate(eval_results[:5], 1):\n", + " pred = r.get('prediction', '')[:30]\n", + " gt = r.get('ground_truth', '')[:30]\n", + " print(f\" {i}. Score: {r['score']:.4f} | pred: {pred} | ground truth: {gt}\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7. Compare with SmallestLLM\n", + "\n", + "Compare the two baseline routers to understand the performance-cost tradeoff." + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "✅ MetaRouter initialized successfully (YAML + data loaded).\n", + "✅ SmallestLLM initialized successfully\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "\n", + "\u001b[1;31mGive Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new\u001b[0m\n", + "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.\n", + "\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "\n", + "\u001b[1;31mGive Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new\u001b[0m\n", + "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.\n", + "\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "\n", + "\u001b[1;31mGive Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new\u001b[0m\n", + "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.\n", + "\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Comparison: SmallestLLM vs LargestLLM\n", + "============================================================\n", + "Metric SmallestLLM LargestLLM \n", + "------------------------------------------------------------\n", + "avg_performance 0.0000 0.0000 \n", + "total_queries 10 10 \n", + "success_count 0 0 \n", + "success_rate 0.0 % 0.0 %\n" + ] + } + ], + "source": [ + "from llmrouter.evaluation import evaluate_batch\n", + "from llmrouter.models.smallest_llm import SmallestLLM\n", + "\n", + "smallest_router = SmallestLLM(yaml_path=\"configs/model_config_test/smallest_llm.yaml\")\n", + "smallest_test_queries = router.routing_data_test[:10]\n", + "smallest_results = smallest_router.route_batch(smallest_test_queries.to_dict('records'))\n", + "\n", + "eval_data = [{\n", + " 'prediction': r.get('response', ''),\n", + " 'ground_truth': r.get('ground_truth', ''),\n", + " 'metric': r.get('metric', 'em'),\n", + " 'task_name': r.get('task_name', None)\n", + "} for r in smallest_results]\n", + "\n", + "smallest_eval_results = evaluate_batch(eval_data)\n", + "\n", + "smallest_metrics = {\n", + " 'avg_performance': sum(r.get('score', 0) for r in smallest_eval_results) / len(smallest_eval_results) if smallest_eval_results else 0,\n", + " 'total_queries': len(smallest_eval_results),\n", + " 'success_count': sum(1 for r in smallest_eval_results if r.get('score', 0) > 0)\n", + "}\n", + "\n", + "print(\"Comparison: SmallestLLM vs LargestLLM\")\n", + "print(\"=\" * 60)\n", + "print(f\"{'Metric':<20} {'SmallestLLM':<15} {'LargestLLM':<15}\")\n", + "print(\"-\" * 60)\n", + "print(f\"{'avg_performance':<20} {smallest_metrics['avg_performance']:<15.4f} {largest_metrics['avg_performance']:<15.4f}\")\n", + "print(f\"{'total_queries':<20} {smallest_metrics['total_queries']:<15} {largest_metrics['total_queries']:<15}\")\n", + "print(f\"{'success_count':<20} {smallest_metrics['success_count']:<15} {largest_metrics['success_count']:<15}\")\n", + "print(f\"{'success_rate':<20} {smallest_metrics['success_count']/smallest_metrics['total_queries']*100:<14.1f}% {largest_metrics['success_count']/largest_metrics['total_queries']*100:<14.1f}%\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 8. File-Based Inference\n", + "\n", + "Load queries from a custom file and save results." + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loaded 706 queries from: data/example_data/query_data/default_query_test.jsonl\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Routed 10 queries\n", + "Results saved to: outputs/largest_llm_results.jsonl\n", + "\n", + "Sample results:\n", + " 1. Q: There are 4 houses in a row, numbered... -> mixtral-8x22b-instruct-v0.1\n", + " 2. Q: There are 3 houses in a row, numbered... -> mixtral-8x22b-instruct-v0.1\n", + " 3. Q: There are 3 houses in a row, numbered... -> mixtral-8x22b-instruct-v0.1\n" + ] + } + ], + "source": [ + "import json\n", + "\n", + "# Load queries from a JSONL file\n", + "def load_queries_from_file(file_path):\n", + " \"\"\"Load queries from a JSONL file.\"\"\"\n", + " queries = []\n", + " with open(file_path, 'r', encoding='utf-8') as f:\n", + " for line in f:\n", + " if line.strip():\n", + " queries.append(json.loads(line))\n", + " return queries\n", + "\n", + "# Save results to a JSONL file\n", + "def save_results_to_file(results, output_path):\n", + " \"\"\"Save routing results to a JSONL file.\"\"\"\n", + " os.makedirs(os.path.dirname(output_path), exist_ok=True)\n", + " with open(output_path, 'w', encoding='utf-8') as f:\n", + " for result in results:\n", + " f.write(json.dumps(result, ensure_ascii=False) + '\\n')\n", + " print(f\"Results saved to: {output_path}\")\n", + "\n", + "# Example: Load from your own query file\n", + "QUERY_FILE = \"data/example_data/query_data/default_query_test.jsonl\"\n", + "OUTPUT_FILE = \"outputs/largest_llm_results.jsonl\"\n", + "\n", + "if os.path.exists(QUERY_FILE):\n", + " # Load queries from file\n", + " file_queries = load_queries_from_file(QUERY_FILE)\n", + " print(f\"Loaded {len(file_queries)} queries from: {QUERY_FILE}\")\n", + " \n", + " # Route queries using route_batch\n", + " file_results = router.route_batch(batch=file_queries[:10])\n", + " print(f\"Routed {len(file_results)} queries\")\n", + " \n", + " # Save results to file\n", + " save_results_to_file(file_results, OUTPUT_FILE)\n", + " \n", + " # Show sample results\n", + " print(f\"\\nSample results:\")\n", + " for i, result in enumerate(file_results[:3], 1):\n", + " print(f\" {i}. {result.get('query', '')[:40]}... -> {result['model_name']}\")\n", + "else:\n", + " print(f\"Query file not found: {QUERY_FILE}\")\n", + " print(\"Create a JSONL file with format: {\\\"query\\\": \\\"Your question\\\"}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "**LargestLLM Router**:\n", + "- Always routes to the largest model\n", + "- No training required (deterministic baseline)\n", + "- Provides upper bound for performance, lower bound for cost efficiency\n", + "- Useful for comparing against learned routing methods\n", + "\n", + "**Use Cases**:\n", + "- Baseline comparison for routing research\n", + "- Quality-critical applications regardless of cost\n", + "- Establishing performance ceiling for routing methods" + ] + } + ], + "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.13.11" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/colab_notebooks/llmmultiroundrouter/01_llmmultiroundrouter_inference.ipynb b/colab_notebooks/llmmultiroundrouter/01_llmmultiroundrouter_inference.ipynb new file mode 100644 index 0000000..a0c4277 --- /dev/null +++ b/colab_notebooks/llmmultiroundrouter/01_llmmultiroundrouter_inference.ipynb @@ -0,0 +1,925 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# LLMMultiRoundRouter - Inference\n", + "\n", + "This notebook demonstrates how to use the **LLMMultiRoundRouter** for multi-round query processing.\n", + "\n", + "## Overview\n", + "\n", + "LLMMultiRoundRouter uses LLM prompts for both query decomposition and routing decisions.\n", + "Unlike KNN-based routers, it doesn't require training - it uses LLM reasoning directly.\n", + "\n", + "**Pipeline**:\n", + "1. **Decompose + Route**: LLM breaks query into sub-queries AND routes each in one step\n", + "2. **Execute**: Call routed model APIs for each sub-query\n", + "3. **Aggregate**: LLM combines responses into final answer\n", + "\n", + "**Key Features**:\n", + "- No training required (LLM-based reasoning)\n", + "- Single-step decomposition and routing\n", + "- Model descriptions guide routing decisions\n", + "- Supports both local vLLM and API-based inference" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Environment Setup" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cloning into 'LLMRouter'...\n", + "remote: Enumerating objects: 6017, done.\u001b[K\n", + "remote: Counting objects: 100% (180/180), done.\u001b[K\n", + "remote: Compressing objects: 100% (90/90), done.\u001b[K\n", + "remote: Total 6017 (delta 105), reused 96 (delta 90), pack-reused 5837 (from 2)\u001b[K\n", + "Receiving objects: 100% (6017/6017), 89.41 MiB | 55.09 MiB/s, done.\n", + "Resolving deltas: 100% (2946/2946), done.\n", + "Updating files: 100% (288/288), done.\n", + "/home/zhongjie/LLMRouter\n", + "Obtaining file:///home/zhongjie/LLMRouter\n", + " Installing build dependencies ... \u001b[?25ldone\n", + "\u001b[?25h Checking if build backend supports build_editable ... \u001b[?25ldone\n", + "\u001b[?25h Getting requirements to build editable ... \u001b[?25ldone\n", + "\u001b[?25h Preparing editable metadata (pyproject.toml) ... \u001b[?25ldone\n", + "\u001b[?25hRequirement already satisfied: torch>=2.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.9.1)\n", + "Requirement already satisfied: transformers>=4.40 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (4.57.6)\n", + "Requirement already satisfied: sentencepiece>=0.1.99 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (0.2.1)\n", + "Requirement already satisfied: numpy>=1.21 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.3.5)\n", + "Requirement already satisfied: pandas>=1.5 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.3.3)\n", + "Requirement already satisfied: scikit-learn>=1.2 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (1.8.0)\n", + "Requirement already satisfied: pyyaml>=6.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (6.0.3)\n", + "Requirement already satisfied: tqdm>=4.65 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (4.67.1)\n", + "Requirement already satisfied: datasets>=2.14 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (4.5.0)\n", + "Requirement already satisfied: pydantic>=2.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.12.5)\n", + "Requirement already satisfied: gradio>=4.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (6.3.0)\n", + "Requirement already satisfied: litellm>=1.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (1.81.0)\n", + "Requirement already satisfied: peft>=0.7 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (0.18.1)\n", + "Requirement already satisfied: torch-geometric>=2.3 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.7.0)\n", + "Requirement already satisfied: scipy>=1.10 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (1.16.3)\n", + "Requirement already satisfied: protobuf>=3.20 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (6.31.1)\n", + "Requirement already satisfied: filelock in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (3.20.2)\n", + "Requirement already satisfied: pyarrow>=21.0.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (22.0.0)\n", + "Requirement already satisfied: dill<0.4.1,>=0.3.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (0.4.0)\n", + "Requirement already satisfied: requests>=2.32.2 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (2.32.5)\n", + "Requirement already satisfied: httpx<1.0.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (0.28.1)\n", + "Requirement already satisfied: xxhash in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (3.6.0)\n", + "Requirement already satisfied: multiprocess<0.70.19 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (0.70.18)\n", + "Requirement already satisfied: fsspec<=2025.10.0,>=2023.1.0 in /opt/conda/lib/python3.13/site-packages (from fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (2025.10.0)\n", + "Requirement already satisfied: huggingface-hub<2.0,>=0.25.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (0.36.0)\n", + "Requirement already satisfied: packaging in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (25.0)\n", + "Requirement already satisfied: aiohttp!=4.0.0a0,!=4.0.0a1 in /opt/conda/lib/python3.13/site-packages (from fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (3.13.3)\n", + "Requirement already satisfied: anyio in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (4.12.0)\n", + "Requirement already satisfied: certifi in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (2026.1.4)\n", + "Requirement already satisfied: httpcore==1.* in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.0.9)\n", + "Requirement already satisfied: idna in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (3.11)\n", + "Requirement already satisfied: h11>=0.16 in /opt/conda/lib/python3.13/site-packages (from httpcore==1.*->httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (0.16.0)\n", + "Requirement already satisfied: typing-extensions>=3.7.4.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<2.0,>=0.25.0->datasets>=2.14->llmrouter-lib==0.2.0) (4.15.0)\n", + "Requirement already satisfied: hf-xet<2.0.0,>=1.1.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<2.0,>=0.25.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.2.0)\n", + "Requirement already satisfied: aiohappyeyeballs>=2.5.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (2.6.1)\n", + "Requirement already satisfied: aiosignal>=1.4.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.4.0)\n", + "Requirement already satisfied: attrs>=17.3.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (25.4.0)\n", + "Requirement already satisfied: frozenlist>=1.1.1 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.8.0)\n", + "Requirement already satisfied: multidict<7.0,>=4.5 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (6.7.0)\n", + "Requirement already satisfied: propcache>=0.2.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (0.4.1)\n", + "Requirement already satisfied: yarl<2.0,>=1.17.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.22.0)\n", + "Requirement already satisfied: aiofiles<25.0,>=22.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (24.1.0)\n", + "Requirement already satisfied: audioop-lts<1.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.2.2)\n", + "Requirement already satisfied: brotli>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (1.2.0)\n", + "Requirement already satisfied: fastapi<1.0,>=0.115.2 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.128.0)\n", + "Requirement already satisfied: ffmpy in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (1.0.0)\n", + "Requirement already satisfied: gradio-client==2.0.3 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (2.0.3)\n", + "Requirement already satisfied: groovy~=0.1 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.1.2)\n", + "Requirement already satisfied: jinja2<4.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (3.1.6)\n", + "Requirement already satisfied: markupsafe<4.0,>=2.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (3.0.3)\n", + "Requirement already satisfied: orjson~=3.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (3.11.5)\n", + "Requirement already satisfied: pillow<13.0,>=8.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (12.1.0)\n", + "Requirement already satisfied: pydub in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.25.1)\n", + "Requirement already satisfied: python-multipart>=0.0.18 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.0.21)\n", + "Requirement already satisfied: safehttpx<0.2.0,>=0.1.7 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.1.7)\n", + "Requirement already satisfied: semantic-version~=2.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (2.10.0)\n", + "Requirement already satisfied: starlette<1.0,>=0.40.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.50.0)\n", + "Requirement already satisfied: tomlkit<0.14.0,>=0.12.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.13.3)\n", + "Requirement already satisfied: typer<1.0,>=0.12 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.21.1)\n", + "Requirement already satisfied: uvicorn>=0.14.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.40.0)\n", + "Requirement already satisfied: annotated-doc>=0.0.2 in /opt/conda/lib/python3.13/site-packages (from fastapi<1.0,>=0.115.2->gradio>=4.0->llmrouter-lib==0.2.0) (0.0.4)\n", + "Requirement already satisfied: python-dateutil>=2.8.2 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.2.0) (2.9.0.post0)\n", + "Requirement already satisfied: pytz>=2020.1 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.2.0) (2025.2)\n", + "Requirement already satisfied: tzdata>=2022.7 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.2.0) (2025.3)\n", + "Requirement already satisfied: annotated-types>=0.6.0 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.2.0) (0.7.0)\n", + "Requirement already satisfied: pydantic-core==2.41.5 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.2.0) (2.41.5)\n", + "Requirement already satisfied: typing-inspection>=0.4.2 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.2.0) (0.4.2)\n", + "Requirement already satisfied: click>=8.0.0 in /opt/conda/lib/python3.13/site-packages (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (8.3.1)\n", + "Requirement already satisfied: shellingham>=1.3.0 in /opt/conda/lib/python3.13/site-packages (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (1.5.4)\n", + "Requirement already satisfied: rich>=10.11.0 in /opt/conda/lib/python3.13/site-packages (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (14.2.0)\n", + "Requirement already satisfied: fastuuid>=0.13.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (0.14.0)\n", + "Requirement already satisfied: grpcio!=1.68.*,!=1.69.*,!=1.70.*,!=1.71.0,!=1.71.1,!=1.72.0,!=1.72.1,!=1.73.0,>=1.62.3 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (1.76.0)\n", + "Requirement already satisfied: importlib-metadata>=6.8.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (8.7.0)\n", + "Requirement already satisfied: jsonschema<5.0.0,>=4.23.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (4.25.1)\n", + "Requirement already satisfied: openai>=2.8.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (2.15.0)\n", + "Requirement already satisfied: python-dotenv>=0.2.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (1.2.1)\n", + "Requirement already satisfied: tiktoken>=0.7.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (0.12.0)\n", + "Requirement already satisfied: tokenizers in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (0.22.2)\n", + "Requirement already satisfied: jsonschema-specifications>=2023.03.6 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.2.0) (2025.9.1)\n", + "Requirement already satisfied: referencing>=0.28.4 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.2.0) (0.37.0)\n", + "Requirement already satisfied: rpds-py>=0.7.1 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.2.0) (0.30.0)\n", + "Requirement already satisfied: zipp>=3.20 in /opt/conda/lib/python3.13/site-packages (from importlib-metadata>=6.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (3.23.0)\n", + "Requirement already satisfied: distro<2,>=1.7.0 in /opt/conda/lib/python3.13/site-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (1.9.0)\n", + "Requirement already satisfied: jiter<1,>=0.10.0 in /opt/conda/lib/python3.13/site-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (0.12.0)\n", + "Requirement already satisfied: sniffio in /opt/conda/lib/python3.13/site-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (1.3.1)\n", + "Requirement already satisfied: psutil in /opt/conda/lib/python3.13/site-packages (from peft>=0.7->llmrouter-lib==0.2.0) (7.2.1)\n", + "Requirement already satisfied: accelerate>=0.21.0 in /opt/conda/lib/python3.13/site-packages (from peft>=0.7->llmrouter-lib==0.2.0) (1.12.0)\n", + "Requirement already satisfied: safetensors in /opt/conda/lib/python3.13/site-packages (from peft>=0.7->llmrouter-lib==0.2.0) (0.7.0)\n", + "Requirement already satisfied: six>=1.5 in /opt/conda/lib/python3.13/site-packages (from python-dateutil>=2.8.2->pandas>=1.5->llmrouter-lib==0.2.0) (1.17.0)\n", + "Requirement already satisfied: charset_normalizer<4,>=2 in /opt/conda/lib/python3.13/site-packages (from requests>=2.32.2->datasets>=2.14->llmrouter-lib==0.2.0) (3.4.4)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/conda/lib/python3.13/site-packages (from requests>=2.32.2->datasets>=2.14->llmrouter-lib==0.2.0) (2.6.2)\n", + "Requirement already satisfied: markdown-it-py>=2.2.0 in /opt/conda/lib/python3.13/site-packages (from rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (4.0.0)\n", + "Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /opt/conda/lib/python3.13/site-packages (from rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (2.19.2)\n", + "Requirement already satisfied: mdurl~=0.1 in /opt/conda/lib/python3.13/site-packages (from markdown-it-py>=2.2.0->rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (0.1.2)\n", + "Requirement already satisfied: joblib>=1.3.0 in /opt/conda/lib/python3.13/site-packages (from scikit-learn>=1.2->llmrouter-lib==0.2.0) (1.5.3)\n", + "Requirement already satisfied: threadpoolctl>=3.2.0 in /opt/conda/lib/python3.13/site-packages (from scikit-learn>=1.2->llmrouter-lib==0.2.0) (3.6.0)\n", + "Requirement already satisfied: regex>=2022.1.18 in /opt/conda/lib/python3.13/site-packages (from tiktoken>=0.7.0->litellm>=1.0->llmrouter-lib==0.2.0) (2026.1.15)\n", + "Requirement already satisfied: setuptools in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (80.9.0)\n", + "Requirement already satisfied: sympy>=1.13.3 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (1.14.0)\n", + "Requirement already satisfied: networkx>=2.5.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (3.6.1)\n", + "Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.93)\n", + "Requirement already satisfied: nvidia-cuda-runtime-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.90)\n", + "Requirement already satisfied: nvidia-cuda-cupti-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.90)\n", + "Requirement already satisfied: nvidia-cudnn-cu12==9.10.2.21 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (9.10.2.21)\n", + "Requirement already satisfied: nvidia-cublas-cu12==12.8.4.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.4.1)\n", + "Requirement already satisfied: nvidia-cufft-cu12==11.3.3.83 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (11.3.3.83)\n", + "Requirement already satisfied: nvidia-curand-cu12==10.3.9.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (10.3.9.90)\n", + "Requirement already satisfied: nvidia-cusolver-cu12==11.7.3.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (11.7.3.90)\n", + "Requirement already satisfied: nvidia-cusparse-cu12==12.5.8.93 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.5.8.93)\n", + "Requirement already satisfied: nvidia-cusparselt-cu12==0.7.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (0.7.1)\n", + "Requirement already satisfied: nvidia-nccl-cu12==2.27.5 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (2.27.5)\n", + "Requirement already satisfied: nvidia-nvshmem-cu12==3.3.20 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (3.3.20)\n", + "Requirement already satisfied: nvidia-nvtx-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.90)\n", + "Requirement already satisfied: nvidia-nvjitlink-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.93)\n", + "Requirement already satisfied: nvidia-cufile-cu12==1.13.1.3 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (1.13.1.3)\n", + "Requirement already satisfied: triton==3.5.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (3.5.1)\n", + "Requirement already satisfied: mpmath<1.4,>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from sympy>=1.13.3->torch>=2.0->llmrouter-lib==0.2.0) (1.3.0)\n", + "Requirement already satisfied: pyparsing in /opt/conda/lib/python3.13/site-packages (from torch-geometric>=2.3->llmrouter-lib==0.2.0) (3.3.1)\n", + "Building wheels for collected packages: llmrouter-lib\n", + " Building editable for llmrouter-lib (pyproject.toml) ... \u001b[?25ldone\n", + "\u001b[?25h Created wheel for llmrouter-lib: filename=llmrouter_lib-0.2.0-0.editable-py3-none-any.whl size=15709 sha256=ffb5b5840bbaf72b8620517fdb46a164069b08683d5a38bae987263abad5747a\n", + " Stored in directory: /tmp/pip-ephem-wheel-cache-f28j99y7/wheels/82/4a/fd/59c4aec93c356c380d86a88ab74bece164c0aa855d68b155e4\n", + "Successfully built llmrouter-lib\n", + "Installing collected packages: llmrouter-lib\n", + " Attempting uninstall: llmrouter-lib\n", + " Found existing installation: llmrouter-lib 0.2.0\n", + " Uninstalling llmrouter-lib-0.2.0:\n", + " Successfully uninstalled llmrouter-lib-0.2.0\n", + "Successfully installed llmrouter-lib-0.2.0\n", + "Requirement already satisfied: pyyaml in /opt/conda/lib/python3.13/site-packages (6.0.3)\n", + "Requirement already satisfied: openai in /opt/conda/lib/python3.13/site-packages (2.15.0)\n", + "Requirement already satisfied: transformers in /opt/conda/lib/python3.13/site-packages (4.57.6)\n", + "Requirement already satisfied: torch in /opt/conda/lib/python3.13/site-packages (2.9.1)\n", + "Requirement already satisfied: anyio<5,>=3.5.0 in /opt/conda/lib/python3.13/site-packages (from openai) (4.12.0)\n", + "Requirement already satisfied: distro<2,>=1.7.0 in /opt/conda/lib/python3.13/site-packages (from openai) (1.9.0)\n", + "Requirement already satisfied: httpx<1,>=0.23.0 in /opt/conda/lib/python3.13/site-packages (from openai) (0.28.1)\n", + "Requirement already satisfied: jiter<1,>=0.10.0 in /opt/conda/lib/python3.13/site-packages (from openai) (0.12.0)\n", + "Requirement already satisfied: pydantic<3,>=1.9.0 in /opt/conda/lib/python3.13/site-packages (from openai) (2.12.5)\n", + "Requirement already satisfied: sniffio in /opt/conda/lib/python3.13/site-packages (from openai) (1.3.1)\n", + "Requirement already satisfied: tqdm>4 in /opt/conda/lib/python3.13/site-packages (from openai) (4.67.1)\n", + "Requirement already satisfied: typing-extensions<5,>=4.11 in /opt/conda/lib/python3.13/site-packages (from openai) (4.15.0)\n", + "Requirement already satisfied: idna>=2.8 in /opt/conda/lib/python3.13/site-packages (from anyio<5,>=3.5.0->openai) (3.11)\n", + "Requirement already satisfied: certifi in /opt/conda/lib/python3.13/site-packages (from httpx<1,>=0.23.0->openai) (2026.1.4)\n", + "Requirement already satisfied: httpcore==1.* in /opt/conda/lib/python3.13/site-packages (from httpx<1,>=0.23.0->openai) (1.0.9)\n", + "Requirement already satisfied: h11>=0.16 in /opt/conda/lib/python3.13/site-packages (from httpcore==1.*->httpx<1,>=0.23.0->openai) (0.16.0)\n", + "Requirement already satisfied: annotated-types>=0.6.0 in /opt/conda/lib/python3.13/site-packages (from pydantic<3,>=1.9.0->openai) (0.7.0)\n", + "Requirement already satisfied: pydantic-core==2.41.5 in /opt/conda/lib/python3.13/site-packages (from pydantic<3,>=1.9.0->openai) (2.41.5)\n", + "Requirement already satisfied: typing-inspection>=0.4.2 in /opt/conda/lib/python3.13/site-packages (from pydantic<3,>=1.9.0->openai) (0.4.2)\n", + "Requirement already satisfied: filelock in /opt/conda/lib/python3.13/site-packages (from transformers) (3.20.2)\n", + "Requirement already satisfied: huggingface-hub<1.0,>=0.34.0 in /opt/conda/lib/python3.13/site-packages (from transformers) (0.36.0)\n", + "Requirement already satisfied: numpy>=1.17 in /opt/conda/lib/python3.13/site-packages (from transformers) (2.3.5)\n", + "Requirement already satisfied: packaging>=20.0 in /opt/conda/lib/python3.13/site-packages (from transformers) (25.0)\n", + "Requirement already satisfied: regex!=2019.12.17 in /opt/conda/lib/python3.13/site-packages (from transformers) (2026.1.15)\n", + "Requirement already satisfied: requests in /opt/conda/lib/python3.13/site-packages (from transformers) (2.32.5)\n", + "Requirement already satisfied: tokenizers<=0.23.0,>=0.22.0 in /opt/conda/lib/python3.13/site-packages (from transformers) (0.22.2)\n", + "Requirement already satisfied: safetensors>=0.4.3 in /opt/conda/lib/python3.13/site-packages (from transformers) (0.7.0)\n", + "Requirement already satisfied: fsspec>=2023.5.0 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (2025.10.0)\n", + "Requirement already satisfied: hf-xet<2.0.0,>=1.1.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (1.2.0)\n", + "Requirement already satisfied: setuptools in /opt/conda/lib/python3.13/site-packages (from torch) (80.9.0)\n", + "Requirement already satisfied: sympy>=1.13.3 in /opt/conda/lib/python3.13/site-packages (from torch) (1.14.0)\n", + "Requirement already satisfied: networkx>=2.5.1 in /opt/conda/lib/python3.13/site-packages (from torch) (3.6.1)\n", + "Requirement already satisfied: jinja2 in /opt/conda/lib/python3.13/site-packages (from torch) (3.1.6)\n", + "Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.93)\n", + "Requirement already satisfied: nvidia-cuda-runtime-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-cuda-cupti-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-cudnn-cu12==9.10.2.21 in /opt/conda/lib/python3.13/site-packages (from torch) (9.10.2.21)\n", + "Requirement already satisfied: nvidia-cublas-cu12==12.8.4.1 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.4.1)\n", + "Requirement already satisfied: nvidia-cufft-cu12==11.3.3.83 in /opt/conda/lib/python3.13/site-packages (from torch) (11.3.3.83)\n", + "Requirement already satisfied: nvidia-curand-cu12==10.3.9.90 in /opt/conda/lib/python3.13/site-packages (from torch) (10.3.9.90)\n", + "Requirement already satisfied: nvidia-cusolver-cu12==11.7.3.90 in /opt/conda/lib/python3.13/site-packages (from torch) (11.7.3.90)\n", + "Requirement already satisfied: nvidia-cusparse-cu12==12.5.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.5.8.93)\n", + "Requirement already satisfied: nvidia-cusparselt-cu12==0.7.1 in /opt/conda/lib/python3.13/site-packages (from torch) (0.7.1)\n", + "Requirement already satisfied: nvidia-nccl-cu12==2.27.5 in /opt/conda/lib/python3.13/site-packages (from torch) (2.27.5)\n", + "Requirement already satisfied: nvidia-nvshmem-cu12==3.3.20 in /opt/conda/lib/python3.13/site-packages (from torch) (3.3.20)\n", + "Requirement already satisfied: nvidia-nvtx-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-nvjitlink-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.93)\n", + "Requirement already satisfied: nvidia-cufile-cu12==1.13.1.3 in /opt/conda/lib/python3.13/site-packages (from torch) (1.13.1.3)\n", + "Requirement already satisfied: triton==3.5.1 in /opt/conda/lib/python3.13/site-packages (from torch) (3.5.1)\n", + "Requirement already satisfied: mpmath<1.4,>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from sympy>=1.13.3->torch) (1.3.0)\n", + "Requirement already satisfied: MarkupSafe>=2.0 in /opt/conda/lib/python3.13/site-packages (from jinja2->torch) (3.0.3)\n", + "Requirement already satisfied: charset_normalizer<4,>=2 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (3.4.4)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (2.6.2)\n" + ] + } + ], + "source": [ + "# For Google Colab\n", + "import os\n", + "\n", + "!git clone https://github.com/ulab-uiuc/LLMRouter.git\n", + "%cd LLMRouter\n", + "!pip install -e .\n", + "!pip install pyyaml openai transformers torch\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "os.environ['OPENAI_API_KEY'] = 'your-key'\n", + "os.environ['ANTHROPIC_API_KEY'] = 'your-key'\n", + "# Or for multiple keys:\n", + "os.environ['API_KEYS'] = '[\"key1\", \"key2\"]'" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Environment setup complete!\n" + ] + } + ], + "source": [ + "from llmrouter.utils import setup_environment\n", + "import yaml\n", + "\n", + "setup_environment()\n", + "print(\"Environment setup complete!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Configuration\n", + "\n", + "LLMMultiRoundRouter requires:\n", + "\n", + "| Parameter | Description | Required |\n", + "|-----------|-------------|----------|\n", + "| `llm_data` | LLM candidates with descriptions | Yes |\n", + "| `base_model` | LLM for decomposition/aggregation | Yes |\n", + "| `api_endpoint` | API endpoint for execution | Yes |\n", + "| `use_local_llm` | Use vLLM for local inference | No |" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Configuration:\n", + "==================================================\n", + "api_endpoint: https://integrate.api.nvidia.com/v1\n", + "base_model: qwen/qwen2.5-7b-instruct\n", + "data_path:\n", + " llm_data: data/example_data/llm_candidates/default_llm.json\n", + " query_data_test: data/example_data/query_data/default_query_test.jsonl\n", + " routing_data_test: data/example_data/routing_data/default_routing_test_data.jsonl\n", + "use_local_llm: false\n", + "\n" + ] + } + ], + "source": [ + "# Create configuration for LLMMultiRoundRouter\n", + "llm_multi_config = {\n", + " \"data_path\": {\n", + " \"query_data_test\": \"data/example_data/query_data/default_query_test.jsonl\",\n", + " \"routing_data_test\": \"data/example_data/routing_data/default_routing_test_data.jsonl\",\n", + " \"llm_data\": \"data/example_data/llm_candidates/default_llm.json\"\n", + " },\n", + " \"base_model\": \"qwen/qwen2.5-7b-instruct\",\n", + " \"use_local_llm\": False,\n", + " \"api_endpoint\": os.environ.get(\"API_ENDPOINT\", \"https://api.openai.com/v1\")\n", + "}\n", + "\n", + "# Save config\n", + "CONFIG_PATH = \"configs/model_config_train/llmmultiroundrouter_temp.yaml\"\n", + "os.makedirs(os.path.dirname(CONFIG_PATH), exist_ok=True)\n", + "\n", + "with open(CONFIG_PATH, 'w') as f:\n", + " yaml.dump(llm_multi_config, f, default_flow_style=False)\n", + "\n", + "print(\"Configuration:\")\n", + "print(\"=\" * 50)\n", + "print(yaml.dump(llm_multi_config, default_flow_style=False))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Initialize Router" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "✅ MetaRouter initialized successfully (YAML + data loaded).\n", + "Router initialized successfully!\n", + "Base model: qwen/qwen2.5-7b-instruct\n", + "Use local LLM: False\n", + "Number of LLM candidates: 7\n" + ] + } + ], + "source": [ + "from llmrouter.models.llmmultiroundrouter import LLMMultiRoundRouter\n", + "\n", + "try:\n", + " router = LLMMultiRoundRouter(yaml_path=CONFIG_PATH)\n", + " print(\"Router initialized successfully!\")\n", + " print(f\"Base model: {router.base_model}\")\n", + " print(f\"Use local LLM: {router.use_local_llm}\")\n", + " print(f\"Number of LLM candidates: {len(router.llm_data)}\")\n", + "except Exception as e:\n", + " print(f\"Error initializing router: {e}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Available LLM Candidates:\n", + "============================================================\n", + "\n", + "qwen2.5-7b-instruct:\n", + " Size: 7BB parameters\n", + "\n", + "llama-3.1-8b-instruct:\n", + " Size: 8BB parameters\n", + "\n", + "mistral-7b-instruct-v0.3:\n", + " Size: 7BB parameters\n", + "\n", + "llama-3.3-nemotron-super-49b-v1:\n", + " Size: 49BB parameters\n", + "\n", + "llama3-70b-instruct:\n", + " Size: 70BB parameters\n", + "\n", + "mixtral-8x7b-instruct-v0.1:\n", + " Size: 45BB parameters\n", + "\n", + "mixtral-8x22b-instruct-v0.1:\n", + " Size: 141BB parameters\n" + ] + } + ], + "source": [ + "# Display LLM candidates with their descriptions\n", + "print(\"Available LLM Candidates:\")\n", + "print(\"=\" * 60)\n", + "\n", + "for name, info in router.llm_data.items():\n", + " print(f\"\\n{name}:\")\n", + " if 'description' in info:\n", + " print(f\" Description: {info['description'][:100]}...\")\n", + " if 'size' in info:\n", + " print(f\" Size: {info['size']}B parameters\")\n", + " if 'capabilities' in info:\n", + " print(f\" Capabilities: {info['capabilities']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Chat Mode (Simple Queries)\n", + "\n", + "For simple queries, pass a string and get a string response." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Query: What are the main causes of climate change and what solutions exist?\n", + "============================================================\n", + "\n", + "Response:\n", + "The main causes of climate change are primarily human activities, which include:\n", + "\n", + "1. **Burning Fossil Fuels**: The combustion of coal, oil, and gas for energy releases large amounts of carbon dioxide (CO₂) and other greenhouse gases into the atmosphere.\n", + "2. **Deforestation**: Cutting down forests reduces the number of trees that can absorb CO₂, and often leads to the release of stored carbon when trees are burned or decompose.\n", + "3. **Agriculture**: Practices such as livestock farming and rice cultivation release methane, a potent greenhouse gas. The use of fertilizers also leads to increased nitrous oxide emissions.\n", + "4. **Industrial Processes**: Various industrial activities release greenhouse gases, including CO₂, methane, and fluorinated gases.\n", + "5. **Waste Management**: Landfills produce methane as organic waste decomposes.\n", + "\n", + "To address these causes, solutions can be broadly categorized into mitigation and adaptation strategies:\n", + "\n", + "1. **Mitigation**:\n", + " - **Renewable Energy**: Transitioning from fossil fuels to renewable sources like solar, wind, and hydroelectric power.\n", + " - **Energy Efficiency**: Improving the efficiency of buildings, vehicles, and industrial processes.\n", + " - **Carbon Capture and Storage (CCS)**: Capturing CO2 emissions from power plants and industrial processes and storing them underground.\n", + " - **Afforestation and Reforestation**: Planting trees to absorb CO2 from the atmosphere.\n", + "\n", + "2. **Adaptation**:\n", + " - **Infrastructure Resilience**: Building sea walls, improving drainage systems, and reinforcing buildings to withstand extreme weather events.\n", + " - **Water Management**: Implementing water-saving technologies and improving water storage and distribution systems.\n", + " - **Agricultural Practices**: Developing drought-resistant crops and improving irrigation techniques.\n", + " - **Early Warning Systems**: Establishing systems to predict and respond to extreme weather events.\n", + "\n", + "Combining both mitigation and adaptation strategies is crucial for effectively addressing climate change.\n", + "\n", + "\n", + "The main causes of climate change are primarily human activities such as burning fossil fuels, deforestation, agriculture, industrial processes, and waste management. To address these causes, solutions can be broadly categorized into mitigation and adaptation strategies. Mitigation strategies include transitioning to renewable energy, improving energy efficiency, carbon capture and storage, and afforestation. Adaptation strategies involve infrastructure resilience, water management, agricultural practices, and early warning systems. Combining both mitigation and adaptation strategies is essential for effectively addressing climate change.\n", + "\n" + ] + } + ], + "source": [ + "# Chat mode - simple string input/output\n", + "query = \"What are the main causes of climate change and what solutions exist?\"\n", + "\n", + "print(f\"Query: {query}\")\n", + "print(\"=\" * 60)\n", + "\n", + "try:\n", + " response = router.route_single(query)\n", + " print(f\"\\nResponse:\\n{response}\")\n", + "except Exception as e:\n", + " print(f\"Error: {e}\")\n", + " print(\"\\nNote: LLMMultiRoundRouter requires API access for LLM calls.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Evaluation Mode (With Metrics)\n", + "\n", + "For evaluation, pass a dict with task_name and ground_truth." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Query: What is the largest planet in our solar system?\n", + "Task: trivia\n", + "Ground Truth: Jupiter\n", + "============================================================\n", + "\n", + "Response: To determine the largest planet in our solar system, let's analyze the provided information step by step:\n", + "\n", + "1. **Sub-query: What is the largest planet in our solar system?**\n", + " - Response: The largest planet in our solar system is Jupiter.\n", + "\n", + "2. **Sub-query: What is the name of the largest planet in our solar system?**\n", + " - Response: The largest planet in our solar system is Jupiter.\n", + "\n", + "3. **Sub-query: What is the size of the largest planet in our solar system?**\n", + " - Response: The largest planet in our solar system is Jupiter, with a diameter of approximately 86,881 miles (139,822 kilometers).\n", + "\n", + "From these sub-queries, we can clearly see that Jupiter is consistently identified as the largest planet in our solar system. The additional information about its size further confirms this.\n", + "\n", + "Therefore, the largest planet in our solar system is Jupiter.\n", + "\n", + "Jupiter\n", + "Success: True\n", + "Prompt Tokens: 819\n", + "Completion Tokens: 170\n", + "Task Performance: 1.00\n" + ] + } + ], + "source": [ + "# Evaluation mode - dict input with metrics\n", + "eval_query = {\n", + " \"query\": \"What is the largest planet in our solar system?\",\n", + " \"task_name\": \"trivia\",\n", + " \"ground_truth\": \"Jupiter\"\n", + "}\n", + "\n", + "print(f\"Query: {eval_query['query']}\")\n", + "print(f\"Task: {eval_query['task_name']}\")\n", + "print(f\"Ground Truth: {eval_query['ground_truth']}\")\n", + "print(\"=\" * 60)\n", + "\n", + "try:\n", + " result = router.route_single(eval_query)\n", + " \n", + " print(f\"\\nResponse: {result.get('response', 'N/A')}\")\n", + " print(f\"Success: {result.get('success', False)}\")\n", + " print(f\"Prompt Tokens: {result.get('prompt_tokens', 0)}\")\n", + " print(f\"Completion Tokens: {result.get('completion_tokens', 0)}\")\n", + " if 'task_performance' in result:\n", + " print(f\"Task Performance: {result['task_performance']:.2f}\")\n", + "except Exception as e:\n", + " print(f\"Error: {e}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. Batch Processing" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Processing 3 queries...\n", + "============================================================\n", + "\n", + "\u001b[1;31mGive Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new\u001b[0m\n", + "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.\n", + "\n", + "\n", + "1. Query: Explain quantum computing....\n", + " Success: True\n", + " Response: API Error: litellm.Timeout: APITimeoutError - Request timed out. Error_str: Request timed out....\n", + "\n", + "2. Query: What is the difference between AI and ML?...\n", + " Success: True\n", + " Response: To understand the difference between AI and ML, let's break it down step by step:\n", + "\n", + "1. **Scope**:\n", + " ...\n", + "\n", + "3. Query: How does blockchain technology work?...\n", + " Success: True\n", + " Response: Blockchain technology works through a combination of decentralized networks, cryptographic technique...\n" + ] + } + ], + "source": [ + "# Batch processing\n", + "batch_queries = [\n", + " {\"query\": \"Explain quantum computing.\"},\n", + " {\"query\": \"What is the difference between AI and ML?\"},\n", + " {\"query\": \"How does blockchain technology work?\"},\n", + "]\n", + "\n", + "print(f\"Processing {len(batch_queries)} queries...\")\n", + "print(\"=\" * 60)\n", + "\n", + "\n", + "try:\n", + " results = router.route_batch(batch_queries)\n", + " \n", + " for i, result in enumerate(results, 1):\n", + " print(f\"\\n{i}. Query: {result.get('query', 'N/A')[:50]}...\")\n", + " print(f\" Success: {result.get('success', False)}\")\n", + " response = result.get('response', 'N/A')\n", + " print(f\" Response: {response[:100] if response else 'N/A'}...\")\n", + "except Exception as e:\n", + " print(f\"Error: {e}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7. Task-Specific Routing\n", + "\n", + "LLMMultiRoundRouter supports task-specific prompts for evaluation." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Multiple Choice Query:\n", + "Question: What is the capital of Australia? A) Sydney B) Melbourne C) Canberra D) Perth\n", + "Ground Truth: C\n", + "============================================================\n", + "\n", + "Response: C\n" + ] + } + ], + "source": [ + "# Multiple choice task\n", + "mc_query = {\n", + " \"query\": \"What is the capital of Australia? A) Sydney B) Melbourne C) Canberra D) Perth\",\n", + " \"task_name\": \"commonsense_qa\",\n", + " \"choices\": {\n", + " \"label\": [\"A\", \"B\", \"C\", \"D\"],\n", + " \"text\": [\"Sydney\", \"Melbourne\", \"Canberra\", \"Perth\"]\n", + " },\n", + " \"ground_truth\": \"C\"\n", + "}\n", + "\n", + "print(f\"Multiple Choice Query:\")\n", + "print(f\"Question: {mc_query['query']}\")\n", + "print(f\"Ground Truth: {mc_query['ground_truth']}\")\n", + "print(\"=\" * 60)\n", + "\n", + "try:\n", + " results = router.route_batch([mc_query], task_name=\"commonsense_qa\")\n", + " result = results[0]\n", + " \n", + " print(f\"\\nResponse: {result.get('response', 'N/A')}\")\n", + " if 'task_performance' in result:\n", + " print(f\"Correct: {result['task_performance'] == 1.0}\")\n", + "except Exception as e:\n", + " print(f\"Error: {e}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 8. Understanding the Pipeline\n", + "\n", + "Let's examine how LLMMultiRoundRouter works." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(\"LLMMultiRoundRouter Pipeline:\")\n", + "print(\"=\" * 60)\n", + "\n", + "print(\"\"\"\n", + "┌─────────────────────────────────────────────────────────────┐\n", + "│ Input Query │\n", + "│ \"What are the causes of climate change and solutions?\" │\n", + "└────────────────────────┬────────────────────────────────────┘\n", + " │\n", + " ▼\n", + "┌─────────────────────────────────────────────────────────────┐\n", + "│ Step 1: Decompose + Route │\n", + "│ LLM breaks query into sub-queries AND routes each │\n", + "│ │\n", + "│ Output format: : │\n", + "│ • \"What causes climate change?\": Qwen-7B │\n", + "│ • \"What solutions exist for climate change?\": Llama-70B │\n", + "└────────────────────────┬────────────────────────────────────┘\n", + " │\n", + " ▼\n", + "┌─────────────────────────────────────────────────────────────┐\n", + "│ Step 2: Execute Sub-queries │\n", + "│ Call routed model API for each sub-query │\n", + "│ │\n", + "│ • Qwen-7B → \"Greenhouse gases, deforestation...\" │\n", + "│ • Llama-70B → \"Renewable energy, carbon capture...\" │\n", + "└────────────────────────┬────────────────────────────────────┘\n", + " │\n", + " ▼\n", + "┌─────────────────────────────────────────────────────────────┐\n", + "│ Step 3: Aggregate Responses │\n", + "│ LLM combines sub-responses into coherent final answer │\n", + "│ │\n", + "│ \"Climate change is caused by greenhouse gases... │\n", + "│ Solutions include renewable energy and...\" │\n", + "└─────────────────────────────────────────────────────────────┘\n", + "\"\"\")\n", + "\n", + "print(\"\\nKey Advantages:\")\n", + "print(\"• No training required - uses LLM reasoning directly\")\n", + "print(\"• Model descriptions guide intelligent routing decisions\")\n", + "print(\"• Single-step decomposition and routing for efficiency\")\n", + "print(\"• Flexible aggregation based on task type\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 9. Comparison with KNNMultiRoundRouter" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(\"LLMMultiRoundRouter vs KNNMultiRoundRouter:\")\n", + "print(\"=\" * 60)\n", + "\n", + "comparison = \"\"\"\n", + "| Feature | LLMMultiRoundRouter | KNNMultiRoundRouter |\n", + "|----------------------|-------------------------|-------------------------|\n", + "| Training Required | No | Yes (KNN fitting) |\n", + "| Routing Method | LLM reasoning | KNN on embeddings |\n", + "| Model Selection | Based on descriptions | Based on similarity |\n", + "| Decomposition | Same LLM call | Separate LLM call |\n", + "| Flexibility | High (prompt-based) | Medium (learned) |\n", + "| Inference Cost | Higher (more LLM calls) | Lower (KNN is fast) |\n", + "| Cold Start | Works immediately | Needs training data |\n", + "\"\"\"\n", + "print(comparison)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 10. File-Based Inference\n", + "\n", + "Load queries from a file and save results." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loaded 706 queries from: data/example_data/query_data/default_query_test.jsonl\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "\n", + "\u001b[1;31mGive Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new\u001b[0m\n", + "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.\n", + "\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Routed 5 queries\n", + "Results saved to: outputs/llmmultiroundrouter_results.jsonl\n", + "\n", + "Sample results:\n", + " 1. Q: There are 4 houses in a row, numbered...\n", + " Success: True\n", + " 2. Q: There are 3 houses in a row, numbered...\n", + " Success: True\n", + " 3. Q: There are 3 houses in a row, numbered...\n", + " Success: True\n" + ] + } + ], + "source": [ + "import json\n", + "\n", + "# Load queries from a JSONL file\n", + "def load_queries_from_file(file_path):\n", + " \"\"\"Load queries from a JSONL file.\"\"\"\n", + " queries = []\n", + " with open(file_path, 'r', encoding='utf-8') as f:\n", + " for line in f:\n", + " if line.strip():\n", + " queries.append(json.loads(line))\n", + " return queries\n", + "\n", + "# Save results to a JSONL file\n", + "def save_results_to_file(results, output_path):\n", + " \"\"\"Save routing results to a JSONL file.\"\"\"\n", + " os.makedirs(os.path.dirname(output_path), exist_ok=True)\n", + " with open(output_path, 'w', encoding='utf-8') as f:\n", + " for result in results:\n", + " f.write(json.dumps(result, ensure_ascii=False) + '\\n')\n", + " print(f\"Results saved to: {output_path}\")\n", + "\n", + "# Example: Load from default query file\n", + "QUERY_FILE = \"data/example_data/query_data/default_query_test.jsonl\"\n", + "OUTPUT_FILE = \"outputs/llmmultiroundrouter_results.jsonl\"\n", + "\n", + "if os.path.exists(QUERY_FILE):\n", + " # Load queries\n", + " file_queries = load_queries_from_file(QUERY_FILE)\n", + " print(f\"Loaded {len(file_queries)} queries from: {QUERY_FILE}\")\n", + " \n", + " # Route queries (limit to 5 for demo due to API costs)\n", + " try:\n", + " file_results = router.route_batch(file_queries[:5])\n", + " print(f\"Routed {len(file_results)} queries\")\n", + " \n", + " # Save results\n", + " save_results_to_file(file_results, OUTPUT_FILE)\n", + " \n", + " # Show sample results\n", + " print(f\"\\nSample results:\")\n", + " for i, result in enumerate(file_results[:3], 1):\n", + " print(f\" {i}. {result.get('query', '')[:40]}...\")\n", + " print(f\" Success: {result.get('success', False)}\")\n", + " except Exception as e:\n", + " print(f\"Error during batch routing: {e}\")\n", + "else:\n", + " print(f\"Query file not found: {QUERY_FILE}\")\n", + " print(\"Create a JSONL file with format: {\\\"query\\\": \\\"Your question\\\"}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "**LLMMultiRoundRouter** provides:\n", + "- Zero-shot multi-round routing (no training)\n", + "- LLM-based decomposition and routing in one step\n", + "- Model description-guided routing decisions\n", + "- Flexible aggregation for different task types\n", + "\n", + "**Use Cases**:\n", + "- Quick prototyping without training data\n", + "- Complex queries requiring expert routing decisions\n", + "- When model descriptions are more reliable than embeddings\n", + "- Low-volume, high-quality routing needs\n", + "\n", + "**Requirements**:\n", + "- API access for LLM calls\n", + "- Optional: vLLM for local inference\n", + "- Model descriptions in llm_data (recommended)" + ] + } + ], + "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.13.11" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/colab_notebooks/mfrouter/01_mfrouter_training_and_inference.ipynb b/colab_notebooks/mfrouter/01_mfrouter_training_and_inference.ipynb new file mode 100644 index 0000000..efefe66 --- /dev/null +++ b/colab_notebooks/mfrouter/01_mfrouter_training_and_inference.ipynb @@ -0,0 +1 @@ +{"cells":[{"cell_type":"markdown","metadata":{"id":"zjPl0CYiuK1P"},"source":["# MFRouter - Training\n","\n","This notebook demonstrates how to train the **MFRouter** (Matrix Factorization Router).\n","\n","## Overview\n","\n","MFRouter uses matrix factorization to learn latent representations for both queries and LLMs,\n","then predicts the best LLM based on the similarity in the latent space.\n","\n","**Key Features**:\n","- Learns embeddings for both queries and models\n","- Can capture collaborative filtering patterns\n","- Effective for large query-model interaction matrices"]},{"cell_type":"markdown","metadata":{"id":"ssWNH0bbuK1Q"},"source":["## 1. Environment Setup"]},{"cell_type":"code","source":["# Install required packages (for Colab)\n","\n","!git clone https://github.com/ulab-uiuc/LLMRouter.git\n","%cd LLMRouter\n","!pip install -e .\n","!pip install transformers torch"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"MeUJKuhkuOqp","executionInfo":{"status":"ok","timestamp":1767593356081,"user_tz":-480,"elapsed":29653,"user":{"displayName":"dai","userId":"16597391218515739453"}},"outputId":"690503fb-cfe9-49bf-819b-ad22dd078c98"},"execution_count":null,"outputs":[{"output_type":"stream","name":"stdout","text":["Collecting llmrouter-lib\n"," Downloading llmrouter_lib-0.1.1-py3-none-any.whl.metadata (27 kB)\n","Requirement already satisfied: transformers in /usr/local/lib/python3.12/dist-packages (4.57.3)\n","Requirement already satisfied: torch in /usr/local/lib/python3.12/dist-packages (2.9.0+cpu)\n","Requirement already satisfied: peft in /usr/local/lib/python3.12/dist-packages (0.18.0)\n","Requirement already satisfied: accelerate in /usr/local/lib/python3.12/dist-packages (1.12.0)\n","Collecting bitsandbytes\n"," Downloading bitsandbytes-0.49.0-py3-none-manylinux_2_24_x86_64.whl.metadata (10 kB)\n","Requirement already satisfied: sentencepiece>=0.1.99 in /usr/local/lib/python3.12/dist-packages (from llmrouter-lib) (0.2.1)\n","Requirement already satisfied: numpy>=1.21 in /usr/local/lib/python3.12/dist-packages (from llmrouter-lib) (2.0.2)\n","Requirement already satisfied: pandas>=1.5 in /usr/local/lib/python3.12/dist-packages (from llmrouter-lib) (2.2.2)\n","Requirement already satisfied: scikit-learn>=1.2 in /usr/local/lib/python3.12/dist-packages (from llmrouter-lib) (1.6.1)\n","Requirement already satisfied: pyyaml>=6.0 in /usr/local/lib/python3.12/dist-packages (from llmrouter-lib) (6.0.3)\n","Requirement already satisfied: tqdm>=4.65 in /usr/local/lib/python3.12/dist-packages (from llmrouter-lib) (4.67.1)\n","Requirement already satisfied: datasets>=2.14 in /usr/local/lib/python3.12/dist-packages (from llmrouter-lib) (4.0.0)\n","Requirement already satisfied: pydantic>=2.0 in /usr/local/lib/python3.12/dist-packages (from llmrouter-lib) (2.12.3)\n","Requirement already satisfied: gradio>=4.0 in /usr/local/lib/python3.12/dist-packages (from llmrouter-lib) (5.50.0)\n","Collecting litellm>=1.0 (from llmrouter-lib)\n"," Downloading litellm-1.80.11-py3-none-any.whl.metadata (29 kB)\n","Collecting torch-geometric>=2.3 (from llmrouter-lib)\n"," Downloading torch_geometric-2.7.0-py3-none-any.whl.metadata (63 kB)\n","\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m63.7/63.7 kB\u001b[0m \u001b[31m2.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n","\u001b[?25hRequirement already satisfied: scipy>=1.10 in /usr/local/lib/python3.12/dist-packages (from llmrouter-lib) (1.16.3)\n","Requirement already satisfied: protobuf>=3.20 in /usr/local/lib/python3.12/dist-packages (from llmrouter-lib) (5.29.5)\n","Requirement already satisfied: filelock in /usr/local/lib/python3.12/dist-packages (from transformers) (3.20.0)\n","Requirement already satisfied: huggingface-hub<1.0,>=0.34.0 in /usr/local/lib/python3.12/dist-packages (from transformers) (0.36.0)\n","Requirement already satisfied: packaging>=20.0 in /usr/local/lib/python3.12/dist-packages (from transformers) (25.0)\n","Requirement already satisfied: regex!=2019.12.17 in /usr/local/lib/python3.12/dist-packages (from transformers) (2025.11.3)\n","Requirement already satisfied: requests in /usr/local/lib/python3.12/dist-packages (from transformers) (2.32.4)\n","Requirement already satisfied: tokenizers<=0.23.0,>=0.22.0 in /usr/local/lib/python3.12/dist-packages (from transformers) (0.22.1)\n","Requirement already satisfied: safetensors>=0.4.3 in /usr/local/lib/python3.12/dist-packages (from transformers) (0.7.0)\n","Requirement already satisfied: typing-extensions>=4.10.0 in /usr/local/lib/python3.12/dist-packages (from torch) (4.15.0)\n","Requirement already satisfied: setuptools in /usr/local/lib/python3.12/dist-packages (from torch) (75.2.0)\n","Requirement already satisfied: sympy>=1.13.3 in /usr/local/lib/python3.12/dist-packages (from torch) (1.14.0)\n","Requirement already satisfied: networkx>=2.5.1 in /usr/local/lib/python3.12/dist-packages (from torch) (3.6.1)\n","Requirement already satisfied: jinja2 in /usr/local/lib/python3.12/dist-packages (from torch) (3.1.6)\n","Requirement already satisfied: fsspec>=0.8.5 in /usr/local/lib/python3.12/dist-packages (from torch) (2025.3.0)\n","Requirement already satisfied: psutil in /usr/local/lib/python3.12/dist-packages (from peft) (5.9.5)\n","Requirement already satisfied: pyarrow>=15.0.0 in /usr/local/lib/python3.12/dist-packages (from datasets>=2.14->llmrouter-lib) (18.1.0)\n","Requirement already satisfied: dill<0.3.9,>=0.3.0 in /usr/local/lib/python3.12/dist-packages (from datasets>=2.14->llmrouter-lib) (0.3.8)\n","Requirement already satisfied: xxhash in /usr/local/lib/python3.12/dist-packages (from datasets>=2.14->llmrouter-lib) (3.6.0)\n","Requirement already satisfied: multiprocess<0.70.17 in /usr/local/lib/python3.12/dist-packages (from datasets>=2.14->llmrouter-lib) (0.70.16)\n","Requirement already satisfied: aiofiles<25.0,>=22.0 in /usr/local/lib/python3.12/dist-packages (from gradio>=4.0->llmrouter-lib) (24.1.0)\n","Requirement already satisfied: anyio<5.0,>=3.0 in /usr/local/lib/python3.12/dist-packages (from gradio>=4.0->llmrouter-lib) (4.12.0)\n","Requirement already satisfied: brotli>=1.1.0 in /usr/local/lib/python3.12/dist-packages (from gradio>=4.0->llmrouter-lib) (1.2.0)\n","Requirement already satisfied: fastapi<1.0,>=0.115.2 in /usr/local/lib/python3.12/dist-packages (from gradio>=4.0->llmrouter-lib) (0.123.10)\n","Requirement already satisfied: ffmpy in /usr/local/lib/python3.12/dist-packages (from gradio>=4.0->llmrouter-lib) (1.0.0)\n","Requirement already satisfied: gradio-client==1.14.0 in /usr/local/lib/python3.12/dist-packages (from gradio>=4.0->llmrouter-lib) (1.14.0)\n","Requirement already satisfied: groovy~=0.1 in /usr/local/lib/python3.12/dist-packages (from gradio>=4.0->llmrouter-lib) (0.1.2)\n","Requirement already satisfied: httpx<1.0,>=0.24.1 in /usr/local/lib/python3.12/dist-packages (from gradio>=4.0->llmrouter-lib) (0.28.1)\n","Requirement already satisfied: markupsafe<4.0,>=2.0 in /usr/local/lib/python3.12/dist-packages (from gradio>=4.0->llmrouter-lib) (3.0.3)\n","Requirement already satisfied: orjson~=3.0 in /usr/local/lib/python3.12/dist-packages (from gradio>=4.0->llmrouter-lib) (3.11.5)\n","Requirement already satisfied: pillow<12.0,>=8.0 in /usr/local/lib/python3.12/dist-packages (from gradio>=4.0->llmrouter-lib) (11.3.0)\n","Requirement already satisfied: pydub in /usr/local/lib/python3.12/dist-packages (from gradio>=4.0->llmrouter-lib) (0.25.1)\n","Requirement already satisfied: python-multipart>=0.0.18 in /usr/local/lib/python3.12/dist-packages (from gradio>=4.0->llmrouter-lib) (0.0.20)\n","Requirement already satisfied: ruff>=0.9.3 in /usr/local/lib/python3.12/dist-packages (from gradio>=4.0->llmrouter-lib) (0.14.9)\n","Requirement already satisfied: safehttpx<0.2.0,>=0.1.6 in /usr/local/lib/python3.12/dist-packages (from gradio>=4.0->llmrouter-lib) (0.1.7)\n","Requirement already satisfied: semantic-version~=2.0 in /usr/local/lib/python3.12/dist-packages (from gradio>=4.0->llmrouter-lib) (2.10.0)\n","Requirement already satisfied: starlette<1.0,>=0.40.0 in /usr/local/lib/python3.12/dist-packages (from gradio>=4.0->llmrouter-lib) (0.50.0)\n","Requirement already satisfied: tomlkit<0.14.0,>=0.12.0 in /usr/local/lib/python3.12/dist-packages (from gradio>=4.0->llmrouter-lib) (0.13.3)\n","Requirement already satisfied: typer<1.0,>=0.12 in /usr/local/lib/python3.12/dist-packages (from gradio>=4.0->llmrouter-lib) (0.20.0)\n","Requirement already satisfied: uvicorn>=0.14.0 in /usr/local/lib/python3.12/dist-packages (from gradio>=4.0->llmrouter-lib) (0.38.0)\n","Requirement already satisfied: websockets<16.0,>=13.0 in /usr/local/lib/python3.12/dist-packages (from gradio-client==1.14.0->gradio>=4.0->llmrouter-lib) (15.0.1)\n","Requirement already satisfied: hf-xet<2.0.0,>=1.1.3 in /usr/local/lib/python3.12/dist-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (1.2.0)\n","Requirement already satisfied: aiohttp>=3.10 in /usr/local/lib/python3.12/dist-packages (from litellm>=1.0->llmrouter-lib) (3.13.2)\n","Requirement already satisfied: click in /usr/local/lib/python3.12/dist-packages (from litellm>=1.0->llmrouter-lib) (8.3.1)\n","Collecting fastuuid>=0.13.0 (from litellm>=1.0->llmrouter-lib)\n"," Downloading fastuuid-0.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.1 kB)\n","Collecting grpcio<1.68.0,>=1.62.3 (from litellm>=1.0->llmrouter-lib)\n"," Downloading grpcio-1.67.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (3.9 kB)\n","Requirement already satisfied: importlib-metadata>=6.8.0 in /usr/local/lib/python3.12/dist-packages (from litellm>=1.0->llmrouter-lib) (8.7.0)\n","Requirement already satisfied: jsonschema<5.0.0,>=4.23.0 in /usr/local/lib/python3.12/dist-packages (from litellm>=1.0->llmrouter-lib) (4.25.1)\n","Requirement already satisfied: openai>=2.8.0 in /usr/local/lib/python3.12/dist-packages (from litellm>=1.0->llmrouter-lib) (2.12.0)\n","Requirement already satisfied: python-dotenv>=0.2.0 in /usr/local/lib/python3.12/dist-packages (from litellm>=1.0->llmrouter-lib) (1.2.1)\n","Requirement already satisfied: tiktoken>=0.7.0 in /usr/local/lib/python3.12/dist-packages (from litellm>=1.0->llmrouter-lib) (0.12.0)\n","Requirement already satisfied: python-dateutil>=2.8.2 in /usr/local/lib/python3.12/dist-packages (from pandas>=1.5->llmrouter-lib) (2.9.0.post0)\n","Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.12/dist-packages (from pandas>=1.5->llmrouter-lib) (2025.2)\n","Requirement already satisfied: tzdata>=2022.7 in /usr/local/lib/python3.12/dist-packages (from pandas>=1.5->llmrouter-lib) (2025.3)\n","Requirement already satisfied: annotated-types>=0.6.0 in /usr/local/lib/python3.12/dist-packages (from pydantic>=2.0->llmrouter-lib) (0.7.0)\n","Requirement already satisfied: pydantic-core==2.41.4 in /usr/local/lib/python3.12/dist-packages (from pydantic>=2.0->llmrouter-lib) (2.41.4)\n","Requirement already satisfied: typing-inspection>=0.4.2 in /usr/local/lib/python3.12/dist-packages (from pydantic>=2.0->llmrouter-lib) (0.4.2)\n","Requirement already satisfied: charset_normalizer<4,>=2 in /usr/local/lib/python3.12/dist-packages (from requests->transformers) (3.4.4)\n","Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.12/dist-packages (from requests->transformers) (3.11)\n","Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.12/dist-packages (from requests->transformers) (2.5.0)\n","Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.12/dist-packages (from requests->transformers) (2025.11.12)\n","Requirement already satisfied: joblib>=1.2.0 in /usr/local/lib/python3.12/dist-packages (from scikit-learn>=1.2->llmrouter-lib) (1.5.3)\n","Requirement already satisfied: threadpoolctl>=3.1.0 in /usr/local/lib/python3.12/dist-packages (from scikit-learn>=1.2->llmrouter-lib) (3.6.0)\n","Requirement already satisfied: mpmath<1.4,>=1.1.0 in /usr/local/lib/python3.12/dist-packages (from sympy>=1.13.3->torch) (1.3.0)\n","Requirement already satisfied: pyparsing in /usr/local/lib/python3.12/dist-packages (from torch-geometric>=2.3->llmrouter-lib) (3.2.5)\n","Requirement already satisfied: aiohappyeyeballs>=2.5.0 in /usr/local/lib/python3.12/dist-packages (from aiohttp>=3.10->litellm>=1.0->llmrouter-lib) (2.6.1)\n","Requirement already satisfied: aiosignal>=1.4.0 in /usr/local/lib/python3.12/dist-packages (from aiohttp>=3.10->litellm>=1.0->llmrouter-lib) (1.4.0)\n","Requirement already satisfied: attrs>=17.3.0 in /usr/local/lib/python3.12/dist-packages (from aiohttp>=3.10->litellm>=1.0->llmrouter-lib) (25.4.0)\n","Requirement already satisfied: frozenlist>=1.1.1 in /usr/local/lib/python3.12/dist-packages (from aiohttp>=3.10->litellm>=1.0->llmrouter-lib) (1.8.0)\n","Requirement already satisfied: multidict<7.0,>=4.5 in /usr/local/lib/python3.12/dist-packages (from aiohttp>=3.10->litellm>=1.0->llmrouter-lib) (6.7.0)\n","Requirement already satisfied: propcache>=0.2.0 in /usr/local/lib/python3.12/dist-packages (from aiohttp>=3.10->litellm>=1.0->llmrouter-lib) (0.4.1)\n","Requirement already satisfied: yarl<2.0,>=1.17.0 in /usr/local/lib/python3.12/dist-packages (from aiohttp>=3.10->litellm>=1.0->llmrouter-lib) (1.22.0)\n","Requirement already satisfied: annotated-doc>=0.0.2 in /usr/local/lib/python3.12/dist-packages (from fastapi<1.0,>=0.115.2->gradio>=4.0->llmrouter-lib) (0.0.4)\n","Requirement already satisfied: httpcore==1.* in /usr/local/lib/python3.12/dist-packages (from httpx<1.0,>=0.24.1->gradio>=4.0->llmrouter-lib) (1.0.9)\n","Requirement already satisfied: h11>=0.16 in /usr/local/lib/python3.12/dist-packages (from httpcore==1.*->httpx<1.0,>=0.24.1->gradio>=4.0->llmrouter-lib) (0.16.0)\n","Requirement already satisfied: zipp>=3.20 in /usr/local/lib/python3.12/dist-packages (from importlib-metadata>=6.8.0->litellm>=1.0->llmrouter-lib) (3.23.0)\n","Requirement already satisfied: jsonschema-specifications>=2023.03.6 in /usr/local/lib/python3.12/dist-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib) (2025.9.1)\n","Requirement already satisfied: referencing>=0.28.4 in /usr/local/lib/python3.12/dist-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib) (0.37.0)\n","Requirement already satisfied: rpds-py>=0.7.1 in /usr/local/lib/python3.12/dist-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib) (0.30.0)\n","Requirement already satisfied: distro<2,>=1.7.0 in /usr/local/lib/python3.12/dist-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib) (1.9.0)\n","Requirement already satisfied: jiter<1,>=0.10.0 in /usr/local/lib/python3.12/dist-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib) (0.12.0)\n","Requirement already satisfied: sniffio in /usr/local/lib/python3.12/dist-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib) (1.3.1)\n","Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.12/dist-packages (from python-dateutil>=2.8.2->pandas>=1.5->llmrouter-lib) (1.17.0)\n","Requirement already satisfied: shellingham>=1.3.0 in /usr/local/lib/python3.12/dist-packages (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib) (1.5.4)\n","Requirement already satisfied: rich>=10.11.0 in /usr/local/lib/python3.12/dist-packages (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib) (13.9.4)\n","Requirement already satisfied: markdown-it-py>=2.2.0 in /usr/local/lib/python3.12/dist-packages (from rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib) (4.0.0)\n","Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /usr/local/lib/python3.12/dist-packages (from rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib) (2.19.2)\n","Requirement already satisfied: mdurl~=0.1 in /usr/local/lib/python3.12/dist-packages (from markdown-it-py>=2.2.0->rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib) (0.1.2)\n","Downloading llmrouter_lib-0.1.1-py3-none-any.whl (236 kB)\n","\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m236.2/236.2 kB\u001b[0m \u001b[31m5.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n","\u001b[?25hDownloading bitsandbytes-0.49.0-py3-none-manylinux_2_24_x86_64.whl (59.1 MB)\n","\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m59.1/59.1 MB\u001b[0m \u001b[31m11.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n","\u001b[?25hDownloading litellm-1.80.11-py3-none-any.whl (11.5 MB)\n","\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m11.5/11.5 MB\u001b[0m \u001b[31m98.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n","\u001b[?25hDownloading torch_geometric-2.7.0-py3-none-any.whl (1.3 MB)\n","\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.3/1.3 MB\u001b[0m \u001b[31m64.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n","\u001b[?25hDownloading fastuuid-0.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (278 kB)\n","\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m278.1/278.1 kB\u001b[0m \u001b[31m22.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n","\u001b[?25hDownloading grpcio-1.67.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (5.9 MB)\n","\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m5.9/5.9 MB\u001b[0m \u001b[31m100.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n","\u001b[?25hInstalling collected packages: grpcio, fastuuid, torch-geometric, bitsandbytes, litellm, llmrouter-lib\n"," Attempting uninstall: grpcio\n"," Found existing installation: grpcio 1.76.0\n"," Uninstalling grpcio-1.76.0:\n"," Successfully uninstalled grpcio-1.76.0\n","\u001b[31mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\n","grpcio-status 1.71.2 requires grpcio>=1.71.2, but you have grpcio 1.67.1 which is incompatible.\u001b[0m\u001b[31m\n","\u001b[0mSuccessfully installed bitsandbytes-0.49.0 fastuuid-0.14.0 grpcio-1.67.1 litellm-1.80.11 llmrouter-lib-0.1.1 torch-geometric-2.7.0\n","Cloning into 'LLMRouter'...\n","remote: Enumerating objects: 5849, done.\u001b[K\n","remote: Counting objects: 100% (172/172), done.\u001b[K\n","remote: Compressing objects: 100% (108/108), done.\u001b[K\n","remote: Total 5849 (delta 86), reused 75 (delta 64), pack-reused 5677 (from 1)\u001b[K\n","Receiving objects: 100% (5849/5849), 88.85 MiB | 23.97 MiB/s, done.\n","Resolving deltas: 100% (2858/2858), done.\n","Updating files: 100% (280/280), done.\n","/content/LLMRouter\n"]}]},{"cell_type":"code","source":["import os\n","os.environ['OPENAI_API_KEY'] = 'your-key'\n","os.environ['ANTHROPIC_API_KEY'] = 'your-key'\n","# Or for multiple keys:\n","os.environ['API_KEYS'] = '[\"key1\", \"key2\"]'"],"metadata":{"id":"XjggDdKJxaUw"},"execution_count":null,"outputs":[]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"S-Kqc9rYuK1R","executionInfo":{"status":"ok","timestamp":1767593991226,"user_tz":-480,"elapsed":18088,"user":{"displayName":"dai","userId":"16597391218515739453"}},"outputId":"08950ca2-b17e-45f3-bafb-0e73b840d01f"},"outputs":[{"output_type":"stream","name":"stderr","text":["WARNING:torchao.kernel.intmm:Warning: Detected no triton, on systems without Triton certain kernels will not work\n"]},{"output_type":"stream","name":"stdout","text":["Environment setup complete!\n"]}],"source":["from llmrouter.models.mfrouter import MFRouter, MFRouterTrainer\n","from llmrouter.utils import setup_environment\n","\n","setup_environment()\n","print(\"Environment setup complete!\")"]},{"cell_type":"markdown","metadata":{"id":"Zz2SZU6kuK1R"},"source":["## 2. Configuration\n","\n","MFRouter uses the following configuration parameters:\n","\n","| Parameter | Description | Default |\n","|-----------|-------------|--------|\n","| `latent_dim` | Dimension of latent space | 128 |\n","| `text_dim` | Query embedding dimension | 768 |\n","| `lr` | Learning rate | 0.001 |\n","| `epochs` | Training epochs | 5 |\n","| `noise_alpha` | Noise for regularization | 0.0 |"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"pX9OovSauK1S","executionInfo":{"status":"ok","timestamp":1767593994187,"user_tz":-480,"elapsed":14,"user":{"displayName":"dai","userId":"16597391218515739453"}},"outputId":"b3188e1d-a4c2-4b8a-a5cf-c3a8127a2cd7"},"outputs":[{"output_type":"stream","name":"stdout","text":["Current Configuration:\n","==================================================\n","data_path:\n"," llm_data: data/example_data/llm_candidates/default_llm.json\n"," llm_embedding_data: data/example_data/llm_candidates/default_llm_embeddings.json\n"," query_data_test: data/example_data/query_data/default_query_test.jsonl\n"," query_data_train: data/example_data/query_data/default_query_train.jsonl\n"," query_embedding_data: data/example_data/routing_data/query_embeddings_longformer.pt\n"," routing_data_test: data/example_data/routing_data/default_routing_test_data.jsonl\n"," routing_data_train: data/example_data/routing_data/default_routing_train_data.jsonl\n","hparam:\n"," batch_size: 64\n"," epochs: 5\n"," latent_dim: 128\n"," lr: 0.001\n"," noise_alpha: 0.0\n"," text_dim: 768\n","metric:\n"," weights:\n"," cost: 0\n"," llm_judge: 0\n"," performance: 1\n","model_path:\n"," ini_model_path: ''\n"," save_model_path: saved_models/mfrouter/mfrouter.pkl\n","\n"]}],"source":["import yaml\n","\n","CONFIG_PATH = \"configs/model_config_train/mfrouter.yaml\"\n","\n","with open(CONFIG_PATH, 'r') as f:\n"," config = yaml.safe_load(f)\n","\n","print(\"Current Configuration:\")\n","print(\"=\" * 50)\n","print(yaml.dump(config, default_flow_style=False))"]},{"cell_type":"markdown","metadata":{"id":"kQvWMCFtuK1S"},"source":["## 3. Initialize Router"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"qTIXJ0EUuK1T","executionInfo":{"status":"ok","timestamp":1767594004695,"user_tz":-480,"elapsed":8848,"user":{"displayName":"dai","userId":"16597391218515739453"}},"outputId":"21eedc95-865c-49ef-be61-cb3322b2dd1e"},"outputs":[{"output_type":"stream","name":"stdout","text":["✅ MetaRouter initialized successfully (YAML + data loaded).\n","Router initialized successfully!\n","Number of training samples: 50544\n","Number of LLM candidates: 14\n","LLM candidates: ['qwen2.5-7b-instruct', 'codegemma-7b', 'gemma-2-9b-it', 'llama-3.1-8b-instruct', 'llama3-chatqa-1.5-8b', 'mistral-7b-instruct-v0.3', 'llama-3.3-nemotron-super-49b-v1', 'llama-3.1-nemotron-51b-instruct', 'llama3-chatqa-1.5-70b', 'llama3-70b-instruct', 'mixtral-8x7b-instruct-v0.1', 'mixtral-8x22b-instruct-v0.1', 'palmyra-creative-122b', 'mistral-nemo-12b-instruct']\n"]}],"source":["router = MFRouter(yaml_path=CONFIG_PATH)\n","\n","print(\"Router initialized successfully!\")\n","print(f\"Number of training samples: {len(router.routing_data_train)}\")\n","print(f\"Number of LLM candidates: {len(router.llm_data)}\")\n","print(f\"LLM candidates: {list(router.llm_data.keys())}\")"]},{"cell_type":"markdown","metadata":{"id":"gv84EfAuuK1T"},"source":["## 4. Training"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"UBEZLc3uuK1T","executionInfo":{"status":"ok","timestamp":1767594007153,"user_tz":-480,"elapsed":24,"user":{"displayName":"dai","userId":"16597391218515739453"}},"outputId":"d2af7efd-e37b-41c4-c69d-79d6fe3cd5bf"},"outputs":[{"output_type":"stream","name":"stdout","text":["[MFRouterTrainer] Initialized with precomputed embeddings (fast mode).\n","[MFRouterTrainer] Batch size: 64\n","Trainer initialized!\n","Save path: /content/LLMRouter/llmrouter/saved_models/mfrouter/mfrouter.pkl\n"]}],"source":["trainer = MFRouterTrainer(router=router, device='cpu')\n","\n","print(\"Trainer initialized!\")\n","print(f\"Save path: {trainer.save_model_path}\")"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"3V-jSdsGuK1U","executionInfo":{"status":"ok","timestamp":1767594023980,"user_tz":-480,"elapsed":15461,"user":{"displayName":"dai","userId":"16597391218515739453"}},"outputId":"c84a9072-5070-4a18-c828-0739c490ffd4"},"outputs":[{"output_type":"stream","name":"stdout","text":["Starting training...\n","==================================================\n","[MFRouterTrainer] Training with 44928 samples, 702 batches per epoch\n","[MFRouterTrainer] Epoch 1/5 - Loss=0.380405\n","[MFRouterTrainer] Epoch 2/5 - Loss=0.324342\n","[MFRouterTrainer] Epoch 3/5 - Loss=0.306890\n","[MFRouterTrainer] Epoch 4/5 - Loss=0.292752\n","[MFRouterTrainer] Epoch 5/5 - Loss=0.281824\n","Successfully saved pickle model: /content/LLMRouter/llmrouter/saved_models/mfrouter/mfrouter.pkl\n","[MFRouterTrainer] Model saved to /content/LLMRouter/llmrouter/saved_models/mfrouter/mfrouter.pkl\n","==================================================\n","Training completed!\n"]}],"source":["print(\"Starting training...\")\n","print(\"=\" * 50)\n","\n","trainer.train()\n","\n","print(\"=\" * 50)\n","print(\"Training completed!\")"]},{"cell_type":"markdown","metadata":{"id":"8zA3hZs9uK1U"},"source":["## 5. Model Verification"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"utr5Il8luK1U","executionInfo":{"status":"ok","timestamp":1767594027530,"user_tz":-480,"elapsed":15,"user":{"displayName":"dai","userId":"16597391218515739453"}},"outputId":"99cf15b2-c13e-4f88-ed17-9ff9e55663f1"},"outputs":[{"output_type":"stream","name":"stdout","text":["Successfully loaded pickle model: /content/LLMRouter/llmrouter/saved_models/mfrouter/mfrouter.pkl\n","Model loaded successfully!\n","Model type: OrderedDict\n"]}],"source":["from llmrouter.utils import load_model\n","import torch\n","\n","saved_model = load_model(trainer.save_model_path)\n","\n","print(\"Model loaded successfully!\")\n","print(f\"Model type: {type(saved_model).__name__}\")"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":409,"referenced_widgets":["abd8c69bc179439fa4aa9f1f7af55b7f","4b3c37283eef477a9cd5bb70ecb950d8","c37027db092841158ad0d02be8aab613","287ab521f1c24ffcace30f2999e2da04","cdb9db1d027c437d973c71f2ff6893fa","2f772431e31943ee82531a8ade330cb1","d1833b2f8de842c1be24dcc6995daf58","36581ffaaebf43c4a8b06d69a6583f7f","1430ad66ae1f49febb74b3cd61cedb63","97a4f20285fd471a9f23d2843671f20c","3c26184d80354f478c08f1064a12ca38","56ccf4126288487191241456c8edca56","1457fbd0cf7c450bb4c1469fb72cf495","297528647730480d9136c58c8bc0bec3","d7d60f32fdf441c8a772cb499c92a163","eaaac4f730034a4eb3dc9a2870508e4c","2874d30df81d4e318b2983fd9c880ab1","787b35d476dd465886ca2e6109fe4a3b","df5e1d098dc3409192f25b11e800dec3","20926f914ae74aacb27bd7a5e378ee94","da8163b329c94d9dbc4de98ba0b0a005","89e63fe06b114d65b64eb657511f4055","194e6fdc99f641ceacd68eb58f291318","9322533bde7146d6af32a6e39c6bf8c4","afb5afa199ab40c19797ae75fa6711b1","1b43e22399f14727a853a87b03e096bf","92d9d7397ad24c3bba58c91daa507513","ed786a98f1b64015bf1724c0a39c6f92","56218de7d288452ab2dafc04a439ffd7","1d50ca50bd084d7280595e6ee6e65f9d","a5f1b75dffe9467f9dafe82fa297f7f1","d5bd39277cae4dc2a0142338b490c9db","6313403f37ed4051b79e156101b470a1","baf5337b51954a95bf1c11d5af9d83e0","627cb1761f554278a2c9c9dcfd109da7","6c4445b088654c52a94ad68dd6318544","5f3d234c0cf8492389a4a21eb72e0d43","b0e84bc948db4a6f9e41016adab3f048","1184fe4e9d6d4c1582639515c5665377","c2edb9b8c43d4cd9af763b6a91f1fb4c","6d97101037f34c91a5fc5b8b95b70382","49c72a651ff444de85ca1a2cb627a2b4","f52db08fef2543f19bae1e6c7cdda473","2aa8a39906ab43e49ef90244484d7c21","9a890f4f20ff4653978aeb3740b7ba0a","dc819942fb794847829fe46addecd3bf","803e010c01434d9bb8ef73a72326a0fb","29641e4978ba46e3a6ab2cc434551293","80a1c56f9de146f8b379e5b1bb8daa09","976de2862dcc47dbbc825cdb331bd123","eefb922df9f34effa712d55218139b0e","9b9e6860ac4f4f1a91ce980eb42e9c70","f4de3fb74b954fc9b752fd4cf61c480e","8ec72e4601d6432395c5e4c0d8f922e1","0b1a37f10d6f4d298d29a9230e30e6b8","dce54b81e10d4f2e847defbe6a1bad27","41c125c1caab4c44962814c7d57ef567","c1167753a0df4fcb9ebe7dc049377b81","d4f4e5b9985b494b9f84d7797843360a","8ac6fbd5353b4c16bcb5fae49f22c3f3","05310e512a1f437a9bf45da298f64c5d","5122fe3b9f434d9693f34fa3e26bda8b","954be82e545749cfa18e788cc90d0fa9","b0a0580a114b4d66997d59ec74d867be","ac35d0f7ffdb451db06c18edf1c6129d","ca2d902217e141cba50eb8ccecbc37ec"]},"id":"j9aK0dsSuK1U","executionInfo":{"status":"ok","timestamp":1767594042370,"user_tz":-480,"elapsed":13608,"user":{"displayName":"dai","userId":"16597391218515739453"}},"outputId":"d782b7ff-1ef3-4f38-84f8-22c72a833945"},"outputs":[{"output_type":"stream","name":"stdout","text":["Successfully loaded pickle model: /content/LLMRouter/llmrouter/saved_models/mfrouter/mfrouter.pkl\n"]},{"output_type":"stream","name":"stderr","text":["/usr/local/lib/python3.12/dist-packages/huggingface_hub/utils/_auth.py:94: UserWarning: \n","The secret `HF_TOKEN` does not exist in your Colab secrets.\n","To authenticate with the Hugging Face Hub, create a token in your settings tab (https://huggingface.co/settings/tokens), set it as secret in your Google Colab and restart your session.\n","You will be able to reuse this secret in all of your notebooks.\n","Please note that authentication is recommended but still optional to access public models or datasets.\n"," warnings.warn(\n"]},{"output_type":"display_data","data":{"text/plain":["config.json: 0%| | 0.00/694 [00:00 {result['model_name']}\")\n","else:\n"," print(f\"Query file not found: {QUERY_FILE}\")\n"," print(\"Create a JSONL file with format: {\\\"query\\\": \\\"Your question\\\"}\")"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"Fdi56FgA1E7A","executionInfo":{"status":"ok","timestamp":1767594539615,"user_tz":-480,"elapsed":148065,"user":{"displayName":"dai","userId":"16597391218515739453"}},"outputId":"e326d166-3923-49e0-b7ae-59a5e1c9ba68"},"execution_count":null,"outputs":[{"output_type":"stream","name":"stdout","text":["Loaded 706 queries from: data/example_data/query_data/default_query_test.jsonl\n","Successfully loaded pickle model: /content/LLMRouter/llmrouter/saved_models/mfrouter/mfrouter.pkl\n","Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n","Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n","Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n","Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n","Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n","Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n","Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n","Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n","Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n","Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n","Routed 10 queries\n","Results saved to: outputs/mfrouter_results.jsonl\n","\n","Sample results:\n"," 1. Q: There are 4 houses in a row, numbered... -> qwen2.5-7b-instruct\n"," 2. Q: There are 3 houses in a row, numbered... -> gemma-2-9b-it\n"," 3. Q: There are 3 houses in a row, numbered... -> gemma-2-9b-it\n"]}]},{"cell_type":"markdown","source":["## Summary\n","\n","This notebook demonstrated:\n","1. Loading a trained MFRouter\n","2. Routing queries using matrix factorization\n","\n","MFRouter is effective for:\n","- Capturing query-model interaction patterns\n","- Collaborative filtering-style routing"],"metadata":{"id":"20BUZHYM1FTw"}}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.10.0"},"colab":{"provenance":[]},"widgets":{"application/vnd.jupyter.widget-state+json":{"abd8c69bc179439fa4aa9f1f7af55b7f":{"model_module":"@jupyter-widgets/controls","model_name":"HBoxModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HBoxModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HBoxView","box_style":"","children":["IPY_MODEL_4b3c37283eef477a9cd5bb70ecb950d8","IPY_MODEL_c37027db092841158ad0d02be8aab613","IPY_MODEL_287ab521f1c24ffcace30f2999e2da04"],"layout":"IPY_MODEL_cdb9db1d027c437d973c71f2ff6893fa"}},"4b3c37283eef477a9cd5bb70ecb950d8":{"model_module":"@jupyter-widgets/controls","model_name":"HTMLModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_2f772431e31943ee82531a8ade330cb1","placeholder":"​","style":"IPY_MODEL_d1833b2f8de842c1be24dcc6995daf58","value":"config.json: 100%"}},"c37027db092841158ad0d02be8aab613":{"model_module":"@jupyter-widgets/controls","model_name":"FloatProgressModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"FloatProgressModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"ProgressView","bar_style":"success","description":"","description_tooltip":null,"layout":"IPY_MODEL_36581ffaaebf43c4a8b06d69a6583f7f","max":694,"min":0,"orientation":"horizontal","style":"IPY_MODEL_1430ad66ae1f49febb74b3cd61cedb63","value":694}},"287ab521f1c24ffcace30f2999e2da04":{"model_module":"@jupyter-widgets/controls","model_name":"HTMLModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_97a4f20285fd471a9f23d2843671f20c","placeholder":"​","style":"IPY_MODEL_3c26184d80354f478c08f1064a12ca38","value":" 694/694 [00:00<00:00, 62.6kB/s]"}},"cdb9db1d027c437d973c71f2ff6893fa":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"2f772431e31943ee82531a8ade330cb1":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"d1833b2f8de842c1be24dcc6995daf58":{"model_module":"@jupyter-widgets/controls","model_name":"DescriptionStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}},"36581ffaaebf43c4a8b06d69a6583f7f":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"1430ad66ae1f49febb74b3cd61cedb63":{"model_module":"@jupyter-widgets/controls","model_name":"ProgressStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"ProgressStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","bar_color":null,"description_width":""}},"97a4f20285fd471a9f23d2843671f20c":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"3c26184d80354f478c08f1064a12ca38":{"model_module":"@jupyter-widgets/controls","model_name":"DescriptionStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}},"56ccf4126288487191241456c8edca56":{"model_module":"@jupyter-widgets/controls","model_name":"HBoxModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HBoxModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HBoxView","box_style":"","children":["IPY_MODEL_1457fbd0cf7c450bb4c1469fb72cf495","IPY_MODEL_297528647730480d9136c58c8bc0bec3","IPY_MODEL_d7d60f32fdf441c8a772cb499c92a163"],"layout":"IPY_MODEL_eaaac4f730034a4eb3dc9a2870508e4c"}},"1457fbd0cf7c450bb4c1469fb72cf495":{"model_module":"@jupyter-widgets/controls","model_name":"HTMLModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_2874d30df81d4e318b2983fd9c880ab1","placeholder":"​","style":"IPY_MODEL_787b35d476dd465886ca2e6109fe4a3b","value":"vocab.json: "}},"297528647730480d9136c58c8bc0bec3":{"model_module":"@jupyter-widgets/controls","model_name":"FloatProgressModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"FloatProgressModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"ProgressView","bar_style":"success","description":"","description_tooltip":null,"layout":"IPY_MODEL_df5e1d098dc3409192f25b11e800dec3","max":1,"min":0,"orientation":"horizontal","style":"IPY_MODEL_20926f914ae74aacb27bd7a5e378ee94","value":1}},"d7d60f32fdf441c8a772cb499c92a163":{"model_module":"@jupyter-widgets/controls","model_name":"HTMLModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_da8163b329c94d9dbc4de98ba0b0a005","placeholder":"​","style":"IPY_MODEL_89e63fe06b114d65b64eb657511f4055","value":" 899k/? [00:00<00:00, 13.9MB/s]"}},"eaaac4f730034a4eb3dc9a2870508e4c":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"2874d30df81d4e318b2983fd9c880ab1":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"787b35d476dd465886ca2e6109fe4a3b":{"model_module":"@jupyter-widgets/controls","model_name":"DescriptionStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}},"df5e1d098dc3409192f25b11e800dec3":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":"20px"}},"20926f914ae74aacb27bd7a5e378ee94":{"model_module":"@jupyter-widgets/controls","model_name":"ProgressStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"ProgressStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","bar_color":null,"description_width":""}},"da8163b329c94d9dbc4de98ba0b0a005":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"89e63fe06b114d65b64eb657511f4055":{"model_module":"@jupyter-widgets/controls","model_name":"DescriptionStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}},"194e6fdc99f641ceacd68eb58f291318":{"model_module":"@jupyter-widgets/controls","model_name":"HBoxModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HBoxModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HBoxView","box_style":"","children":["IPY_MODEL_9322533bde7146d6af32a6e39c6bf8c4","IPY_MODEL_afb5afa199ab40c19797ae75fa6711b1","IPY_MODEL_1b43e22399f14727a853a87b03e096bf"],"layout":"IPY_MODEL_92d9d7397ad24c3bba58c91daa507513"}},"9322533bde7146d6af32a6e39c6bf8c4":{"model_module":"@jupyter-widgets/controls","model_name":"HTMLModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_ed786a98f1b64015bf1724c0a39c6f92","placeholder":"​","style":"IPY_MODEL_56218de7d288452ab2dafc04a439ffd7","value":"merges.txt: "}},"afb5afa199ab40c19797ae75fa6711b1":{"model_module":"@jupyter-widgets/controls","model_name":"FloatProgressModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"FloatProgressModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"ProgressView","bar_style":"success","description":"","description_tooltip":null,"layout":"IPY_MODEL_1d50ca50bd084d7280595e6ee6e65f9d","max":1,"min":0,"orientation":"horizontal","style":"IPY_MODEL_a5f1b75dffe9467f9dafe82fa297f7f1","value":1}},"1b43e22399f14727a853a87b03e096bf":{"model_module":"@jupyter-widgets/controls","model_name":"HTMLModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_d5bd39277cae4dc2a0142338b490c9db","placeholder":"​","style":"IPY_MODEL_6313403f37ed4051b79e156101b470a1","value":" 456k/? [00:00<00:00, 21.1MB/s]"}},"92d9d7397ad24c3bba58c91daa507513":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"ed786a98f1b64015bf1724c0a39c6f92":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"56218de7d288452ab2dafc04a439ffd7":{"model_module":"@jupyter-widgets/controls","model_name":"DescriptionStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}},"1d50ca50bd084d7280595e6ee6e65f9d":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":"20px"}},"a5f1b75dffe9467f9dafe82fa297f7f1":{"model_module":"@jupyter-widgets/controls","model_name":"ProgressStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"ProgressStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","bar_color":null,"description_width":""}},"d5bd39277cae4dc2a0142338b490c9db":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"6313403f37ed4051b79e156101b470a1":{"model_module":"@jupyter-widgets/controls","model_name":"DescriptionStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}},"baf5337b51954a95bf1c11d5af9d83e0":{"model_module":"@jupyter-widgets/controls","model_name":"HBoxModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HBoxModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HBoxView","box_style":"","children":["IPY_MODEL_627cb1761f554278a2c9c9dcfd109da7","IPY_MODEL_6c4445b088654c52a94ad68dd6318544","IPY_MODEL_5f3d234c0cf8492389a4a21eb72e0d43"],"layout":"IPY_MODEL_b0e84bc948db4a6f9e41016adab3f048"}},"627cb1761f554278a2c9c9dcfd109da7":{"model_module":"@jupyter-widgets/controls","model_name":"HTMLModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_1184fe4e9d6d4c1582639515c5665377","placeholder":"​","style":"IPY_MODEL_c2edb9b8c43d4cd9af763b6a91f1fb4c","value":"tokenizer.json: "}},"6c4445b088654c52a94ad68dd6318544":{"model_module":"@jupyter-widgets/controls","model_name":"FloatProgressModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"FloatProgressModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"ProgressView","bar_style":"success","description":"","description_tooltip":null,"layout":"IPY_MODEL_6d97101037f34c91a5fc5b8b95b70382","max":1,"min":0,"orientation":"horizontal","style":"IPY_MODEL_49c72a651ff444de85ca1a2cb627a2b4","value":1}},"5f3d234c0cf8492389a4a21eb72e0d43":{"model_module":"@jupyter-widgets/controls","model_name":"HTMLModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_f52db08fef2543f19bae1e6c7cdda473","placeholder":"​","style":"IPY_MODEL_2aa8a39906ab43e49ef90244484d7c21","value":" 1.36M/? [00:00<00:00, 33.8MB/s]"}},"b0e84bc948db4a6f9e41016adab3f048":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"1184fe4e9d6d4c1582639515c5665377":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"c2edb9b8c43d4cd9af763b6a91f1fb4c":{"model_module":"@jupyter-widgets/controls","model_name":"DescriptionStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}},"6d97101037f34c91a5fc5b8b95b70382":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":"20px"}},"49c72a651ff444de85ca1a2cb627a2b4":{"model_module":"@jupyter-widgets/controls","model_name":"ProgressStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"ProgressStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","bar_color":null,"description_width":""}},"f52db08fef2543f19bae1e6c7cdda473":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"2aa8a39906ab43e49ef90244484d7c21":{"model_module":"@jupyter-widgets/controls","model_name":"DescriptionStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}},"9a890f4f20ff4653978aeb3740b7ba0a":{"model_module":"@jupyter-widgets/controls","model_name":"HBoxModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HBoxModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HBoxView","box_style":"","children":["IPY_MODEL_dc819942fb794847829fe46addecd3bf","IPY_MODEL_803e010c01434d9bb8ef73a72326a0fb","IPY_MODEL_29641e4978ba46e3a6ab2cc434551293"],"layout":"IPY_MODEL_80a1c56f9de146f8b379e5b1bb8daa09"}},"dc819942fb794847829fe46addecd3bf":{"model_module":"@jupyter-widgets/controls","model_name":"HTMLModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_976de2862dcc47dbbc825cdb331bd123","placeholder":"​","style":"IPY_MODEL_eefb922df9f34effa712d55218139b0e","value":"pytorch_model.bin: 100%"}},"803e010c01434d9bb8ef73a72326a0fb":{"model_module":"@jupyter-widgets/controls","model_name":"FloatProgressModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"FloatProgressModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"ProgressView","bar_style":"success","description":"","description_tooltip":null,"layout":"IPY_MODEL_9b9e6860ac4f4f1a91ce980eb42e9c70","max":597257159,"min":0,"orientation":"horizontal","style":"IPY_MODEL_f4de3fb74b954fc9b752fd4cf61c480e","value":597257159}},"29641e4978ba46e3a6ab2cc434551293":{"model_module":"@jupyter-widgets/controls","model_name":"HTMLModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_8ec72e4601d6432395c5e4c0d8f922e1","placeholder":"​","style":"IPY_MODEL_0b1a37f10d6f4d298d29a9230e30e6b8","value":" 597M/597M [00:04<00:00, 171MB/s]"}},"80a1c56f9de146f8b379e5b1bb8daa09":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"976de2862dcc47dbbc825cdb331bd123":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"eefb922df9f34effa712d55218139b0e":{"model_module":"@jupyter-widgets/controls","model_name":"DescriptionStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}},"9b9e6860ac4f4f1a91ce980eb42e9c70":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"f4de3fb74b954fc9b752fd4cf61c480e":{"model_module":"@jupyter-widgets/controls","model_name":"ProgressStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"ProgressStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","bar_color":null,"description_width":""}},"8ec72e4601d6432395c5e4c0d8f922e1":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"0b1a37f10d6f4d298d29a9230e30e6b8":{"model_module":"@jupyter-widgets/controls","model_name":"DescriptionStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}},"dce54b81e10d4f2e847defbe6a1bad27":{"model_module":"@jupyter-widgets/controls","model_name":"HBoxModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HBoxModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HBoxView","box_style":"","children":["IPY_MODEL_41c125c1caab4c44962814c7d57ef567","IPY_MODEL_c1167753a0df4fcb9ebe7dc049377b81","IPY_MODEL_d4f4e5b9985b494b9f84d7797843360a"],"layout":"IPY_MODEL_8ac6fbd5353b4c16bcb5fae49f22c3f3"}},"41c125c1caab4c44962814c7d57ef567":{"model_module":"@jupyter-widgets/controls","model_name":"HTMLModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_05310e512a1f437a9bf45da298f64c5d","placeholder":"​","style":"IPY_MODEL_5122fe3b9f434d9693f34fa3e26bda8b","value":"model.safetensors: 100%"}},"c1167753a0df4fcb9ebe7dc049377b81":{"model_module":"@jupyter-widgets/controls","model_name":"FloatProgressModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"FloatProgressModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"ProgressView","bar_style":"success","description":"","description_tooltip":null,"layout":"IPY_MODEL_954be82e545749cfa18e788cc90d0fa9","max":597241956,"min":0,"orientation":"horizontal","style":"IPY_MODEL_b0a0580a114b4d66997d59ec74d867be","value":597241956}},"d4f4e5b9985b494b9f84d7797843360a":{"model_module":"@jupyter-widgets/controls","model_name":"HTMLModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_ac35d0f7ffdb451db06c18edf1c6129d","placeholder":"​","style":"IPY_MODEL_ca2d902217e141cba50eb8ccecbc37ec","value":" 597M/597M [00:04<00:00, 233MB/s]"}},"8ac6fbd5353b4c16bcb5fae49f22c3f3":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"05310e512a1f437a9bf45da298f64c5d":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"5122fe3b9f434d9693f34fa3e26bda8b":{"model_module":"@jupyter-widgets/controls","model_name":"DescriptionStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}},"954be82e545749cfa18e788cc90d0fa9":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"b0a0580a114b4d66997d59ec74d867be":{"model_module":"@jupyter-widgets/controls","model_name":"ProgressStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"ProgressStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","bar_color":null,"description_width":""}},"ac35d0f7ffdb451db06c18edf1c6129d":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"ca2d902217e141cba50eb8ccecbc37ec":{"model_module":"@jupyter-widgets/controls","model_name":"DescriptionStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}}}}},"nbformat":4,"nbformat_minor":0} \ No newline at end of file diff --git a/colab_notebooks/mlprouter/01_mlprouter_training_and_inference.ipynb b/colab_notebooks/mlprouter/01_mlprouter_training_and_inference.ipynb new file mode 100644 index 0000000..11bfb00 --- /dev/null +++ b/colab_notebooks/mlprouter/01_mlprouter_training_and_inference.ipynb @@ -0,0 +1,1162 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# MLPRouter - Training\n", + "\n", + "This notebook demonstrates how to train the **MLPRouter** (Multi-Layer Perceptron Router).\n", + "\n", + "## Overview\n", + "\n", + "MLPRouter uses a neural network classifier with multiple hidden layers to route queries.\n", + "\n", + "**Key Features**:\n", + "- Can learn complex non-linear decision boundaries\n", + "- Flexible architecture with configurable layers\n", + "- Good for large-scale routing problems" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Environment Setup" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "fatal: destination path 'LLMRouter' already exists and is not an empty directory.\n", + "/home/zhongjie/LLMRouter\n", + "Obtaining file:///home/zhongjie/LLMRouter\n", + " Installing build dependencies ... \u001b[?25ldone\n", + "\u001b[?25h Checking if build backend supports build_editable ... \u001b[?25ldone\n", + "\u001b[?25h Getting requirements to build editable ... \u001b[?25ldone\n", + "\u001b[?25h Preparing editable metadata (pyproject.toml) ... \u001b[?25ldone\n", + "\u001b[?25hRequirement already satisfied: torch>=2.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.9.1)\n", + "Collecting transformers>=4.40 (from llmrouter-lib==0.2.0)\n", + " Using cached transformers-4.57.6-py3-none-any.whl.metadata (43 kB)\n", + "Collecting sentencepiece>=0.1.99 (from llmrouter-lib==0.2.0)\n", + " Using cached sentencepiece-0.2.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.metadata (10 kB)\n", + "Requirement already satisfied: numpy>=1.21 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.3.5)\n", + "Requirement already satisfied: pandas>=1.5 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.3.3)\n", + "Requirement already satisfied: scikit-learn>=1.2 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (1.8.0)\n", + "Requirement already satisfied: pyyaml>=6.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (6.0.3)\n", + "Requirement already satisfied: tqdm>=4.65 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (4.67.1)\n", + "Collecting datasets>=2.14 (from llmrouter-lib==0.2.0)\n", + " Using cached datasets-4.5.0-py3-none-any.whl.metadata (19 kB)\n", + "Requirement already satisfied: pydantic>=2.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.12.5)\n", + "Collecting gradio>=4.0 (from llmrouter-lib==0.2.0)\n", + " Using cached gradio-6.3.0-py3-none-any.whl.metadata (16 kB)\n", + "Collecting litellm>=1.0 (from llmrouter-lib==0.2.0)\n", + " Using cached litellm-1.81.0-py3-none-any.whl.metadata (29 kB)\n", + "Collecting peft>=0.7 (from llmrouter-lib==0.2.0)\n", + " Using cached peft-0.18.1-py3-none-any.whl.metadata (14 kB)\n", + "Collecting torch-geometric>=2.3 (from llmrouter-lib==0.2.0)\n", + " Using cached torch_geometric-2.7.0-py3-none-any.whl.metadata (63 kB)\n", + "Requirement already satisfied: scipy>=1.10 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (1.16.3)\n", + "Requirement already satisfied: protobuf>=3.20 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (6.31.1)\n", + "Requirement already satisfied: filelock in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (3.20.2)\n", + "Requirement already satisfied: pyarrow>=21.0.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (22.0.0)\n", + "Requirement already satisfied: dill<0.4.1,>=0.3.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (0.4.0)\n", + "Requirement already satisfied: requests>=2.32.2 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (2.32.5)\n", + "Requirement already satisfied: httpx<1.0.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (0.28.1)\n", + "Collecting xxhash (from datasets>=2.14->llmrouter-lib==0.2.0)\n", + " Using cached xxhash-3.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (13 kB)\n", + "Collecting multiprocess<0.70.19 (from datasets>=2.14->llmrouter-lib==0.2.0)\n", + " Using cached multiprocess-0.70.18-py313-none-any.whl.metadata (7.2 kB)\n", + "Collecting fsspec<=2025.10.0,>=2023.1.0 (from fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0)\n", + " Using cached fsspec-2025.10.0-py3-none-any.whl.metadata (10 kB)\n", + "Collecting huggingface-hub<2.0,>=0.25.0 (from datasets>=2.14->llmrouter-lib==0.2.0)\n", + " Using cached huggingface_hub-1.3.2-py3-none-any.whl.metadata (13 kB)\n", + "Requirement already satisfied: packaging in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (25.0)\n", + "Collecting aiohttp!=4.0.0a0,!=4.0.0a1 (from fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0)\n", + " Using cached aiohttp-3.13.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (8.1 kB)\n", + "Requirement already satisfied: anyio in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (4.12.0)\n", + "Requirement already satisfied: certifi in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (2026.1.4)\n", + "Requirement already satisfied: httpcore==1.* in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.0.9)\n", + "Requirement already satisfied: idna in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (3.11)\n", + "Requirement already satisfied: h11>=0.16 in /opt/conda/lib/python3.13/site-packages (from httpcore==1.*->httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (0.16.0)\n", + "Collecting hf-xet<2.0.0,>=1.2.0 (from huggingface-hub<2.0,>=0.25.0->datasets>=2.14->llmrouter-lib==0.2.0)\n", + " Using cached hf_xet-1.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (4.9 kB)\n", + "Collecting shellingham (from huggingface-hub<2.0,>=0.25.0->datasets>=2.14->llmrouter-lib==0.2.0)\n", + " Using cached shellingham-1.5.4-py2.py3-none-any.whl.metadata (3.5 kB)\n", + "Collecting typer-slim (from huggingface-hub<2.0,>=0.25.0->datasets>=2.14->llmrouter-lib==0.2.0)\n", + " Using cached typer_slim-0.21.1-py3-none-any.whl.metadata (16 kB)\n", + "Requirement already satisfied: typing-extensions>=4.1.0 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<2.0,>=0.25.0->datasets>=2.14->llmrouter-lib==0.2.0) (4.15.0)\n", + "Collecting aiohappyeyeballs>=2.5.0 (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0)\n", + " Using cached aiohappyeyeballs-2.6.1-py3-none-any.whl.metadata (5.9 kB)\n", + "Collecting aiosignal>=1.4.0 (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0)\n", + " Using cached aiosignal-1.4.0-py3-none-any.whl.metadata (3.7 kB)\n", + "Requirement already satisfied: attrs>=17.3.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (25.4.0)\n", + "Collecting frozenlist>=1.1.1 (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0)\n", + " Using cached frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (20 kB)\n", + "Collecting multidict<7.0,>=4.5 (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0)\n", + " Using cached multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (5.3 kB)\n", + "Collecting propcache>=0.2.0 (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0)\n", + " Using cached propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (13 kB)\n", + "Collecting yarl<2.0,>=1.17.0 (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0)\n", + " Using cached yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (75 kB)\n", + "Collecting aiofiles<25.0,>=22.0 (from gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached aiofiles-24.1.0-py3-none-any.whl.metadata (10 kB)\n", + "Collecting audioop-lts<1.0 (from gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached audioop_lts-0.2.2-cp313-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (2.0 kB)\n", + "Requirement already satisfied: brotli>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (1.2.0)\n", + "Collecting fastapi<1.0,>=0.115.2 (from gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached fastapi-0.128.0-py3-none-any.whl.metadata (30 kB)\n", + "Collecting ffmpy (from gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached ffmpy-1.0.0-py3-none-any.whl.metadata (3.0 kB)\n", + "Collecting gradio-client==2.0.3 (from gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached gradio_client-2.0.3-py3-none-any.whl.metadata (7.1 kB)\n", + "Collecting groovy~=0.1 (from gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached groovy-0.1.2-py3-none-any.whl.metadata (6.1 kB)\n", + "Requirement already satisfied: jinja2<4.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (3.1.6)\n", + "Requirement already satisfied: markupsafe<4.0,>=2.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (3.0.3)\n", + "Collecting orjson~=3.0 (from gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached orjson-3.11.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (41 kB)\n", + "Requirement already satisfied: pillow<13.0,>=8.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (12.1.0)\n", + "Collecting pydub (from gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached pydub-0.25.1-py2.py3-none-any.whl.metadata (1.4 kB)\n", + "Collecting python-multipart>=0.0.18 (from gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached python_multipart-0.0.21-py3-none-any.whl.metadata (1.8 kB)\n", + "Collecting safehttpx<0.2.0,>=0.1.7 (from gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached safehttpx-0.1.7-py3-none-any.whl.metadata (4.2 kB)\n", + "Collecting semantic-version~=2.0 (from gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached semantic_version-2.10.0-py2.py3-none-any.whl.metadata (9.7 kB)\n", + "Collecting starlette<1.0,>=0.40.0 (from gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached starlette-0.52.1-py3-none-any.whl.metadata (6.3 kB)\n", + "Collecting tomlkit<0.14.0,>=0.12.0 (from gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached tomlkit-0.13.3-py3-none-any.whl.metadata (2.8 kB)\n", + "Collecting typer<1.0,>=0.12 (from gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached typer-0.21.1-py3-none-any.whl.metadata (16 kB)\n", + "Collecting uvicorn>=0.14.0 (from gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached uvicorn-0.40.0-py3-none-any.whl.metadata (6.7 kB)\n", + "Collecting starlette<1.0,>=0.40.0 (from gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached starlette-0.50.0-py3-none-any.whl.metadata (6.3 kB)\n", + "Collecting annotated-doc>=0.0.2 (from fastapi<1.0,>=0.115.2->gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached annotated_doc-0.0.4-py3-none-any.whl.metadata (6.6 kB)\n", + "Requirement already satisfied: python-dateutil>=2.8.2 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.2.0) (2.9.0.post0)\n", + "Requirement already satisfied: pytz>=2020.1 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.2.0) (2025.2)\n", + "Requirement already satisfied: tzdata>=2022.7 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.2.0) (2025.3)\n", + "Requirement already satisfied: annotated-types>=0.6.0 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.2.0) (0.7.0)\n", + "Requirement already satisfied: pydantic-core==2.41.5 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.2.0) (2.41.5)\n", + "Requirement already satisfied: typing-inspection>=0.4.2 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.2.0) (0.4.2)\n", + "Requirement already satisfied: click>=8.0.0 in /opt/conda/lib/python3.13/site-packages (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (8.3.1)\n", + "Collecting rich>=10.11.0 (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached rich-14.2.0-py3-none-any.whl.metadata (18 kB)\n", + "Collecting fastuuid>=0.13.0 (from litellm>=1.0->llmrouter-lib==0.2.0)\n", + " Using cached fastuuid-0.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.1 kB)\n", + "Collecting grpcio!=1.68.*,!=1.69.*,!=1.70.*,!=1.71.0,!=1.71.1,!=1.72.0,!=1.72.1,!=1.73.0,>=1.62.3 (from litellm>=1.0->llmrouter-lib==0.2.0)\n", + " Using cached grpcio-1.76.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (3.7 kB)\n", + "Requirement already satisfied: importlib-metadata>=6.8.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (8.7.0)\n", + "Requirement already satisfied: jsonschema<5.0.0,>=4.23.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (4.25.1)\n", + "Collecting openai>=2.8.0 (from litellm>=1.0->llmrouter-lib==0.2.0)\n", + " Using cached openai-2.15.0-py3-none-any.whl.metadata (29 kB)\n", + "Collecting python-dotenv>=0.2.0 (from litellm>=1.0->llmrouter-lib==0.2.0)\n", + " Using cached python_dotenv-1.2.1-py3-none-any.whl.metadata (25 kB)\n", + "Collecting tiktoken>=0.7.0 (from litellm>=1.0->llmrouter-lib==0.2.0)\n", + " Using cached tiktoken-0.12.0-cp313-cp313-manylinux_2_28_x86_64.whl.metadata (6.7 kB)\n", + "Collecting tokenizers (from litellm>=1.0->llmrouter-lib==0.2.0)\n", + " Using cached tokenizers-0.22.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (7.3 kB)\n", + "Requirement already satisfied: jsonschema-specifications>=2023.03.6 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.2.0) (2025.9.1)\n", + "Requirement already satisfied: referencing>=0.28.4 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.2.0) (0.37.0)\n", + "Requirement already satisfied: rpds-py>=0.7.1 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.2.0) (0.30.0)\n", + "Requirement already satisfied: zipp>=3.20 in /opt/conda/lib/python3.13/site-packages (from importlib-metadata>=6.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (3.23.0)\n", + "Requirement already satisfied: distro<2,>=1.7.0 in /opt/conda/lib/python3.13/site-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (1.9.0)\n", + "Collecting jiter<1,>=0.10.0 (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.2.0)\n", + " Using cached jiter-0.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (5.2 kB)\n", + "Requirement already satisfied: sniffio in /opt/conda/lib/python3.13/site-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (1.3.1)\n", + "Requirement already satisfied: psutil in /opt/conda/lib/python3.13/site-packages (from peft>=0.7->llmrouter-lib==0.2.0) (7.2.1)\n", + "Collecting accelerate>=0.21.0 (from peft>=0.7->llmrouter-lib==0.2.0)\n", + " Using cached accelerate-1.12.0-py3-none-any.whl.metadata (19 kB)\n", + "Collecting safetensors (from peft>=0.7->llmrouter-lib==0.2.0)\n", + " Using cached safetensors-0.7.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (4.1 kB)\n", + "Requirement already satisfied: six>=1.5 in /opt/conda/lib/python3.13/site-packages (from python-dateutil>=2.8.2->pandas>=1.5->llmrouter-lib==0.2.0) (1.17.0)\n", + "Requirement already satisfied: charset_normalizer<4,>=2 in /opt/conda/lib/python3.13/site-packages (from requests>=2.32.2->datasets>=2.14->llmrouter-lib==0.2.0) (3.4.4)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/conda/lib/python3.13/site-packages (from requests>=2.32.2->datasets>=2.14->llmrouter-lib==0.2.0) (2.6.2)\n", + "Collecting markdown-it-py>=2.2.0 (from rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached markdown_it_py-4.0.0-py3-none-any.whl.metadata (7.3 kB)\n", + "Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /opt/conda/lib/python3.13/site-packages (from rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (2.19.2)\n", + "Collecting mdurl~=0.1 (from markdown-it-py>=2.2.0->rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0)\n", + " Using cached mdurl-0.1.2-py3-none-any.whl.metadata (1.6 kB)\n", + "Requirement already satisfied: joblib>=1.3.0 in /opt/conda/lib/python3.13/site-packages (from scikit-learn>=1.2->llmrouter-lib==0.2.0) (1.5.3)\n", + "Requirement already satisfied: threadpoolctl>=3.2.0 in /opt/conda/lib/python3.13/site-packages (from scikit-learn>=1.2->llmrouter-lib==0.2.0) (3.6.0)\n", + "Collecting regex>=2022.1.18 (from tiktoken>=0.7.0->litellm>=1.0->llmrouter-lib==0.2.0)\n", + " Using cached regex-2026.1.15-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (40 kB)\n", + "Requirement already satisfied: setuptools in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (80.9.0)\n", + "Requirement already satisfied: sympy>=1.13.3 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (1.14.0)\n", + "Requirement already satisfied: networkx>=2.5.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (3.6.1)\n", + "Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.93)\n", + "Requirement already satisfied: nvidia-cuda-runtime-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.90)\n", + "Requirement already satisfied: nvidia-cuda-cupti-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.90)\n", + "Requirement already satisfied: nvidia-cudnn-cu12==9.10.2.21 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (9.10.2.21)\n", + "Requirement already satisfied: nvidia-cublas-cu12==12.8.4.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.4.1)\n", + "Requirement already satisfied: nvidia-cufft-cu12==11.3.3.83 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (11.3.3.83)\n", + "Requirement already satisfied: nvidia-curand-cu12==10.3.9.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (10.3.9.90)\n", + "Requirement already satisfied: nvidia-cusolver-cu12==11.7.3.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (11.7.3.90)\n", + "Requirement already satisfied: nvidia-cusparse-cu12==12.5.8.93 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.5.8.93)\n", + "Requirement already satisfied: nvidia-cusparselt-cu12==0.7.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (0.7.1)\n", + "Requirement already satisfied: nvidia-nccl-cu12==2.27.5 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (2.27.5)\n", + "Requirement already satisfied: nvidia-nvshmem-cu12==3.3.20 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (3.3.20)\n", + "Requirement already satisfied: nvidia-nvtx-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.90)\n", + "Requirement already satisfied: nvidia-nvjitlink-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.93)\n", + "Requirement already satisfied: nvidia-cufile-cu12==1.13.1.3 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (1.13.1.3)\n", + "Requirement already satisfied: triton==3.5.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (3.5.1)\n", + "Requirement already satisfied: mpmath<1.4,>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from sympy>=1.13.3->torch>=2.0->llmrouter-lib==0.2.0) (1.3.0)\n", + "Requirement already satisfied: pyparsing in /opt/conda/lib/python3.13/site-packages (from torch-geometric>=2.3->llmrouter-lib==0.2.0) (3.3.1)\n", + "Collecting huggingface-hub<2.0,>=0.25.0 (from datasets>=2.14->llmrouter-lib==0.2.0)\n", + " Using cached huggingface_hub-0.36.0-py3-none-any.whl.metadata (14 kB)\n", + "Using cached datasets-4.5.0-py3-none-any.whl (515 kB)\n", + "Using cached fsspec-2025.10.0-py3-none-any.whl (200 kB)\n", + "Using cached hf_xet-1.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.3 MB)\n", + "Using cached multiprocess-0.70.18-py313-none-any.whl (151 kB)\n", + "Using cached aiohttp-3.13.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (1.7 MB)\n", + "Using cached multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (254 kB)\n", + "Using cached yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (377 kB)\n", + "Using cached aiohappyeyeballs-2.6.1-py3-none-any.whl (15 kB)\n", + "Using cached aiosignal-1.4.0-py3-none-any.whl (7.5 kB)\n", + "Using cached frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (234 kB)\n", + "Using cached gradio-6.3.0-py3-none-any.whl (23.0 MB)\n", + "Using cached gradio_client-2.0.3-py3-none-any.whl (55 kB)\n", + "Using cached aiofiles-24.1.0-py3-none-any.whl (15 kB)\n", + "Using cached audioop_lts-0.2.2-cp313-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (85 kB)\n", + "Using cached fastapi-0.128.0-py3-none-any.whl (103 kB)\n", + "Using cached groovy-0.1.2-py3-none-any.whl (14 kB)\n", + "Using cached orjson-3.11.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (138 kB)\n", + "Using cached safehttpx-0.1.7-py3-none-any.whl (9.0 kB)\n", + "Using cached semantic_version-2.10.0-py2.py3-none-any.whl (15 kB)\n", + "Using cached starlette-0.50.0-py3-none-any.whl (74 kB)\n", + "Using cached tomlkit-0.13.3-py3-none-any.whl (38 kB)\n", + "Using cached typer-0.21.1-py3-none-any.whl (47 kB)\n", + "Using cached annotated_doc-0.0.4-py3-none-any.whl (5.3 kB)\n", + "Using cached litellm-1.81.0-py3-none-any.whl (11.8 MB)\n", + "Using cached fastuuid-0.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (278 kB)\n", + "Using cached grpcio-1.76.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (6.6 MB)\n", + "Using cached openai-2.15.0-py3-none-any.whl (1.1 MB)\n", + "Using cached jiter-0.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (361 kB)\n", + "Using cached peft-0.18.1-py3-none-any.whl (556 kB)\n", + "Using cached accelerate-1.12.0-py3-none-any.whl (380 kB)\n", + "Using cached propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (204 kB)\n", + "Using cached python_dotenv-1.2.1-py3-none-any.whl (21 kB)\n", + "Using cached python_multipart-0.0.21-py3-none-any.whl (24 kB)\n", + "Using cached rich-14.2.0-py3-none-any.whl (243 kB)\n", + "Using cached markdown_it_py-4.0.0-py3-none-any.whl (87 kB)\n", + "Using cached mdurl-0.1.2-py3-none-any.whl (10.0 kB)\n", + "Using cached safetensors-0.7.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (507 kB)\n", + "Using cached sentencepiece-0.2.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (1.4 MB)\n", + "Using cached shellingham-1.5.4-py2.py3-none-any.whl (9.8 kB)\n", + "Using cached tiktoken-0.12.0-cp313-cp313-manylinux_2_28_x86_64.whl (1.2 MB)\n", + "Using cached regex-2026.1.15-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (803 kB)\n", + "Using cached torch_geometric-2.7.0-py3-none-any.whl (1.3 MB)\n", + "Using cached transformers-4.57.6-py3-none-any.whl (12.0 MB)\n", + "Using cached huggingface_hub-0.36.0-py3-none-any.whl (566 kB)\n", + "Using cached tokenizers-0.22.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.3 MB)\n", + "Using cached uvicorn-0.40.0-py3-none-any.whl (68 kB)\n", + "Using cached ffmpy-1.0.0-py3-none-any.whl (5.6 kB)\n", + "Using cached pydub-0.25.1-py2.py3-none-any.whl (32 kB)\n", + "Using cached xxhash-3.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (193 kB)\n", + "Building wheels for collected packages: llmrouter-lib\n", + " Building editable for llmrouter-lib (pyproject.toml) ... \u001b[?25ldone\n", + "\u001b[?25h Created wheel for llmrouter-lib: filename=llmrouter_lib-0.2.0-0.editable-py3-none-any.whl size=15709 sha256=8233ee9dc5595a43840547824f64e8dd2bdfd1a5aa4a57491672043eef0ffd2a\n", + " Stored in directory: /tmp/pip-ephem-wheel-cache-q1fd2c9d/wheels/82/4a/fd/59c4aec93c356c380d86a88ab74bece164c0aa855d68b155e4\n", + "Successfully built llmrouter-lib\n", + "Installing collected packages: pydub, xxhash, uvicorn, tomlkit, shellingham, sentencepiece, semantic-version, safetensors, regex, python-multipart, python-dotenv, propcache, orjson, multiprocess, multidict, mdurl, jiter, hf-xet, grpcio, groovy, fsspec, frozenlist, ffmpy, fastuuid, audioop-lts, annotated-doc, aiohappyeyeballs, aiofiles, yarl, tiktoken, starlette, markdown-it-py, huggingface-hub, aiosignal, tokenizers, safehttpx, rich, openai, gradio-client, fastapi, aiohttp, typer, transformers, torch-geometric, litellm, accelerate, peft, gradio, datasets, llmrouter-lib\n", + "\u001b[2K Attempting uninstall: fsspecm╺\u001b[0m\u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m18/50\u001b[0m [grpcio]ocess]ion]\n", + "\u001b[2K Found existing installation: fsspec 2025.12.0━━━━━━━━━━━━━\u001b[0m \u001b[32m18/50\u001b[0m [grpcio]\n", + "\u001b[2K Uninstalling fsspec-2025.12.0:90m━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m18/50\u001b[0m [grpcio]\n", + "\u001b[2K Successfully uninstalled fsspec-2025.12.0━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m20/50\u001b[0m [fsspec]\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m50/50\u001b[0m [llmrouter-lib]0m [datasets]e]tric]\n", + "\u001b[1A\u001b[2KSuccessfully installed accelerate-1.12.0 aiofiles-24.1.0 aiohappyeyeballs-2.6.1 aiohttp-3.13.3 aiosignal-1.4.0 annotated-doc-0.0.4 audioop-lts-0.2.2 datasets-4.5.0 fastapi-0.128.0 fastuuid-0.14.0 ffmpy-1.0.0 frozenlist-1.8.0 fsspec-2025.10.0 gradio-6.3.0 gradio-client-2.0.3 groovy-0.1.2 grpcio-1.76.0 hf-xet-1.2.0 huggingface-hub-0.36.0 jiter-0.12.0 litellm-1.81.0 llmrouter-lib-0.2.0 markdown-it-py-4.0.0 mdurl-0.1.2 multidict-6.7.0 multiprocess-0.70.18 openai-2.15.0 orjson-3.11.5 peft-0.18.1 propcache-0.4.1 pydub-0.25.1 python-dotenv-1.2.1 python-multipart-0.0.21 regex-2026.1.15 rich-14.2.0 safehttpx-0.1.7 safetensors-0.7.0 semantic-version-2.10.0 sentencepiece-0.2.1 shellingham-1.5.4 starlette-0.50.0 tiktoken-0.12.0 tokenizers-0.22.2 tomlkit-0.13.3 torch-geometric-2.7.0 transformers-4.57.6 typer-0.21.1 uvicorn-0.40.0 xxhash-3.6.0 yarl-1.22.0\n", + "Requirement already satisfied: transformers in /opt/conda/lib/python3.13/site-packages (4.57.6)\n", + "Requirement already satisfied: torch in /opt/conda/lib/python3.13/site-packages (2.9.1)\n", + "Requirement already satisfied: filelock in /opt/conda/lib/python3.13/site-packages (from transformers) (3.20.2)\n", + "Requirement already satisfied: huggingface-hub<1.0,>=0.34.0 in /opt/conda/lib/python3.13/site-packages (from transformers) (0.36.0)\n", + "Requirement already satisfied: numpy>=1.17 in /opt/conda/lib/python3.13/site-packages (from transformers) (2.3.5)\n", + "Requirement already satisfied: packaging>=20.0 in /opt/conda/lib/python3.13/site-packages (from transformers) (25.0)\n", + "Requirement already satisfied: pyyaml>=5.1 in /opt/conda/lib/python3.13/site-packages (from transformers) (6.0.3)\n", + "Requirement already satisfied: regex!=2019.12.17 in /opt/conda/lib/python3.13/site-packages (from transformers) (2026.1.15)\n", + "Requirement already satisfied: requests in /opt/conda/lib/python3.13/site-packages (from transformers) (2.32.5)\n", + "Requirement already satisfied: tokenizers<=0.23.0,>=0.22.0 in /opt/conda/lib/python3.13/site-packages (from transformers) (0.22.2)\n", + "Requirement already satisfied: safetensors>=0.4.3 in /opt/conda/lib/python3.13/site-packages (from transformers) (0.7.0)\n", + "Requirement already satisfied: tqdm>=4.27 in /opt/conda/lib/python3.13/site-packages (from transformers) (4.67.1)\n", + "Requirement already satisfied: fsspec>=2023.5.0 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (2025.10.0)\n", + "Requirement already satisfied: typing-extensions>=3.7.4.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (4.15.0)\n", + "Requirement already satisfied: hf-xet<2.0.0,>=1.1.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (1.2.0)\n", + "Requirement already satisfied: setuptools in /opt/conda/lib/python3.13/site-packages (from torch) (80.9.0)\n", + "Requirement already satisfied: sympy>=1.13.3 in /opt/conda/lib/python3.13/site-packages (from torch) (1.14.0)\n", + "Requirement already satisfied: networkx>=2.5.1 in /opt/conda/lib/python3.13/site-packages (from torch) (3.6.1)\n", + "Requirement already satisfied: jinja2 in /opt/conda/lib/python3.13/site-packages (from torch) (3.1.6)\n", + "Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.93)\n", + "Requirement already satisfied: nvidia-cuda-runtime-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-cuda-cupti-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-cudnn-cu12==9.10.2.21 in /opt/conda/lib/python3.13/site-packages (from torch) (9.10.2.21)\n", + "Requirement already satisfied: nvidia-cublas-cu12==12.8.4.1 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.4.1)\n", + "Requirement already satisfied: nvidia-cufft-cu12==11.3.3.83 in /opt/conda/lib/python3.13/site-packages (from torch) (11.3.3.83)\n", + "Requirement already satisfied: nvidia-curand-cu12==10.3.9.90 in /opt/conda/lib/python3.13/site-packages (from torch) (10.3.9.90)\n", + "Requirement already satisfied: nvidia-cusolver-cu12==11.7.3.90 in /opt/conda/lib/python3.13/site-packages (from torch) (11.7.3.90)\n", + "Requirement already satisfied: nvidia-cusparse-cu12==12.5.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.5.8.93)\n", + "Requirement already satisfied: nvidia-cusparselt-cu12==0.7.1 in /opt/conda/lib/python3.13/site-packages (from torch) (0.7.1)\n", + "Requirement already satisfied: nvidia-nccl-cu12==2.27.5 in /opt/conda/lib/python3.13/site-packages (from torch) (2.27.5)\n", + "Requirement already satisfied: nvidia-nvshmem-cu12==3.3.20 in /opt/conda/lib/python3.13/site-packages (from torch) (3.3.20)\n", + "Requirement already satisfied: nvidia-nvtx-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-nvjitlink-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.93)\n", + "Requirement already satisfied: nvidia-cufile-cu12==1.13.1.3 in /opt/conda/lib/python3.13/site-packages (from torch) (1.13.1.3)\n", + "Requirement already satisfied: triton==3.5.1 in /opt/conda/lib/python3.13/site-packages (from torch) (3.5.1)\n", + "Requirement already satisfied: mpmath<1.4,>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from sympy>=1.13.3->torch) (1.3.0)\n", + "Requirement already satisfied: MarkupSafe>=2.0 in /opt/conda/lib/python3.13/site-packages (from jinja2->torch) (3.0.3)\n", + "Requirement already satisfied: charset_normalizer<4,>=2 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (3.4.4)\n", + "Requirement already satisfied: idna<4,>=2.5 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (3.11)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (2.6.2)\n", + "Requirement already satisfied: certifi>=2017.4.17 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (2026.1.4)\n" + ] + } + ], + "source": [ + "# Install required packages (for Colab)\n", + "!git clone https://github.com/ulab-uiuc/LLMRouter.git\n", + "%cd LLMRouter\n", + "!pip install -e .\n", + "!pip install transformers torch\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "os.environ['OPENAI_API_KEY'] = 'your-key'\n", + "os.environ['ANTHROPIC_API_KEY'] = 'your-key'\n", + "# Or for multiple keys:\n", + "os.environ['API_KEYS'] = '[\"key1\", \"key2\"]'" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Environment setup complete!\n" + ] + } + ], + "source": [ + "from llmrouter.models.mlprouter import MLPRouter, MLPTrainer\n", + "from llmrouter.utils import setup_environment\n", + "\n", + "setup_environment()\n", + "print(\"Environment setup complete!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Configuration\n", + "\n", + "MLPRouter uses the following configuration parameters:\n", + "\n", + "| Parameter | Description | Default |\n", + "|-----------|-------------|--------|\n", + "| `hidden_layer_sizes` | Neurons in each hidden layer | [128, 64] |\n", + "| `activation` | Activation function | \"relu\" |\n", + "| `solver` | Optimizer: \"adam\", \"lbfgs\", \"sgd\" | \"adam\" |\n", + "| `alpha` | L2 regularization | 0.0001 |\n", + "| `learning_rate` | Learning rate schedule | \"adaptive\" |\n", + "| `max_iter` | Maximum iterations | 500 |" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Current Configuration:\n", + "==================================================\n", + "api_endpoint: https://integrate.api.nvidia.com/v1\n", + "data_path:\n", + " llm_data: data/example_data/llm_candidates/default_llm.json\n", + " llm_embedding_data: data/example_data/llm_candidates/default_llm_embeddings.json\n", + " query_data_test: data/example_data/query_data/default_query_test.jsonl\n", + " query_data_train: data/example_data/query_data/default_query_train.jsonl\n", + " query_embedding_data: data/example_data/routing_data/query_embeddings_longformer.pt\n", + " routing_data_test: data/example_data/routing_data/default_routing_test_data.jsonl\n", + " routing_data_train: data/example_data/routing_data/default_routing_train_data.jsonl\n", + "hparam:\n", + " activation: relu\n", + " alpha: 0.0001\n", + " batch_size: 32\n", + " epochs: 100\n", + " hidden_layer_sizes:\n", + " - 128\n", + " - 64\n", + " lr: 0.001\n", + "metric:\n", + " weights:\n", + " cost: 0\n", + " llm_judge: 0\n", + " performance: 1\n", + "model_path:\n", + " ini_model_path: ''\n", + " save_model_path: saved_models/mlprouter/mlprouter.pkl\n", + "\n" + ] + } + ], + "source": [ + "import yaml\n", + "\n", + "CONFIG_PATH = \"configs/model_config_train/mlprouter.yaml\"\n", + "\n", + "with open(CONFIG_PATH, 'r') as f:\n", + " config = yaml.safe_load(f)\n", + "\n", + "print(\"Current Configuration:\")\n", + "print(\"=\" * 50)\n", + "print(yaml.dump(config, default_flow_style=False))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Initialize Router" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "✅ MetaRouter initialized successfully (YAML + data loaded).\n", + "Router initialized successfully!\n", + "Number of training samples: 50544\n", + "Number of LLM candidates: 7\n", + "LLM candidates: ['qwen2.5-7b-instruct', 'llama-3.1-8b-instruct', 'mistral-7b-instruct-v0.3', 'llama-3.3-nemotron-super-49b-v1', 'llama3-70b-instruct', 'mixtral-8x7b-instruct-v0.1', 'mixtral-8x22b-instruct-v0.1']\n" + ] + } + ], + "source": [ + "router = MLPRouter(yaml_path=CONFIG_PATH)\n", + "\n", + "print(\"Router initialized successfully!\")\n", + "print(f\"Number of training samples: {len(router.routing_data_train)}\")\n", + "print(f\"Number of LLM candidates: {len(router.llm_data)}\")\n", + "print(f\"LLM candidates: {list(router.llm_data.keys())}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "MLP Model Parameters:\n", + " Input dimension: 768\n", + " Hidden layers: [128, 64]\n", + " Output features: 9\n", + " Activation: relu\n", + " Total parameters: 107273\n" + ] + } + ], + "source": [ + "# Inspect MLP architecture\n", + "print(\"MLP Model Parameters:\")\n", + "print(f\" Input dimension: {router.mlp_model.layers[0].in_features}\")\n", + "print(f\" Hidden layers: {[layer.out_features for layer in router.mlp_model.layers[:-1]]}\")\n", + "print(f\" Output features: {router.mlp_model.layers[-1].out_features}\")\n", + "print(f\" Activation: {router.mlp_model.activation_name}\")\n", + "print(f\" Total parameters: {sum(p.numel() for p in router.mlp_model.parameters())}\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Training" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[MLPTrainer] Initialized on device: cuda\n", + "[MLPTrainer] Model: [128, 64], lr=0.001, epochs=100, batch_size=32\n", + "Trainer initialized!\n", + "Training samples: 5608\n", + "Save path: /home/zhongjie/LLMRouter/llmrouter/saved_models/mlprouter/mlprouter.pkl\n" + ] + } + ], + "source": [ + "import torch\n", + "device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n", + "trainer = MLPTrainer(router=router, device=device)\n", + "\n", + "print(\"Trainer initialized!\")\n", + "print(f\"Training samples: {len(trainer.query_embeddings)}\")\n", + "print(f\"Save path: {trainer.save_model_path}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting training...\n", + "==================================================\n", + "[MLPTrainer] Epoch 10/100 - Loss=1.240885, Accuracy=0.5851\n", + "[MLPTrainer] Epoch 20/100 - Loss=1.028582, Accuracy=0.6471\n", + "[MLPTrainer] Epoch 30/100 - Loss=0.746673, Accuracy=0.7734\n", + "[MLPTrainer] Epoch 40/100 - Loss=0.473416, Accuracy=0.8764\n", + "[MLPTrainer] Epoch 50/100 - Loss=0.304272, Accuracy=0.9331\n", + "[MLPTrainer] Epoch 60/100 - Loss=0.202495, Accuracy=0.9574\n", + "[MLPTrainer] Epoch 70/100 - Loss=0.139151, Accuracy=0.9437\n", + "[MLPTrainer] Epoch 80/100 - Loss=0.126454, Accuracy=0.9579\n", + "[MLPTrainer] Epoch 90/100 - Loss=0.136906, Accuracy=0.9626\n", + "[MLPTrainer] Epoch 100/100 - Loss=0.102195, Accuracy=0.9782\n", + "Successfully saved pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/mlprouter/mlprouter.pkl\n", + "[MLPTrainer] Model saved to /home/zhongjie/LLMRouter/llmrouter/saved_models/mlprouter/mlprouter.pkl\n", + "[MLPTrainer] Loss history saved to /home/zhongjie/LLMRouter/llmrouter/saved_models/mlprouter/mlprouter_loss.json\n", + "==================================================\n", + "Training completed!\n" + ] + } + ], + "source": [ + "print(\"Starting training...\")\n", + "print(\"=\" * 50)\n", + "\n", + "trainer.train()\n", + "\n", + "print(\"=\" * 50)\n", + "print(\"Training completed!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Model Verification" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/mlprouter/mlprouter.pkl\n", + "Model loaded successfully!\n", + "Model type: OrderedDict\n", + "Keys: ['layers.0.weight', 'layers.0.bias', 'layers.1.weight', 'layers.1.bias', 'layers.2.weight', 'layers.2.bias']\n", + "Number of layers: 6\n" + ] + } + ], + "source": [ + "from llmrouter.utils import load_model\n", + "import numpy as np\n", + "\n", + "saved_model = load_model(trainer.save_model_path)\n", + "\n", + "print(\"Model loaded successfully!\")\n", + "print(f\"Model type: {type(saved_model).__name__}\")\n", + "print(f\"Keys: {list(saved_model.keys())}\")\n", + "print(f\"Number of layers: {len([k for k in saved_model.keys() if k.startswith('layers.')])}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Test prediction: llama3-chatqa-1.5-70b\n", + "\n", + "Prediction probabilities:\n", + " llama-3.1-8b-instruct: 0.1218\n", + " qwen2.5-7b-instruct: 0.1084\n", + " gemma-2-9b-it: 0.0915\n", + " mistral-7b-instruct-v0.3: 0.1081\n", + " llama3-chatqa-1.5-8b: 0.0948\n", + " codegemma-7b: 0.1093\n", + " llama-3.3-nemotron-super-49b-v1: 0.1240\n", + " llama3-chatqa-1.5-70b: 0.1461\n", + " llama-3.1-nemotron-51b-instruct: 0.0959\n" + ] + } + ], + "source": [ + "# Quick prediction test\n", + "test_embedding = trainer.query_embeddings[0].unsqueeze(0)\n", + "\n", + "router.mlp_model.eval()\n", + "with torch.no_grad():\n", + " logits = router.mlp_model(test_embedding)\n", + " proba = torch.softmax(logits, dim=1).numpy()[0]\n", + " pred_idx = torch.argmax(logits, dim=1).item()\n", + "\n", + "prediction = router.idx_to_model[pred_idx]\n", + "print(f\"Test prediction: {prediction}\")\n", + "\n", + "print(f\"\\nPrediction probabilities:\")\n", + "for idx, prob in enumerate(proba):\n", + " model_name = router.idx_to_model[idx]\n", + " print(f\" {model_name}: {prob:.4f}\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. Learning Curve Analysis" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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xET7s1SpUvbntm3bFBcXl6cfmZPFXXzxxfQj2NHKLl266LLLLsuzN+g3OJ1JkyapefPmuummm+z04CZNmuizzz6j7+CM2rdvrz///FMbN26011esWGFHAq6++mqOOSiwgnw2me/OR44cyXMfM62vfv36hfa9x79QngV5JCQk2CHFqKioPLeb66bTACcy/znTr18/+wFlDhBGbl85VT/asWMHO9GLmSkMS5cutVMdTkS/wels3bpVw4cPt8eaZ599VosWLdJjjz1mv7z06tWLvoNTeuaZZ+x/7NWuXVt+fn72+83rr7+uW2+9lWMOCqwgn03mPoGBgSpZsqTTvj8TnJzILIw78cvxibcBxiOPPKKVK1fa/8WjH+FMdu7cqccff1xTp061hWc4/qCgzGJ+M+L0xhtv2OtmxMkszDZhygQnPrtwujXb33zzjcaMGaN69epp+fLldh2KGQm488476Te44N+NC/P7M1P1nMBUAjH/K3NiOo6Pjz8paQOmeoyZQjNjxgxVqFDh2A4xiyYN+hGOZ6YymGOJqdTp7+9vN1No5KOPPrKXc48x9BucqGzZsqpbt26e2+rUqXOsaBHHHJzK008/rf79++uWW25RgwYNdMcdd+iJJ56wFT3pNyioghxfzH3MchdTyOZ097nQCE5OYIYZzZcaUynteOZ627ZtndEkuCDzPyhmpGn8+PH666+/bKnX45nr5iByfD8yBxTzJZl+5L06deqkVatW2f/1zd3MKMJtt91mL5tSwfQbnIqpqHfiKQ/MupVKlSrZyxxzcCppaWny9c37ddL853BuOXL6DQqiIP3EfHc2VT+Pv09sbKxWr15deN97CqUEBU5iKoKYyiAjR450rF271tG3b19H0aJFHdu3b2dvwXrwwQdtNZm///7bERsbe2xLS0s7todM9Rlzn/HjxztWrVrluPXWWx1ly5Z1JCUlsRdxzPFV9eg3OJ1FixY5/P39Ha+//rpj06ZNjm+//dYREhLi+Oabb+g7OK0777zTUb58ecevv/7q2LZtm/08Kl26tON///sf/QYnVXtdtmyZ3UwEef/99+3lHTt2FPg7jalIXaFCBcf06dMdS5cudVx66aWORo0aObKyshyFgeDkRB9//LGjUqVKjsDAQEfTpk2PlZkGDHNQOdU2evToPOU7X3rpJVvCMygoyHHRRRfZgw1wpuBEv8Hp/PLLL4769evb44k5RcaIESPy/J6+gxOZL7Xm+FKxYkVHcHCwo2rVqo7nnnvOkZGRQb9BHjNmzDjl9xoTvgt6fDl8+LDjkUcecZQqVcpRpEgRxzXXXOOIiYlxFBYf80/hjG0BAAAAgHtijRMAAAAA5IPgBAAAAAD5IDgBAAAAQD4ITgAAAACQD4ITAAAAAOSD4AQAAAAA+SA4AQAAAEA+CE4AAAAAkA+CEwAAZ8HHx0cTJkxgnwGAlyE4AQDcxl133WWDy4nbVVdd5eymAQA8nL+zGwAAwNkwIWn06NF5bgsKCmInAgAuKEacAABuxYSkMmXK5NlKlixpf2dGn4YPH67OnTurSJEiqlKlin744Yc8j1+1apUuvfRS+/vw8HDdf//9SklJyXOfUaNGqV69eva5ypYtq0ceeSTP7xMSEnT99dcrJCRENWrU0KRJkwrhlQMAnIngBADwKC+88IJuuOEGrVixQrfffrtuvfVWrVu3zv4uLS3NjliZoPXPP//YUDV9+vQ8wcgEr4cfftgGKhOyTCiqXr16nucYOHCgbr75Zq1cuVJXX321brvtNh04cKDQXysAoPD4OBwORyE+HwAA/2mN0zfffKPg4OA8tz/zzDM2MJkRpz59+tjwk6t169Zq2rSphg0bps8++8zed+fOnSpatKj9/eTJk9W1a1ft2bNHUVFRKl++vHr37q3XXnvtlG0wz/H888/r1VdftddTU1NVvHhx+3dYawUAnos1TgAAt3LJJZfkCUZGqVKljl1u06ZNnt+Z68uXL7eXzchTo0aNjoUmo127dsrJydGGDRtsKDIBqlOnTmdsQ8OGDY9dNn/LBKf4+Pj//NoAAK6L4AQAcCsmqJw4dS4/JhAZZpJF7uVT3ceseyqIgICAkx5rwhcAwHOxxgkA4FEWLFhw0vXatWvby3Xr1rWjT2Z6Xa65c+fK19dXNWvWtCNHlStX1p9//lno7QYAuDZGnAAAbiUjI0NxcXF5bvP391fp0qXtZVPwoXnz5mrfvr2+/fZbLVq0SCNHjrS/M0UcXnrpJd155516+eWXtW/fPj366KO644477Pomw9xu1klFRkba6nzJyck2XJn7AQC8F8EJAOBWpkyZYkuEH69WrVpav379sYp3Y8eO1UMPPWRLlZvwZEaaDFM+/I8//tDjjz+uFi1a2OumAt/7779/7G+ZUJWenq4PPvhATz31lA1kN954YyG/SgCAq6GqHgDAY5i1Rj///LOuu+46ZzcFAOBhWOMEAAAAAPkgOAEAAABAPljjBADwGJzTHQBwoTDiBAAAAAD5IDgBAAAAQD4ITgAAAACQD4ITAAAAAOSD4AQAAAAA+SA4AQAAAEA+CE4AAAAAkA+CEwAAAADozP4PAXvWfkd9HzMAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "import json\n", + "import os\n", + "\n", + "loss_path = trainer.save_model_path.replace('.pth', '_loss.json').replace('.pkl', '_loss.json')\n", + "\n", + "if os.path.exists(loss_path):\n", + " with open(loss_path, 'r') as f:\n", + " loss_history = json.load(f)\n", + "\n", + " plt.figure(figsize=(10, 5))\n", + " plt.plot(loss_history)\n", + " plt.xlabel('Epoch')\n", + " plt.ylabel('Loss')\n", + " plt.title('MLP Training Loss Curve')\n", + " plt.grid(True, alpha=0.3)\n", + " plt.show()\n", + "else:\n", + " print(\"Loss history not found. Run training again with updated trainer.\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7. Architecture Comparison" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Architecture comparison:\n", + "==================================================\n", + "[64] Accuracy: 0.5098 (+/- 0.0034)\n", + "[128] Accuracy: 0.5200 (+/- 0.0057)\n", + "[128, 64] Accuracy: 0.5114 (+/- 0.0071)\n", + "[256, 128] Accuracy: 0.5212 (+/- 0.0033)\n", + "[256, 128, 64] Accuracy: 0.5100 (+/- 0.0108)\n", + "\n", + "Best architecture: [256, 128] with accuracy: 0.5212\n" + ] + } + ], + "source": [ + "import numpy as np\n", + "import torch\n", + "import torch.nn as nn\n", + "from torch.utils.data import TensorDataset, DataLoader\n", + "from sklearn.model_selection import KFold\n", + "from llmrouter.models.mlprouter.router import MLPClassifierNN\n", + "\n", + "X_np = trainer.query_embeddings.numpy()\n", + "y_np = trainer.label_indices.numpy()\n", + "X_tensor = torch.FloatTensor(X_np)\n", + "y_tensor = torch.LongTensor(y_np)\n", + "\n", + "architectures = [\n", + " [64],\n", + " [128],\n", + " [128, 64],\n", + " [256, 128],\n", + " [256, 128, 64],\n", + "]\n", + "\n", + "print(\"Architecture comparison:\")\n", + "print(\"=\" * 50)\n", + "\n", + "results = []\n", + "for arch in architectures:\n", + " fold_scores = []\n", + " kf = KFold(n_splits=3, shuffle=True, random_state=42)\n", + " \n", + " for train_idx, val_idx in kf.split(X_tensor):\n", + " X_train, X_val = X_tensor[train_idx], X_tensor[val_idx]\n", + " y_train, y_val = y_tensor[train_idx], y_tensor[val_idx]\n", + " \n", + " model = MLPClassifierNN(\n", + " input_dim=X_np.shape[1],\n", + " hidden_layer_sizes=arch,\n", + " num_classes=len(np.unique(y_np)),\n", + " activation=\"relu\"\n", + " )\n", + " optimizer = torch.optim.Adam(model.parameters(), lr=0.001)\n", + " criterion = nn.CrossEntropyLoss()\n", + " \n", + " model.train()\n", + " for _ in range(50):\n", + " optimizer.zero_grad()\n", + " outputs = model(X_train)\n", + " loss = criterion(outputs, y_train)\n", + " loss.backward()\n", + " optimizer.step()\n", + " \n", + " model.eval()\n", + " with torch.no_grad():\n", + " val_outputs = model(X_val)\n", + " _, predicted = torch.max(val_outputs, 1)\n", + " acc = (predicted == y_val).float().mean().item()\n", + " fold_scores.append(acc)\n", + " \n", + " mean_score = np.mean(fold_scores)\n", + " std_score = np.std(fold_scores)\n", + " results.append((arch, mean_score, std_score))\n", + " print(f\"{str(arch):20} Accuracy: {mean_score:.4f} (+/- {std_score:.4f})\")\n", + "\n", + "best_arch, best_score, _ = max(results, key=lambda x: x[1])\n", + "print(f\"\\nBest architecture: {best_arch} with accuracy: {best_score:.4f}\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "In this notebook, we:\n", + "\n", + "1. **Loaded Configuration**: Set up MLPRouter with YAML configuration\n", + "2. **Trained Model**: Used MLPRouterTrainer to fit the neural network\n", + "3. **Verified Model**: Loaded and tested the saved model\n", + "4. **Compared Architectures**: Found optimal layer configuration\n", + "\n", + "**Next Steps**:\n", + "- Use the next part of notebook for inference\n", + "- Experiment with different activation functions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# MLPRouter - Inference\n", + "\n", + "This part of notebook demonstrates how to use a trained **MLPRouter** for inference." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Environment Setup (optional)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "from llmrouter.models.mlprouter import MLPRouter\n", + "from llmrouter.utils import setup_environment, load_model, get_longformer_embedding\n", + "import yaml\n", + "\n", + "setup_environment()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Load Trained Router" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "✅ MetaRouter initialized successfully (YAML + data loaded).\n", + "Router loaded with 7 LLM candidates\n" + ] + } + ], + "source": [ + "CONFIG_PATH = \"configs/model_config_train/mlprouter.yaml\"\n", + "\n", + "with open(CONFIG_PATH, 'r') as f:\n", + " config = yaml.safe_load(f)\n", + "\n", + "config['model_path']['load_model_path'] = config['model_path']['save_model_path']\n", + "\n", + "INFERENCE_CONFIG_PATH = \"configs/model_config_test/mlprouter_inference.yaml\"\n", + "os.makedirs(os.path.dirname(INFERENCE_CONFIG_PATH), exist_ok=True)\n", + "\n", + "with open(INFERENCE_CONFIG_PATH, 'w') as f:\n", + " yaml.dump(config, f)\n", + "\n", + "router = MLPRouter(yaml_path=INFERENCE_CONFIG_PATH)\n", + "print(f\"Router loaded with {len(router.llm_data)} LLM candidates\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Single Query Routing" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Routing Results:\n", + "============================================================\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/mlprouter/mlprouter.pkl\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Input ids are automatically padded to be a multiple of `config.attention_window`: 512\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1. What is the capital of France?...\n", + " Routed to: gemma-2-9b-it\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/mlprouter/mlprouter.pkl\n", + "2. Solve the equation: 2x + 5 = 15...\n", + " Routed to: qwen2.5-7b-instruct\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/mlprouter/mlprouter.pkl\n", + "3. Write a Python function to check if a number is pr...\n", + " Routed to: llama-3.1-8b-instruct\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/mlprouter/mlprouter.pkl\n", + "4. Explain quantum computing in simple terms....\n", + " Routed to: llama-3.1-8b-instruct\n" + ] + } + ], + "source": [ + "EXAMPLE_QUERIES = [\n", + " {\"query\": \"What is the capital of France?\"},\n", + " {\"query\": \"Solve the equation: 2x + 5 = 15\"},\n", + " {\"query\": \"Write a Python function to check if a number is prime.\"},\n", + " {\"query\": \"Explain quantum computing in simple terms.\"},\n", + "]\n", + "\n", + "print(\"Routing Results:\")\n", + "print(\"=\" * 60)\n", + "\n", + "for i, query in enumerate(EXAMPLE_QUERIES, 1):\n", + " result = router.route_single(query)\n", + " print(f\"{i}. {query['query'][:50]}...\")\n", + " print(f\" Routed to: {result['model_name']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Confidence Analysis" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Confidence Analysis:\n", + "============================================================\n", + "Query: What is the capital of France?...\n", + " Prediction: llama3-chatqa-1.5-8b (Confidence: 0.1274)\n", + "Query: Solve the equation: 2x + 5 = 15...\n", + " Prediction: llama3-chatqa-1.5-8b (Confidence: 0.1263)\n", + "Query: Write a Python function to check if a nu...\n", + " Prediction: llama3-chatqa-1.5-8b (Confidence: 0.1273)\n", + "Query: Explain quantum computing in simple term...\n", + " Prediction: llama3-chatqa-1.5-8b (Confidence: 0.1264)\n" + ] + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import torch\n", + "from pathlib import Path\n", + "\n", + "PROJECT_ROOT = Path(os.getcwd()).parent.parent\n", + "project_root = os.getcwd()\n", + "\n", + "def get_confidence(query_text):\n", + " \"\"\"Get prediction and confidence using router's mlp_model.\"\"\"\n", + " router.mlp_model.eval()\n", + " embedding = get_longformer_embedding(query_text).unsqueeze(0)\n", + " \n", + " with torch.no_grad():\n", + " logits = router.mlp_model(embedding)\n", + " proba = torch.softmax(logits, dim=1).numpy()[0]\n", + " pred_idx = torch.argmax(logits, dim=1).item()\n", + " \n", + " prediction = router.idx_to_model[pred_idx]\n", + " confidence = max(proba)\n", + " probs = {router.idx_to_model[i]: prob for i, prob in enumerate(proba)}\n", + " \n", + " return prediction, confidence, probs\n", + "\n", + "print(\"Confidence Analysis:\")\n", + "print(\"=\" * 60)\n", + "for query in EXAMPLE_QUERIES:\n", + " pred, conf, probs = get_confidence(query['query'])\n", + " print(f\"Query: {query['query'][:40]}...\")\n", + " print(f\" Prediction: {pred} (Confidence: {conf:.4f})\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. File-Based Inference\n", + "\n", + "Load queries from a file and save results." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loaded 706 queries from: data/example_data/query_data/default_query_test.jsonl\n", + "Successfully loaded pickle model: /home/zhongjie/LLMRouter/llmrouter/saved_models/mlprouter/mlprouter.pkl\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "\n", + "\u001b[1;31mGive Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new\u001b[0m\n", + "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.\n", + "\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "\n", + "\u001b[1;31mGive Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new\u001b[0m\n", + "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.\n", + "\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "\n", + "\u001b[1;31mGive Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new\u001b[0m\n", + "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.\n", + "\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "\n", + "\u001b[1;31mGive Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new\u001b[0m\n", + "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.\n", + "\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "\n", + "\u001b[1;31mGive Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new\u001b[0m\n", + "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.\n", + "\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "\n", + "\u001b[1;31mGive Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new\u001b[0m\n", + "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.\n", + "\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "\n", + "\u001b[1;31mGive Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new\u001b[0m\n", + "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.\n", + "\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "\n", + "\u001b[1;31mGive Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new\u001b[0m\n", + "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.\n", + "\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "\n", + "\u001b[1;31mGive Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new\u001b[0m\n", + "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.\n", + "\n", + "Routed 10 queries\n", + "Results saved to: outputs/mlprouter_results.jsonl\n", + "\n", + "Sample results:\n", + " 1. Q: There are 4 houses in a row, numbered... -> gemma-2-9b-it\n", + " 2. Q: There are 3 houses in a row, numbered... -> gemma-2-9b-it\n", + " 3. Q: There are 3 houses in a row, numbered... -> gemma-2-9b-it\n" + ] + } + ], + "source": [ + "import json\n", + "\n", + "# Load queries from a JSONL file\n", + "def load_queries_from_file(file_path):\n", + " \"\"\"Load queries from a JSONL file.\"\"\"\n", + " queries = []\n", + " with open(file_path, 'r', encoding='utf-8') as f:\n", + " for line in f:\n", + " if line.strip():\n", + " queries.append(json.loads(line))\n", + " return queries\n", + "\n", + "# Save results to a JSONL file\n", + "def save_results_to_file(results, output_path):\n", + " \"\"\"Save routing results to a JSONL file.\"\"\"\n", + " os.makedirs(os.path.dirname(output_path), exist_ok=True)\n", + " with open(output_path, 'w', encoding='utf-8') as f:\n", + " for result in results:\n", + " f.write(json.dumps(result, ensure_ascii=False) + '\\n')\n", + " print(f\"Results saved to: {output_path}\")\n", + "\n", + "# Example: Load from default query file\n", + "QUERY_FILE = \"data/example_data/query_data/default_query_test.jsonl\"\n", + "OUTPUT_FILE = \"outputs/mlprouter_results.jsonl\"\n", + "\n", + "if os.path.exists(QUERY_FILE):\n", + " # Load queries\n", + " file_queries = load_queries_from_file(QUERY_FILE)\n", + " print(f\"Loaded {len(file_queries)} queries from: {QUERY_FILE}\")\n", + " \n", + " # Route queries\n", + " file_results = router.route_batch(batch=file_queries[:10])\n", + " print(f\"Routed {len(file_results)} queries\")\n", + " \n", + " # Save results\n", + " save_results_to_file(file_results, OUTPUT_FILE)\n", + " \n", + " # Show sample results\n", + " print(f\"\\nSample results:\")\n", + " for i, result in enumerate(file_results[:3], 1):\n", + " print(f\" {i}. {result.get('query', '')[:40]}... -> {result['model_name']}\")\n", + "else:\n", + " print(f\"Query file not found: {QUERY_FILE}\")\n", + " print(\"Create a JSONL file with format: {\\\"query\\\": \\\"Your question\\\"}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "This notebook demonstrated:\n", + "1. Loading a trained MLPRouter\n", + "2. Single query routing\n", + "3. Confidence analysis\n", + "\n", + "MLPRouter is effective for:\n", + "- Complex decision boundaries\n", + "- Large-scale routing with many LLMs" + ] + } + ], + "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.13.11" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/colab_notebooks/router_r1/01_router_r1_inference.ipynb b/colab_notebooks/router_r1/01_router_r1_inference.ipynb new file mode 100644 index 0000000..cc30aa0 --- /dev/null +++ b/colab_notebooks/router_r1/01_router_r1_inference.ipynb @@ -0,0 +1,2384 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# RouterR1 - Inference\n", + "\n", + "This notebook demonstrates how to use **RouterR1** for agentic routing and reasoning.\n", + "\n", + "## Overview\n", + "\n", + "RouterR1 is an agentic router that performs R1-like reasoning with iterative search and routing.\n", + "It uses vLLM for local inference and an external routing API for model selection.\n", + "\n", + "**Key Features**:\n", + "- Agentic reasoning with iterative search\n", + "- Pre-trained model (no training required)\n", + "- Uses vLLM for efficient GPU inference\n", + "- Integrates with external routing pool API\n", + "\n", + "**Requirements**:\n", + "- CUDA-enabled GPU\n", + "- vLLM library\n", + "- OpenAI-compatible API endpoint" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Environment Setup" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# For Google Colab: Ensure GPU runtime is enabled\n", + "# Runtime -> Change runtime type -> Hardware accelerator -> GPU\n", + "\n", + "import os\n", + "\n", + "if 'COLAB_GPU' in os.environ:\n", + " !git clone https://github.com/ulab-uiuc/LLMRouter.git\n", + " %cd LLMRouter\n", + " !pip install -e .\n", + " !pip install vllm transformers pyyaml openai torch" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "os.environ['OPENAI_API_KEY'] = 'your-key'\n", + "os.environ['ANTHROPIC_API_KEY'] = 'your-key'\n", + "# Or for multiple keys:\n", + "os.environ['API_KEYS'] = '[\"key1\", \"key2\"]'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import sys\n", + "from pathlib import Path\n", + "\n", + "PROJECT_ROOT = Path(os.getcwd()).parent.parent\n", + "if str(PROJECT_ROOT) not in sys.path:\n", + " sys.path.insert(0, str(PROJECT_ROOT))\n", + "\n", + "os.chdir(PROJECT_ROOT)\n", + "print(f\"Working directory: {os.getcwd()}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CUDA available: True\n", + "GPU: NVIDIA A100-SXM4-80GB\n", + "GPU count: 1\n" + ] + } + ], + "source": [ + "import torch\n", + "\n", + "# Check CUDA availability (required for RouterR1)\n", + "print(f\"CUDA available: {torch.cuda.is_available()}\")\n", + "if torch.cuda.is_available():\n", + " print(f\"GPU: {torch.cuda.get_device_name(0)}\")\n", + " print(f\"GPU count: {torch.cuda.device_count()}\")\n", + "else:\n", + " print(\"WARNING: RouterR1 requires CUDA. Please enable GPU runtime.\")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/conda/lib/python3.13/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", + " from .autonotebook import tqdm as notebook_tqdm\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Environment setup complete!\n" + ] + } + ], + "source": [ + "from llmrouter.utils import setup_environment\n", + "import yaml\n", + "\n", + "setup_environment()\n", + "print(\"Environment setup complete!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Configuration\n", + "\n", + "RouterR1 requires the following configuration parameters:\n", + "\n", + "| Parameter | Description | Required |\n", + "|-----------|-------------|----------|\n", + "| `model_id` | HuggingFace model ID for reasoning | Yes |\n", + "| `api_base` | Routing API endpoint URL | Yes |\n", + "| `api_key` | API key for routing service | Yes |\n", + "\n", + "**Note**: Create a config file or set these parameters programmatically." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Configuration:\n", + "==================================================\n", + "data_path:\n", + " llm_data: data/example_data/llm_candidates/default_llm.json\n", + " query_data_test: data/example_data/query_data/default_query_test.jsonl\n", + " query_data_train: data/example_data/query_data/default_query_train.jsonl\n", + " routing_data_test: data/example_data/routing_data/default_routing_test_data.jsonl\n", + " routing_data_train: data/example_data/routing_data/default_routing_train_data.jsonl\n", + "hparam:\n", + " api_base: https://api.openai.com/v1\n", + " api_key: your-key\n", + " model_id: Qwen/Qwen2.5-3B-Instruct\n", + "\n" + ] + } + ], + "source": [ + "# Create configuration for RouterR1\n", + "# Replace with your actual API credentials\n", + "\n", + "router_r1_config = {\n", + " \"data_path\": {\n", + " \"query_data_train\": \"data/example_data/query_data/default_query_train.jsonl\",\n", + " \"query_data_test\": \"data/example_data/query_data/default_query_test.jsonl\",\n", + " \"routing_data_train\": \"data/example_data/routing_data/default_routing_train_data.jsonl\",\n", + " \"routing_data_test\": \"data/example_data/routing_data/default_routing_test_data.jsonl\",\n", + " \"llm_data\": \"data/example_data/llm_candidates/default_llm.json\"\n", + " },\n", + " \"hparam\": {\n", + " \"model_id\": \"Qwen/Qwen2.5-3B-Instruct\", # Pre-trained model for reasoning\n", + " \"api_base\": \"https://api.openai.com/v1\", # Replace with your API endpoint\n", + " \"api_key\": os.environ.get(\"OPENAI_API_KEY\", \"your-api-key-here\") # Replace with your API key\n", + " }\n", + "}\n", + "\n", + "# Save config to temporary file\n", + "CONFIG_PATH = \"configs/model_config_train/router_r1_temp.yaml\"\n", + "os.makedirs(os.path.dirname(CONFIG_PATH), exist_ok=True)\n", + "\n", + "with open(CONFIG_PATH, 'w') as f:\n", + " yaml.dump(router_r1_config, f, default_flow_style=False)\n", + "\n", + "print(\"Configuration:\")\n", + "print(\"=\" * 50)\n", + "print(yaml.dump(router_r1_config, default_flow_style=False))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Initialize Router" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "✅ MetaRouter initialized successfully (YAML + data loaded).\n", + "RouterR1 initialized successfully!\n", + "Model ID: Qwen/Qwen2.5-3B-Instruct\n" + ] + } + ], + "source": [ + "from llmrouter.models.router_r1 import RouterR1\n", + "\n", + "# Initialize RouterR1 (requires valid API credentials)\n", + "\n", + "try:\n", + " router = RouterR1(yaml_path=CONFIG_PATH)\n", + " print(\"RouterR1 initialized successfully!\")\n", + " print(f\"Model ID: {router.model_id}\")\n", + "except Exception as e:\n", + " print(f\"Error initializing RouterR1: {e}\")\n", + " print(\"Please ensure you have valid API credentials configured.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Single Query Routing\n", + "\n", + "RouterR1 performs agentic reasoning with iterative search and routing.\n", + "The process includes:\n", + "1. Initial prompt generation\n", + "2. Iterative reasoning with search queries\n", + "3. External routing API calls\n", + "4. Final answer generation" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Query: What is the capital of France and what is its population?\n", + "============================================================\n", + "INFO 01-11 02:44:56 [utils.py:253] non-default args: {'dtype': 'float16', 'disable_log_stats': True, 'model': 'Qwen/Qwen2.5-3B-Instruct'}\n", + "INFO 01-11 02:44:56 [model.py:514] Resolved architecture: Qwen2ForCausalLM\n", + "WARNING 01-11 02:44:56 [model.py:2005] Casting torch.bfloat16 to torch.float16.\n", + "INFO 01-11 02:44:56 [model.py:1661] Using max model len 32768\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2026-01-11 02:45:00,944\tINFO util.py:154 -- Missing packages: ['ipywidgets']. Run `pip install -U ipywidgets`, then restart the notebook server for rich notebook output.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "INFO 01-11 02:45:00 [scheduler.py:230] Chunked prefill is enabled with max_num_batched_tokens=8192.\n", + "WARNING 01-11 02:45:01 [system_utils.py:136] We must use the `spawn` multiprocessing start method. Overriding VLLM_WORKER_MULTIPROC_METHOD to 'spawn'. See https://docs.vllm.ai/en/latest/usage/troubleshooting.html#python-multiprocessing for more information. Reasons: CUDA is initialized\n", + "\u001b[0;36m(EngineCore_DP0 pid=1301)\u001b[0;0m INFO 01-11 02:45:08 [core.py:93] Initializing a V1 LLM engine (v0.13.0) with config: model='Qwen/Qwen2.5-3B-Instruct', speculative_config=None, tokenizer='Qwen/Qwen2.5-3B-Instruct', skip_tokenizer_init=False, tokenizer_mode=auto, revision=None, tokenizer_revision=None, trust_remote_code=False, dtype=torch.float16, max_seq_len=32768, download_dir=None, load_format=auto, tensor_parallel_size=1, pipeline_parallel_size=1, data_parallel_size=1, disable_custom_all_reduce=False, quantization=None, enforce_eager=False, kv_cache_dtype=auto, device_config=cuda, structured_outputs_config=StructuredOutputsConfig(backend='auto', disable_fallback=False, disable_any_whitespace=False, disable_additional_properties=False, reasoning_parser='', reasoning_parser_plugin='', enable_in_reasoning=False), observability_config=ObservabilityConfig(show_hidden_metrics_for_version=None, otlp_traces_endpoint=None, collect_detailed_traces=None, kv_cache_metrics=False, kv_cache_metrics_sample=0.01, cudagraph_metrics=False, enable_layerwise_nvtx_tracing=False), seed=0, served_model_name=Qwen/Qwen2.5-3B-Instruct, enable_prefix_caching=True, enable_chunked_prefill=True, pooler_config=None, compilation_config={'level': None, 'mode': , 'debug_dump_path': None, 'cache_dir': '', 'compile_cache_save_format': 'binary', 'backend': 'inductor', 'custom_ops': ['none'], 'splitting_ops': ['vllm::unified_attention', 'vllm::unified_attention_with_output', 'vllm::unified_mla_attention', 'vllm::unified_mla_attention_with_output', 'vllm::mamba_mixer2', 'vllm::mamba_mixer', 'vllm::short_conv', 'vllm::linear_attention', 'vllm::plamo2_mamba_mixer', 'vllm::gdn_attention_core', 'vllm::kda_attention', 'vllm::sparse_attn_indexer'], 'compile_mm_encoder': False, 'compile_sizes': [], 'compile_ranges_split_points': [8192], 'inductor_compile_config': {'enable_auto_functionalized_v2': False, 'combo_kernels': True, 'benchmark_combo_kernel': True}, 'inductor_passes': {}, 'cudagraph_mode': , 'cudagraph_num_of_warmups': 1, 'cudagraph_capture_sizes': [1, 2, 4, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128, 136, 144, 152, 160, 168, 176, 184, 192, 200, 208, 216, 224, 232, 240, 248, 256, 272, 288, 304, 320, 336, 352, 368, 384, 400, 416, 432, 448, 464, 480, 496, 512], 'cudagraph_copy_inputs': False, 'cudagraph_specialize_lora': True, 'use_inductor_graph_partition': False, 'pass_config': {'fuse_norm_quant': False, 'fuse_act_quant': False, 'fuse_attn_quant': False, 'eliminate_noops': True, 'enable_sp': False, 'fuse_gemm_comms': False, 'fuse_allreduce_rms': False}, 'max_cudagraph_capture_size': 512, 'dynamic_shapes_config': {'type': , 'evaluate_guards': False}, 'local_cache_dir': None}\n", + "\u001b[0;36m(EngineCore_DP0 pid=1301)\u001b[0;0m INFO 01-11 02:45:08 [parallel_state.py:1203] world_size=1 rank=0 local_rank=0 distributed_init_method=tcp://10.42.33.28:52331 backend=nccl\n", + "\u001b[0;36m(EngineCore_DP0 pid=1301)\u001b[0;0m INFO 01-11 02:45:08 [parallel_state.py:1411] rank 0 in world size 1 is assigned as DP rank 0, PP rank 0, PCP rank 0, TP rank 0, EP rank 0\n", + "\u001b[0;36m(EngineCore_DP0 pid=1301)\u001b[0;0m INFO 01-11 02:45:09 [gpu_model_runner.py:3562] Starting to load model Qwen/Qwen2.5-3B-Instruct...\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[0;36m(EngineCore_DP0 pid=1301)\u001b[0;0m /opt/conda/lib/python3.13/site-packages/tvm_ffi/_optional_torch_c_dlpack.py:174: UserWarning: Failed to JIT torch c dlpack extension, EnvTensorAllocator will not be enabled.\n", + "\u001b[0;36m(EngineCore_DP0 pid=1301)\u001b[0;0m We recommend installing via `pip install torch-c-dlpack-ext`\n", + "\u001b[0;36m(EngineCore_DP0 pid=1301)\u001b[0;0m warnings.warn(\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[0;36m(EngineCore_DP0 pid=1301)\u001b[0;0m INFO 01-11 02:45:11 [cuda.py:351] Using FLASH_ATTN attention backend out of potential backends: ('FLASH_ATTN', 'FLASHINFER', 'TRITON_ATTN', 'FLEX_ATTENTION')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Loading safetensors checkpoint shards: 0% Completed | 0/2 [00:00\n", + "To answer this question, we need to ascertain both the capital of France and its population. The capital of France is Paris. For the population, we need to find that specific information, as the population numbers can fluctuate over time. While specific population numbers can be found readily, we might not directly access them without an external search. To narrow down which LLM to consult, we should consider the most appropriate for handling this factual query: definitively knowing the capital and the population from a reliable source.\n", + "\n", + "\n", + "LLaMA-3.1-8B-Instruct: The capital of France and its current population. \n", + "meta/llama-3.1-8b-instruct\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Processing: 100%|█████████████████████████████████████████████████████| 1/1 [00:01<00:00, 1.16s/it]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "I can assist with the sub-question about the capital of France and its current population.\n", + "\n", + "The capital of France is Paris. \n", + "\n", + "As for the current population of Paris, according to the latest available data (2020 estimates), the population of the city of Paris is approximately 2.1 million people. However, the population of the larger metropolitan area of Paris, which includes the surrounding suburbs and cities, is around 12.2 million people.\n", + "\n", + "Please note that population figures may have changed slightly since the last available estimates. For the most up-to-date information, I recommend consulting a reliable source such as the French National Institute for Statistics and Economic Studies (INSEE) or the World Bank. 139\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Adding requests: 100%|██████████| 1/1 [00:00<00:00, 291.98it/s]\n", + "Processed prompts: 100%|██████████| 1/1 [00:00<00:00, 2.61it/s, est. speed input: 3511.09 toks/s, output: 136.24 toks/s]\n", + "[rank0]:[W111 02:46:11.281526755 ProcessGroupNCCL.cpp:1524] Warning: WARNING: destroy_process_group() was not called before program exit, which can leak resources. For more info, please see https://pytorch.org/docs/stable/distributed.html#shutdown (function operator())\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[Generation 1] Output:\n", + "Paris is the capital of France. As of the latest available estimates, the current population of Paris is approximately 2.1 million people. The total population of the metropolitan area of Paris is around 12.2 million people.\n", + "\n", + "\n", + "################# [Output] ##################\n", + "\n", + "\n", + "\n", + "\n", + "To answer this question, we need to ascertain both the capital of France and its population. The capital of France is Paris. For the population, we need to find that specific information, as the population numbers can fluctuate over time. While specific population numbers can be found readily, we might not directly access them without an external search. To narrow down which LLM to consult, we should consider the most appropriate for handling this factual query: definitively knowing the capital and the population from a reliable source.\n", + "\n", + "\n", + "LLaMA-3.1-8B-Instruct: The capital of France and its current population. \n", + "I can assist with the sub-question about the capital of France and its current population.\n", + "\n", + "The capital of France is Paris. \n", + "\n", + "As for the current population of Paris, according to the latest available data (2020 estimates), the population of the city of Paris is approximately 2.1 million people. However, the population of the larger metropolitan area of Paris, which includes the surrounding suburbs and cities, is around 12.2 million people.\n", + "\n", + "Please note that population figures may have changed slightly since the last available estimates. For the most up-to-date information, I recommend consulting a reliable source such as the French National Institute for Statistics and Economic Studies (INSEE) or the World Bank.\n", + "Paris is the capital of France. As of the latest available estimates, the current population of Paris is approximately 2.1 million people. The total population of the metropolitan area of Paris is around 12.2 million people.\n", + "\n", + "\n", + "\n", + "################# [Output] ##################\n", + "\n", + "\n", + "\n", + "Response:\n", + "\n", + "To answer this question, we need to ascertain both the capital of France and its population. The capital of France is Paris. For the population, we need to find that specific information, as the population numbers can fluctuate over time. While specific population numbers can be found readily, we might not directly access them without an external search. To narrow down which LLM to consult, we should consider the most appropriate for handling this factual query: definitively knowing the capital and the population from a reliable source.\n", + "\n", + "\n", + "LLaMA-3.1-8B-Instruct: The capital of France and its current population. \n", + "I can assist with the sub-question about the capital of France and its current population.\n", + "\n", + "The capital of France is Paris. \n", + "\n", + "As for the current population of Paris, according to the latest available data (2020 estimates), the population of the city of Paris is approximately 2.1 million people. However, the population of the larger metropolitan area of Paris, which includes the surrounding suburbs and cities, is around 12.2 million people.\n", + "\n", + "Please note that population figures may have changed slightly since the last available estimates. For the most up-to-date information, I recommend consulting a reliable source such as the French National Institute for Statistics and Economic Studies (INSEE) or the World Bank.\n", + "Paris is the capital of France. As of the latest available estimates, the current population of Paris is approximately 2.1 million people. The total population of the metropolitan area of Paris is around 12.2 million people.\n", + "\n", + "Token Usage:\n", + " Prompt tokens: 2400\n", + " Completion tokens: 183\n", + " Route tokens: 25.02\n", + " Total tokens: 2608.02\n" + ] + } + ], + "source": [ + "# Example query for RouterR1\n", + "test_query = {\"query\": \"What is the capital of France and what is its population?\"}\n", + "\n", + "print(f\"Query: {test_query['query']}\")\n", + "print(\"=\" * 60)\n", + "\n", + "# Route with details (includes token counts)\n", + "try:\n", + " result = router.route_single(test_query, return_details=True)\n", + " \n", + " print(\"\\nResponse:\")\n", + " print(result[\"response\"])\n", + " print(\"\\nToken Usage:\")\n", + " print(f\" Prompt tokens: {result['prompt_tokens']}\")\n", + " print(f\" Completion tokens: {result['completion_tokens']}\")\n", + " print(f\" Route tokens: {result['route_tokens']}\")\n", + " print(f\" Total tokens: {result['total_tokens']}\")\n", + "except Exception as e:\n", + " print(f\"Error during routing: {e}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Batch Routing" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Processing 3 queries...\n", + "============================================================\n", + "INFO 01-11 02:46:21 [utils.py:253] non-default args: {'dtype': 'float16', 'disable_log_stats': True, 'model': 'Qwen/Qwen2.5-3B-Instruct'}\n", + "INFO 01-11 02:46:22 [model.py:514] Resolved architecture: Qwen2ForCausalLM\n", + "WARNING 01-11 02:46:22 [model.py:2005] Casting torch.bfloat16 to torch.float16.\n", + "INFO 01-11 02:46:22 [model.py:1661] Using max model len 32768\n", + "INFO 01-11 02:46:22 [scheduler.py:230] Chunked prefill is enabled with max_num_batched_tokens=8192.\n", + "\u001b[0;36m(EngineCore_DP0 pid=1509)\u001b[0;0m INFO 01-11 02:46:31 [core.py:93] Initializing a V1 LLM engine (v0.13.0) with config: model='Qwen/Qwen2.5-3B-Instruct', speculative_config=None, tokenizer='Qwen/Qwen2.5-3B-Instruct', skip_tokenizer_init=False, tokenizer_mode=auto, revision=None, tokenizer_revision=None, trust_remote_code=False, dtype=torch.float16, max_seq_len=32768, download_dir=None, load_format=auto, tensor_parallel_size=1, pipeline_parallel_size=1, data_parallel_size=1, disable_custom_all_reduce=False, quantization=None, enforce_eager=False, kv_cache_dtype=auto, device_config=cuda, structured_outputs_config=StructuredOutputsConfig(backend='auto', disable_fallback=False, disable_any_whitespace=False, disable_additional_properties=False, reasoning_parser='', reasoning_parser_plugin='', enable_in_reasoning=False), observability_config=ObservabilityConfig(show_hidden_metrics_for_version=None, otlp_traces_endpoint=None, collect_detailed_traces=None, kv_cache_metrics=False, kv_cache_metrics_sample=0.01, cudagraph_metrics=False, enable_layerwise_nvtx_tracing=False), seed=0, served_model_name=Qwen/Qwen2.5-3B-Instruct, enable_prefix_caching=True, enable_chunked_prefill=True, pooler_config=None, compilation_config={'level': None, 'mode': , 'debug_dump_path': None, 'cache_dir': '', 'compile_cache_save_format': 'binary', 'backend': 'inductor', 'custom_ops': ['none'], 'splitting_ops': ['vllm::unified_attention', 'vllm::unified_attention_with_output', 'vllm::unified_mla_attention', 'vllm::unified_mla_attention_with_output', 'vllm::mamba_mixer2', 'vllm::mamba_mixer', 'vllm::short_conv', 'vllm::linear_attention', 'vllm::plamo2_mamba_mixer', 'vllm::gdn_attention_core', 'vllm::kda_attention', 'vllm::sparse_attn_indexer'], 'compile_mm_encoder': False, 'compile_sizes': [], 'compile_ranges_split_points': [8192], 'inductor_compile_config': {'enable_auto_functionalized_v2': False, 'combo_kernels': True, 'benchmark_combo_kernel': True}, 'inductor_passes': {}, 'cudagraph_mode': , 'cudagraph_num_of_warmups': 1, 'cudagraph_capture_sizes': [1, 2, 4, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128, 136, 144, 152, 160, 168, 176, 184, 192, 200, 208, 216, 224, 232, 240, 248, 256, 272, 288, 304, 320, 336, 352, 368, 384, 400, 416, 432, 448, 464, 480, 496, 512], 'cudagraph_copy_inputs': False, 'cudagraph_specialize_lora': True, 'use_inductor_graph_partition': False, 'pass_config': {'fuse_norm_quant': False, 'fuse_act_quant': False, 'fuse_attn_quant': False, 'eliminate_noops': True, 'enable_sp': False, 'fuse_gemm_comms': False, 'fuse_allreduce_rms': False}, 'max_cudagraph_capture_size': 512, 'dynamic_shapes_config': {'type': , 'evaluate_guards': False}, 'local_cache_dir': None}\n", + "\u001b[0;36m(EngineCore_DP0 pid=1509)\u001b[0;0m INFO 01-11 02:46:32 [parallel_state.py:1203] world_size=1 rank=0 local_rank=0 distributed_init_method=tcp://10.42.33.28:40209 backend=nccl\n", + "\u001b[0;36m(EngineCore_DP0 pid=1509)\u001b[0;0m INFO 01-11 02:46:32 [parallel_state.py:1411] rank 0 in world size 1 is assigned as DP rank 0, PP rank 0, PCP rank 0, TP rank 0, EP rank 0\n", + "\u001b[0;36m(EngineCore_DP0 pid=1509)\u001b[0;0m INFO 01-11 02:46:33 [gpu_model_runner.py:3562] Starting to load model Qwen/Qwen2.5-3B-Instruct...\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[0;36m(EngineCore_DP0 pid=1509)\u001b[0;0m /opt/conda/lib/python3.13/site-packages/tvm_ffi/_optional_torch_c_dlpack.py:174: UserWarning: Failed to JIT torch c dlpack extension, EnvTensorAllocator will not be enabled.\n", + "\u001b[0;36m(EngineCore_DP0 pid=1509)\u001b[0;0m We recommend installing via `pip install torch-c-dlpack-ext`\n", + "\u001b[0;36m(EngineCore_DP0 pid=1509)\u001b[0;0m warnings.warn(\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[0;36m(EngineCore_DP0 pid=1509)\u001b[0;0m INFO 01-11 02:46:35 [cuda.py:351] Using FLASH_ATTN attention backend out of potential backends: ('FLASH_ATTN', 'FLASHINFER', 'TRITON_ATTN', 'FLEX_ATTENTION')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Loading safetensors checkpoint shards: 0% Completed | 0/2 [00:00\n", + "To answer the question \"What is machine learning?\", I need to gather information about the concept and its core principles. I will use Qwen2.5-7B-Instruct, as it’s designed to provide strong explanations and definitions in a variety of fields, including natural language processing and applications like machine learning.\n", + "\n", + " Qwen2.5-7B-Instruct: What is machine learning? \n", + "qwen/qwen2.5-7b-instruct\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Processing: 100%|█████████████████████████████████████████████████████| 1/1 [00:01<00:00, 1.75s/it]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Machine learning is a subset of artificial intelligence that focuses on developing algorithms and statistical models to enable computer systems to improve their performance on a specific task through experience. Essentially, machine learning involves training models on large datasets so they can make predictions or decisions without being explicitly programmed. This field relies heavily on data and statistical methods to identify patterns and make decisions based on those patterns. 73\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Adding requests: 100%|██████████| 1/1 [00:00<00:00, 329.38it/s]\n", + "Processed prompts: 100%|██████████| 1/1 [00:00<00:00, 1.67it/s, est. speed input: 2039.39 toks/s, output: 133.84 toks/s]\n", + "[rank0]:[W111 02:47:36.361510725 ProcessGroupNCCL.cpp:1524] Warning: WARNING: destroy_process_group() was not called before program exit, which can leak resources. For more info, please see https://pytorch.org/docs/stable/distributed.html#shutdown (function operator())\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[Generation 1] Output:\n", + "\n", + "\n", + "Based on the provided information, I can now form a clear answer.\n", + "\n", + " Machine learning is a subset of artificial intelligence that involves developing algorithms and statistical models to enable computer systems to improve their performance on specific tasks through experience. It focuses on training models on large datasets so they can make predictions or decisions based on patterns they identify in the data without being explicitly programmed. \n", + "\n", + "\n", + "################# [Output] ##################\n", + "\n", + "\n", + "\n", + "\n", + "To answer the question \"What is machine learning?\", I need to gather information about the concept and its core principles. I will use Qwen2.5-7B-Instruct, as it’s designed to provide strong explanations and definitions in a variety of fields, including natural language processing and applications like machine learning.\n", + "\n", + " Qwen2.5-7B-Instruct: What is machine learning? \n", + "Machine learning is a subset of artificial intelligence that focuses on developing algorithms and statistical models to enable computer systems to improve their performance on a specific task through experience. Essentially, machine learning involves training models on large datasets so they can make predictions or decisions without being explicitly programmed. This field relies heavily on data and statistical methods to identify patterns and make decisions based on those patterns.\n", + "\n", + "\n", + "Based on the provided information, I can now form a clear answer.\n", + "\n", + " Machine learning is a subset of artificial intelligence that involves developing algorithms and statistical models to enable computer systems to improve their performance on specific tasks through experience. It focuses on training models on large datasets so they can make predictions or decisions based on patterns they identify in the data without being explicitly programmed. \n", + "\n", + "\n", + "\n", + "################# [Output] ##################\n", + "\n", + "\n", + "INFO 01-11 02:47:37 [utils.py:253] non-default args: {'dtype': 'float16', 'disable_log_stats': True, 'model': 'Qwen/Qwen2.5-3B-Instruct'}\n", + "INFO 01-11 02:47:37 [model.py:514] Resolved architecture: Qwen2ForCausalLM\n", + "WARNING 01-11 02:47:37 [model.py:2005] Casting torch.bfloat16 to torch.float16.\n", + "INFO 01-11 02:47:37 [model.py:1661] Using max model len 32768\n", + "INFO 01-11 02:47:37 [scheduler.py:230] Chunked prefill is enabled with max_num_batched_tokens=8192.\n", + "\u001b[0;36m(EngineCore_DP0 pid=1708)\u001b[0;0m INFO 01-11 02:47:47 [core.py:93] Initializing a V1 LLM engine (v0.13.0) with config: model='Qwen/Qwen2.5-3B-Instruct', speculative_config=None, tokenizer='Qwen/Qwen2.5-3B-Instruct', skip_tokenizer_init=False, tokenizer_mode=auto, revision=None, tokenizer_revision=None, trust_remote_code=False, dtype=torch.float16, max_seq_len=32768, download_dir=None, load_format=auto, tensor_parallel_size=1, pipeline_parallel_size=1, data_parallel_size=1, disable_custom_all_reduce=False, quantization=None, enforce_eager=False, kv_cache_dtype=auto, device_config=cuda, structured_outputs_config=StructuredOutputsConfig(backend='auto', disable_fallback=False, disable_any_whitespace=False, disable_additional_properties=False, reasoning_parser='', reasoning_parser_plugin='', enable_in_reasoning=False), observability_config=ObservabilityConfig(show_hidden_metrics_for_version=None, otlp_traces_endpoint=None, collect_detailed_traces=None, kv_cache_metrics=False, kv_cache_metrics_sample=0.01, cudagraph_metrics=False, enable_layerwise_nvtx_tracing=False), seed=0, served_model_name=Qwen/Qwen2.5-3B-Instruct, enable_prefix_caching=True, enable_chunked_prefill=True, pooler_config=None, compilation_config={'level': None, 'mode': , 'debug_dump_path': None, 'cache_dir': '', 'compile_cache_save_format': 'binary', 'backend': 'inductor', 'custom_ops': ['none'], 'splitting_ops': ['vllm::unified_attention', 'vllm::unified_attention_with_output', 'vllm::unified_mla_attention', 'vllm::unified_mla_attention_with_output', 'vllm::mamba_mixer2', 'vllm::mamba_mixer', 'vllm::short_conv', 'vllm::linear_attention', 'vllm::plamo2_mamba_mixer', 'vllm::gdn_attention_core', 'vllm::kda_attention', 'vllm::sparse_attn_indexer'], 'compile_mm_encoder': False, 'compile_sizes': [], 'compile_ranges_split_points': [8192], 'inductor_compile_config': {'enable_auto_functionalized_v2': False, 'combo_kernels': True, 'benchmark_combo_kernel': True}, 'inductor_passes': {}, 'cudagraph_mode': , 'cudagraph_num_of_warmups': 1, 'cudagraph_capture_sizes': [1, 2, 4, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128, 136, 144, 152, 160, 168, 176, 184, 192, 200, 208, 216, 224, 232, 240, 248, 256, 272, 288, 304, 320, 336, 352, 368, 384, 400, 416, 432, 448, 464, 480, 496, 512], 'cudagraph_copy_inputs': False, 'cudagraph_specialize_lora': True, 'use_inductor_graph_partition': False, 'pass_config': {'fuse_norm_quant': False, 'fuse_act_quant': False, 'fuse_attn_quant': False, 'eliminate_noops': True, 'enable_sp': False, 'fuse_gemm_comms': False, 'fuse_allreduce_rms': False}, 'max_cudagraph_capture_size': 512, 'dynamic_shapes_config': {'type': , 'evaluate_guards': False}, 'local_cache_dir': None}\n", + "\u001b[0;36m(EngineCore_DP0 pid=1708)\u001b[0;0m INFO 01-11 02:47:48 [parallel_state.py:1203] world_size=1 rank=0 local_rank=0 distributed_init_method=tcp://10.42.33.28:40169 backend=nccl\n", + "\u001b[0;36m(EngineCore_DP0 pid=1708)\u001b[0;0m INFO 01-11 02:47:48 [parallel_state.py:1411] rank 0 in world size 1 is assigned as DP rank 0, PP rank 0, PCP rank 0, TP rank 0, EP rank 0\n", + "\u001b[0;36m(EngineCore_DP0 pid=1708)\u001b[0;0m INFO 01-11 02:47:49 [gpu_model_runner.py:3562] Starting to load model Qwen/Qwen2.5-3B-Instruct...\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[0;36m(EngineCore_DP0 pid=1708)\u001b[0;0m /opt/conda/lib/python3.13/site-packages/tvm_ffi/_optional_torch_c_dlpack.py:174: UserWarning: Failed to JIT torch c dlpack extension, EnvTensorAllocator will not be enabled.\n", + "\u001b[0;36m(EngineCore_DP0 pid=1708)\u001b[0;0m We recommend installing via `pip install torch-c-dlpack-ext`\n", + "\u001b[0;36m(EngineCore_DP0 pid=1708)\u001b[0;0m warnings.warn(\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[0;36m(EngineCore_DP0 pid=1708)\u001b[0;0m INFO 01-11 02:47:51 [cuda.py:351] Using FLASH_ATTN attention backend out of potential backends: ('FLASH_ATTN', 'FLASHINFER', 'TRITON_ATTN', 'FLEX_ATTENTION')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Loading safetensors checkpoint shards: 0% Completed | 0/2 [00:00\n", + "To explain quantum computing in simple terms, I must first understand the basic concept and key principles without immediately calling any external models, as this is a straightforward explanation rather than a complex task that would benefit significantly from the advanced capabilities of these specialized models. I will proceed directly to answering the question.\n", + "\n", + "\n", + "\n", + "Quantum computing is a type of computing where information is processed using quantum-mechanical phenomena, such as superposition and entanglement. Unlike classical computers that use bits (1s and 0s) to store and process information, quantum computers use quantum bits, or qubits. \n", + "\n", + "A qubit can exist in a state of 0, 1, or a combination of both (superposition). This property allows a quantum computer to perform many calculations at once. Additionally, qubits can become entangled in such a way that the state of one qubit (whether it is 0 or 1) can depend on the state of another, regardless of the distance between them.\n", + "\n", + "This allows quantum computers to solve certain problems, especially ones involving large-scale data, much faster than classical computers. However, quantum computers are still in the experimental phase and are more complex to build and control.\n", + "\n", + "\n", + "\n", + "################# [Output] ##################\n", + "\n", + "\n", + "\n", + "To explain quantum computing in simple terms, I must first understand the basic concept and key principles without immediately calling any external models, as this is a straightforward explanation rather than a complex task that would benefit significantly from the advanced capabilities of these specialized models. I will proceed directly to answering the question.\n", + "\n", + "\n", + "\n", + "Quantum computing is a type of computing where information is processed using quantum-mechanical phenomena, such as superposition and entanglement. Unlike classical computers that use bits (1s and 0s) to store and process information, quantum computers use quantum bits, or qubits. \n", + "\n", + "A qubit can exist in a state of 0, 1, or a combination of both (superposition). This property allows a quantum computer to perform many calculations at once. Additionally, qubits can become entangled in such a way that the state of one qubit (whether it is 0 or 1) can depend on the state of another, regardless of the distance between them.\n", + "\n", + "This allows quantum computers to solve certain problems, especially ones involving large-scale data, much faster than classical computers. However, quantum computers are still in the experimental phase and are more complex to build and control.\n", + "\n", + "\n", + "\n", + "\n", + "################# [Output] ##################\n", + "\n", + "\n", + "INFO 01-11 02:48:49 [utils.py:253] non-default args: {'dtype': 'float16', 'disable_log_stats': True, 'model': 'Qwen/Qwen2.5-3B-Instruct'}\n", + "INFO 01-11 02:48:49 [model.py:514] Resolved architecture: Qwen2ForCausalLM\n", + "WARNING 01-11 02:48:49 [model.py:2005] Casting torch.bfloat16 to torch.float16.\n", + "INFO 01-11 02:48:49 [model.py:1661] Using max model len 32768\n", + "INFO 01-11 02:48:49 [scheduler.py:230] Chunked prefill is enabled with max_num_batched_tokens=8192.\n", + "\u001b[0;36m(EngineCore_DP0 pid=1898)\u001b[0;0m INFO 01-11 02:48:58 [core.py:93] Initializing a V1 LLM engine (v0.13.0) with config: model='Qwen/Qwen2.5-3B-Instruct', speculative_config=None, tokenizer='Qwen/Qwen2.5-3B-Instruct', skip_tokenizer_init=False, tokenizer_mode=auto, revision=None, tokenizer_revision=None, trust_remote_code=False, dtype=torch.float16, max_seq_len=32768, download_dir=None, load_format=auto, tensor_parallel_size=1, pipeline_parallel_size=1, data_parallel_size=1, disable_custom_all_reduce=False, quantization=None, enforce_eager=False, kv_cache_dtype=auto, device_config=cuda, structured_outputs_config=StructuredOutputsConfig(backend='auto', disable_fallback=False, disable_any_whitespace=False, disable_additional_properties=False, reasoning_parser='', reasoning_parser_plugin='', enable_in_reasoning=False), observability_config=ObservabilityConfig(show_hidden_metrics_for_version=None, otlp_traces_endpoint=None, collect_detailed_traces=None, kv_cache_metrics=False, kv_cache_metrics_sample=0.01, cudagraph_metrics=False, enable_layerwise_nvtx_tracing=False), seed=0, served_model_name=Qwen/Qwen2.5-3B-Instruct, enable_prefix_caching=True, enable_chunked_prefill=True, pooler_config=None, compilation_config={'level': None, 'mode': , 'debug_dump_path': None, 'cache_dir': '', 'compile_cache_save_format': 'binary', 'backend': 'inductor', 'custom_ops': ['none'], 'splitting_ops': ['vllm::unified_attention', 'vllm::unified_attention_with_output', 'vllm::unified_mla_attention', 'vllm::unified_mla_attention_with_output', 'vllm::mamba_mixer2', 'vllm::mamba_mixer', 'vllm::short_conv', 'vllm::linear_attention', 'vllm::plamo2_mamba_mixer', 'vllm::gdn_attention_core', 'vllm::kda_attention', 'vllm::sparse_attn_indexer'], 'compile_mm_encoder': False, 'compile_sizes': [], 'compile_ranges_split_points': [8192], 'inductor_compile_config': {'enable_auto_functionalized_v2': False, 'combo_kernels': True, 'benchmark_combo_kernel': True}, 'inductor_passes': {}, 'cudagraph_mode': , 'cudagraph_num_of_warmups': 1, 'cudagraph_capture_sizes': [1, 2, 4, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128, 136, 144, 152, 160, 168, 176, 184, 192, 200, 208, 216, 224, 232, 240, 248, 256, 272, 288, 304, 320, 336, 352, 368, 384, 400, 416, 432, 448, 464, 480, 496, 512], 'cudagraph_copy_inputs': False, 'cudagraph_specialize_lora': True, 'use_inductor_graph_partition': False, 'pass_config': {'fuse_norm_quant': False, 'fuse_act_quant': False, 'fuse_attn_quant': False, 'eliminate_noops': True, 'enable_sp': False, 'fuse_gemm_comms': False, 'fuse_allreduce_rms': False}, 'max_cudagraph_capture_size': 512, 'dynamic_shapes_config': {'type': , 'evaluate_guards': False}, 'local_cache_dir': None}\n", + "\u001b[0;36m(EngineCore_DP0 pid=1898)\u001b[0;0m INFO 01-11 02:48:59 [parallel_state.py:1203] world_size=1 rank=0 local_rank=0 distributed_init_method=tcp://10.42.33.28:57429 backend=nccl\n", + "\u001b[0;36m(EngineCore_DP0 pid=1898)\u001b[0;0m INFO 01-11 02:48:59 [parallel_state.py:1411] rank 0 in world size 1 is assigned as DP rank 0, PP rank 0, PCP rank 0, TP rank 0, EP rank 0\n", + "\u001b[0;36m(EngineCore_DP0 pid=1898)\u001b[0;0m INFO 01-11 02:49:00 [gpu_model_runner.py:3562] Starting to load model Qwen/Qwen2.5-3B-Instruct...\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[0;36m(EngineCore_DP0 pid=1898)\u001b[0;0m /opt/conda/lib/python3.13/site-packages/tvm_ffi/_optional_torch_c_dlpack.py:174: UserWarning: Failed to JIT torch c dlpack extension, EnvTensorAllocator will not be enabled.\n", + "\u001b[0;36m(EngineCore_DP0 pid=1898)\u001b[0;0m We recommend installing via `pip install torch-c-dlpack-ext`\n", + "\u001b[0;36m(EngineCore_DP0 pid=1898)\u001b[0;0m warnings.warn(\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[0;36m(EngineCore_DP0 pid=1898)\u001b[0;0m INFO 01-11 02:49:02 [cuda.py:351] Using FLASH_ATTN attention backend out of potential backends: ('FLASH_ATTN', 'FLASHINFER', 'TRITON_ATTN', 'FLEX_ATTENTION')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Loading safetensors checkpoint shards: 0% Completed | 0/2 [00:00\n", + "To answer the question about how photosynthesis works, I need to gather information about the process and its components. Photosynthesis is a multi-step process that plants and other organisms use to convert light energy into chemical energy, which are then stored in the form of organic compounds. Each of the selected large language models could potentially provide accurate information about this process, but let's consider the capabilities of the models mentioned. Given the need for accuracy and detail, LLaMA-3.1-70B-Instruct or Mistral-7B-Instruct might be particularly fitting due to their strong performance on standard industry benchmarks and tasks in understanding and generating complex texts.\n", + "\n", + "I will now search for reliable information about photosynthesis using LLaMA-3.1-70B-Instruct.\n", + "\n", + "LLaMA-3.1-70B-Instruct: How does photosynthesis work? \n", + "meta/llama-3.1-70b-instruct\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Processing: 100%|█████████████████████████████████████████████████████| 1/1 [00:04<00:00, 4.06s/it]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "I'd be happy to help explain how photosynthesis works.\n", + "\n", + "**Direct Answer:** Photosynthesis is the process by which plants, algae, and some bacteria convert light energy from the sun into chemical energy in the form of organic compounds, such as glucose. This process occurs in specialized organelles called chloroplasts and involves the conversion of carbon dioxide and water into glucose and oxygen.\n", + "\n", + "**Brief Explanation:** Photosynthesis involves two stages: the light-dependent reactions and the light-independent reactions (Calvin cycle). In the light-dependent reactions, light energy is absorbed by pigments such as chlorophyll and converted into ATP and NADPH. In the Calvin cycle, CO2 is fixed into glucose using the ATP and NADPH produced in the light-dependent reactions.\n", + "\n", + "**Helpful Context:** Photosynthesis is essential for life on Earth, as it provides the energy and organic compounds needed to support the food chain. It's estimated that photosynthesis produces between 100 and 150 billion metric tons of glucose annually, which is roughly 10 times the total energy consumption of human civilization.\n", + "\n", + "**Additional Insights:** There are different types of photosynthesis, including C3, C4, and CAM photosynthesis, which vary in their mechanisms and efficiencies. Understanding photosynthesis is crucial for developing sustainable solutions for food production, bioenergy, and climate change mitigation.\n", + "\n", + "I hope this explanation helps the base model understand the basics of photosynthesis! 283\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Adding requests: 100%|██████████| 1/1 [00:00<00:00, 251.76it/s]\n", + "Processed prompts: 100%|██████████| 1/1 [00:01<00:00, 1.72s/it, est. speed input: 892.88 toks/s, output: 136.11 toks/s]\n", + "[rank0]:[W111 02:50:07.049585785 ProcessGroupNCCL.cpp:1524] Warning: WARNING: destroy_process_group() was not called before program exit, which can leak resources. For more info, please see https://pytorch.org/docs/stable/distributed.html#shutdown (function operator())\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[Generation 1] Output:\n", + "\n", + "Photosynthesis is the process by which plants, algae, and some bacteria convert light energy from the sun into chemical energy in the form of organic compounds, such as glucose. This process occurs in specialized organelles called chloroplasts and involves the conversion of carbon dioxide and water into glucose and oxygen. \n", + "\n", + "The process breaks down into two stages: the light-dependent reactions and the Calvin cycle. In the light-dependent reactions, light energy is absorbed by pigments such as chlorophyll and converted into ATP (adenosine triphosphate) and NADPH (nicotinamide adenine dinucleotide phosphate), energy-carrying molecules. In the Calvin cycle, CO2 is fixed into glucose using the ATP and NADPH produced in the light-dependent reactions. This cycle also generates oxygen as a byproduct.\n", + "\n", + "Photosynthesis is essential for life on Earth, as it provides the energy and organic compounds needed to support the food chain. It's estimated that photosynthesis produces between 100 and 150 billion metric tons of glucose annually, which is roughly 10 times the total energy consumption of human civilization.\n", + "\n", + "\n", + "\n", + "################# [Output] ##################\n", + "\n", + "\n", + "\n", + "\n", + "To answer the question about how photosynthesis works, I need to gather information about the process and its components. Photosynthesis is a multi-step process that plants and other organisms use to convert light energy into chemical energy, which are then stored in the form of organic compounds. Each of the selected large language models could potentially provide accurate information about this process, but let's consider the capabilities of the models mentioned. Given the need for accuracy and detail, LLaMA-3.1-70B-Instruct or Mistral-7B-Instruct might be particularly fitting due to their strong performance on standard industry benchmarks and tasks in understanding and generating complex texts.\n", + "\n", + "I will now search for reliable information about photosynthesis using LLaMA-3.1-70B-Instruct.\n", + "\n", + "LLaMA-3.1-70B-Instruct: How does photosynthesis work? \n", + "I'd be happy to help explain how photosynthesis works.\n", + "\n", + "**Direct Answer:** Photosynthesis is the process by which plants, algae, and some bacteria convert light energy from the sun into chemical energy in the form of organic compounds, such as glucose. This process occurs in specialized organelles called chloroplasts and involves the conversion of carbon dioxide and water into glucose and oxygen.\n", + "\n", + "**Brief Explanation:** Photosynthesis involves two stages: the light-dependent reactions and the light-independent reactions (Calvin cycle). In the light-dependent reactions, light energy is absorbed by pigments such as chlorophyll and converted into ATP and NADPH. In the Calvin cycle, CO2 is fixed into glucose using the ATP and NADPH produced in the light-dependent reactions.\n", + "\n", + "**Helpful Context:** Photosynthesis is essential for life on Earth, as it provides the energy and organic compounds needed to support the food chain. It's estimated that photosynthesis produces between 100 and 150 billion metric tons of glucose annually, which is roughly 10 times the total energy consumption of human civilization.\n", + "\n", + "**Additional Insights:** There are different types of photosynthesis, including C3, C4, and CAM photosynthesis, which vary in their mechanisms and efficiencies. Understanding photosynthesis is crucial for developing sustainable solutions for food production, bioenergy, and climate change mitigation.\n", + "\n", + "I hope this explanation helps the base model understand the basics of photosynthesis!\n", + "\n", + "Photosynthesis is the process by which plants, algae, and some bacteria convert light energy from the sun into chemical energy in the form of organic compounds, such as glucose. This process occurs in specialized organelles called chloroplasts and involves the conversion of carbon dioxide and water into glucose and oxygen. \n", + "\n", + "The process breaks down into two stages: the light-dependent reactions and the Calvin cycle. In the light-dependent reactions, light energy is absorbed by pigments such as chlorophyll and converted into ATP (adenosine triphosphate) and NADPH (nicotinamide adenine dinucleotide phosphate), energy-carrying molecules. In the Calvin cycle, CO2 is fixed into glucose using the ATP and NADPH produced in the light-dependent reactions. This cycle also generates oxygen as a byproduct.\n", + "\n", + "Photosynthesis is essential for life on Earth, as it provides the energy and organic compounds needed to support the food chain. It's estimated that photosynthesis produces between 100 and 150 billion metric tons of glucose annually, which is roughly 10 times the total energy consumption of human civilization.\n", + "\n", + "\n", + "\n", + "\n", + "################# [Output] ##################\n", + "\n", + "\n", + "\n", + "1. Query: What is machine learning?...\n", + " Success: True\n", + " Response length: 1386 chars\n", + " Tokens: 2457.9\n", + "\n", + "2. Query: Explain quantum computing in simple terms....\n", + " Success: True\n", + " Response length: 1276 chars\n", + " Tokens: 1304\n", + "\n", + "3. Query: How does photosynthesis work?...\n", + " Success: True\n", + " Response length: 3559 chars\n", + " Tokens: 3256.04\n" + ] + } + ], + "source": [ + "# Batch routing with multiple queries\n", + "batch_queries = [\n", + " {\"query\": \"What is machine learning?\"},\n", + " {\"query\": \"Explain quantum computing in simple terms.\"},\n", + " {\"query\": \"How does photosynthesis work?\"},\n", + "]\n", + "\n", + "print(f\"Processing {len(batch_queries)} queries...\")\n", + "print(\"=\" * 60)\n", + "\n", + "try:\n", + " results = router.route_batch(batch_queries)\n", + " \n", + " for i, result in enumerate(results, 1):\n", + " print(f\"\\n{i}. Query: {result.get('query', 'N/A')[:50]}...\")\n", + " print(f\" Success: {result.get('success', False)}\")\n", + " print(f\" Response length: {len(result.get('response', ''))} chars\")\n", + " print(f\" Tokens: {result.get('prompt_tokens', 0) + result.get('completion_tokens', 0)}\")\n", + "except Exception as e:\n", + " print(f\"Error during batch routing: {e}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. Task-Specific Routing\n", + "\n", + "RouterR1 can format queries for specific tasks (MMLU, GSM8K, etc.)." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Task Query: What is the derivative of x^2?\n", + "Task: math\n", + "Ground Truth: 2x\n", + "============================================================\n", + "INFO 01-11 02:51:56 [utils.py:253] non-default args: {'dtype': 'float16', 'disable_log_stats': True, 'model': 'Qwen/Qwen2.5-3B-Instruct'}\n", + "INFO 01-11 02:51:56 [model.py:514] Resolved architecture: Qwen2ForCausalLM\n", + "WARNING 01-11 02:51:56 [model.py:2005] Casting torch.bfloat16 to torch.float16.\n", + "INFO 01-11 02:51:56 [model.py:1661] Using max model len 32768\n", + "INFO 01-11 02:51:56 [scheduler.py:230] Chunked prefill is enabled with max_num_batched_tokens=8192.\n", + "\u001b[0;36m(EngineCore_DP0 pid=2110)\u001b[0;0m INFO 01-11 02:52:06 [core.py:93] Initializing a V1 LLM engine (v0.13.0) with config: model='Qwen/Qwen2.5-3B-Instruct', speculative_config=None, tokenizer='Qwen/Qwen2.5-3B-Instruct', skip_tokenizer_init=False, tokenizer_mode=auto, revision=None, tokenizer_revision=None, trust_remote_code=False, dtype=torch.float16, max_seq_len=32768, download_dir=None, load_format=auto, tensor_parallel_size=1, pipeline_parallel_size=1, data_parallel_size=1, disable_custom_all_reduce=False, quantization=None, enforce_eager=False, kv_cache_dtype=auto, device_config=cuda, structured_outputs_config=StructuredOutputsConfig(backend='auto', disable_fallback=False, disable_any_whitespace=False, disable_additional_properties=False, reasoning_parser='', reasoning_parser_plugin='', enable_in_reasoning=False), observability_config=ObservabilityConfig(show_hidden_metrics_for_version=None, otlp_traces_endpoint=None, collect_detailed_traces=None, kv_cache_metrics=False, kv_cache_metrics_sample=0.01, cudagraph_metrics=False, enable_layerwise_nvtx_tracing=False), seed=0, served_model_name=Qwen/Qwen2.5-3B-Instruct, enable_prefix_caching=True, enable_chunked_prefill=True, pooler_config=None, compilation_config={'level': None, 'mode': , 'debug_dump_path': None, 'cache_dir': '', 'compile_cache_save_format': 'binary', 'backend': 'inductor', 'custom_ops': ['none'], 'splitting_ops': ['vllm::unified_attention', 'vllm::unified_attention_with_output', 'vllm::unified_mla_attention', 'vllm::unified_mla_attention_with_output', 'vllm::mamba_mixer2', 'vllm::mamba_mixer', 'vllm::short_conv', 'vllm::linear_attention', 'vllm::plamo2_mamba_mixer', 'vllm::gdn_attention_core', 'vllm::kda_attention', 'vllm::sparse_attn_indexer'], 'compile_mm_encoder': False, 'compile_sizes': [], 'compile_ranges_split_points': [8192], 'inductor_compile_config': {'enable_auto_functionalized_v2': False, 'combo_kernels': True, 'benchmark_combo_kernel': True}, 'inductor_passes': {}, 'cudagraph_mode': , 'cudagraph_num_of_warmups': 1, 'cudagraph_capture_sizes': [1, 2, 4, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128, 136, 144, 152, 160, 168, 176, 184, 192, 200, 208, 216, 224, 232, 240, 248, 256, 272, 288, 304, 320, 336, 352, 368, 384, 400, 416, 432, 448, 464, 480, 496, 512], 'cudagraph_copy_inputs': False, 'cudagraph_specialize_lora': True, 'use_inductor_graph_partition': False, 'pass_config': {'fuse_norm_quant': False, 'fuse_act_quant': False, 'fuse_attn_quant': False, 'eliminate_noops': True, 'enable_sp': False, 'fuse_gemm_comms': False, 'fuse_allreduce_rms': False}, 'max_cudagraph_capture_size': 512, 'dynamic_shapes_config': {'type': , 'evaluate_guards': False}, 'local_cache_dir': None}\n", + "\u001b[0;36m(EngineCore_DP0 pid=2110)\u001b[0;0m INFO 01-11 02:52:06 [parallel_state.py:1203] world_size=1 rank=0 local_rank=0 distributed_init_method=tcp://10.42.33.28:49843 backend=nccl\n", + "\u001b[0;36m(EngineCore_DP0 pid=2110)\u001b[0;0m INFO 01-11 02:52:06 [parallel_state.py:1411] rank 0 in world size 1 is assigned as DP rank 0, PP rank 0, PCP rank 0, TP rank 0, EP rank 0\n", + "\u001b[0;36m(EngineCore_DP0 pid=2110)\u001b[0;0m INFO 01-11 02:52:07 [gpu_model_runner.py:3562] Starting to load model Qwen/Qwen2.5-3B-Instruct...\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[0;36m(EngineCore_DP0 pid=2110)\u001b[0;0m /opt/conda/lib/python3.13/site-packages/tvm_ffi/_optional_torch_c_dlpack.py:174: UserWarning: Failed to JIT torch c dlpack extension, EnvTensorAllocator will not be enabled.\n", + "\u001b[0;36m(EngineCore_DP0 pid=2110)\u001b[0;0m We recommend installing via `pip install torch-c-dlpack-ext`\n", + "\u001b[0;36m(EngineCore_DP0 pid=2110)\u001b[0;0m warnings.warn(\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[0;36m(EngineCore_DP0 pid=2110)\u001b[0;0m INFO 01-11 02:52:10 [cuda.py:351] Using FLASH_ATTN attention backend out of potential backends: ('FLASH_ATTN', 'FLASHINFER', 'TRITON_ATTN', 'FLEX_ATTENTION')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Loading safetensors checkpoint shards: 0% Completed | 0/2 [00:00\n", + "The question asks for the derivative of \\(x^2\\). To provide a precise and educational response on the topic, using a specialized model like LLaMA-3.1-8B-Instruct, would be the most appropriate choice due to its strong reasoning capabilities and performance in various tasks including handling mathematical derivations.\n", + "\n", + "LLaMA-3.1-8B-Instruct: What is the derivative of \\(x^2\\) \n", + "meta/llama-3.1-8b-instruct\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Processing: 100%|█████████████████████████████████████████████████████| 1/1 [00:00<00:00, 1.01it/s]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "To find the derivative of \\(x^2\\), we can use the power rule of differentiation. The power rule states that if we have a function of the form \\(x^n\\), its derivative is \\(nx^{n-1}\\).\n", + "\n", + "Applying this rule to \\(x^2\\), we get:\n", + "\n", + "- The exponent \\(n\\) is 2.\n", + "- The derivative of \\(x^2\\) is therefore \\(2x^{2-1}\\).\n", + "- Simplifying the exponent, we get \\(2x^1\\).\n", + "- Since \\(x^1\\) is equivalent to \\(x\\), the derivative of \\(x^2\\) is \\(2x\\).\n", + "\n", + "Therefore, the derivative of \\(x^2\\) is \\(2x\\). 153\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Adding requests: 100%|██████████| 1/1 [00:00<00:00, 325.34it/s]\n", + "Processed prompts: 100%|██████████| 1/1 [00:00<00:00, 10.84it/s, est. speed input: 14294.29 toks/s, output: 130.92 toks/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[Generation 1] Output:\n", + "\n", + "\n", + " 2x \n", + "\n", + "\n", + "################# [Output] ##################\n", + "\n", + "\n", + "\n", + "\n", + "The question asks for the derivative of \\(x^2\\). To provide a precise and educational response on the topic, using a specialized model like LLaMA-3.1-8B-Instruct, would be the most appropriate choice due to its strong reasoning capabilities and performance in various tasks including handling mathematical derivations.\n", + "\n", + "LLaMA-3.1-8B-Instruct: What is the derivative of \\(x^2\\) \n", + "To find the derivative of \\(x^2\\), we can use the power rule of differentiation. The power rule states that if we have a function of the form \\(x^n\\), its derivative is \\(nx^{n-1}\\).\n", + "\n", + "Applying this rule to \\(x^2\\), we get:\n", + "\n", + "- The exponent \\(n\\) is 2.\n", + "- The derivative of \\(x^2\\) is therefore \\(2x^{2-1}\\).\n", + "- Simplifying the exponent, we get \\(2x^1\\).\n", + "- Since \\(x^1\\) is equivalent to \\(x\\), the derivative of \\(x^2\\) is \\(2x\\).\n", + "\n", + "Therefore, the derivative of \\(x^2\\) is \\(2x\\).\n", + "\n", + "\n", + " 2x \n", + "\n", + "\n", + "\n", + "################# [Output] ##################\n", + "\n", + "\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n", + "[rank0]:[W111 02:53:09.710784467 ProcessGroupNCCL.cpp:1524] Warning: WARNING: destroy_process_group() was not called before program exit, which can leak resources. For more info, please see https://pytorch.org/docs/stable/distributed.html#shutdown (function operator())\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Response: \n", + "The question asks for the derivative of \\(x^2\\). To provide a precise and educational response on the topic, using a specialized model like LLaMA-3.1-8B-Instruct, would be the most appropriate...\n", + "Task Performance: 0.00\n" + ] + } + ], + "source": [ + "# Task-specific routing with ground truth for evaluation\n", + "task_query = {\n", + " \"query\": \"What is the derivative of x^2?\",\n", + " \"task_name\": \"math\",\n", + " \"ground_truth\": \"2x\"\n", + "}\n", + "\n", + "print(f\"Task Query: {task_query['query']}\")\n", + "print(f\"Task: {task_query['task_name']}\")\n", + "print(f\"Ground Truth: {task_query['ground_truth']}\")\n", + "print(\"=\" * 60)\n", + "\n", + "try:\n", + " results = router.route_batch([task_query], task_name=\"math\")\n", + " result = results[0]\n", + " \n", + " print(f\"\\nResponse: {result.get('response', 'N/A')[:200]}...\")\n", + " if 'task_performance' in result:\n", + " print(f\"Task Performance: {result['task_performance']:.2f}\")\n", + "except Exception as e:\n", + " print(f\"Error: {e}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7. File-Based Inference\n", + "\n", + "Load queries from a file and save results." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loaded 706 queries from: LLMRouter/data/example_data/query_data/default_query_test.jsonl\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "INFO 01-11 02:53:42 [utils.py:253] non-default args: {'dtype': 'float16', 'disable_log_stats': True, 'model': 'Qwen/Qwen2.5-3B-Instruct'}\n", + "INFO 01-11 02:53:42 [model.py:514] Resolved architecture: Qwen2ForCausalLM\n", + "WARNING 01-11 02:53:42 [model.py:2005] Casting torch.bfloat16 to torch.float16.\n", + "INFO 01-11 02:53:42 [model.py:1661] Using max model len 32768\n", + "INFO 01-11 02:53:42 [scheduler.py:230] Chunked prefill is enabled with max_num_batched_tokens=8192.\n", + "\u001b[0;36m(EngineCore_DP0 pid=2319)\u001b[0;0m INFO 01-11 02:53:51 [core.py:93] Initializing a V1 LLM engine (v0.13.0) with config: model='Qwen/Qwen2.5-3B-Instruct', speculative_config=None, tokenizer='Qwen/Qwen2.5-3B-Instruct', skip_tokenizer_init=False, tokenizer_mode=auto, revision=None, tokenizer_revision=None, trust_remote_code=False, dtype=torch.float16, max_seq_len=32768, download_dir=None, load_format=auto, tensor_parallel_size=1, pipeline_parallel_size=1, data_parallel_size=1, disable_custom_all_reduce=False, quantization=None, enforce_eager=False, kv_cache_dtype=auto, device_config=cuda, structured_outputs_config=StructuredOutputsConfig(backend='auto', disable_fallback=False, disable_any_whitespace=False, disable_additional_properties=False, reasoning_parser='', reasoning_parser_plugin='', enable_in_reasoning=False), observability_config=ObservabilityConfig(show_hidden_metrics_for_version=None, otlp_traces_endpoint=None, collect_detailed_traces=None, kv_cache_metrics=False, kv_cache_metrics_sample=0.01, cudagraph_metrics=False, enable_layerwise_nvtx_tracing=False), seed=0, served_model_name=Qwen/Qwen2.5-3B-Instruct, enable_prefix_caching=True, enable_chunked_prefill=True, pooler_config=None, compilation_config={'level': None, 'mode': , 'debug_dump_path': None, 'cache_dir': '', 'compile_cache_save_format': 'binary', 'backend': 'inductor', 'custom_ops': ['none'], 'splitting_ops': ['vllm::unified_attention', 'vllm::unified_attention_with_output', 'vllm::unified_mla_attention', 'vllm::unified_mla_attention_with_output', 'vllm::mamba_mixer2', 'vllm::mamba_mixer', 'vllm::short_conv', 'vllm::linear_attention', 'vllm::plamo2_mamba_mixer', 'vllm::gdn_attention_core', 'vllm::kda_attention', 'vllm::sparse_attn_indexer'], 'compile_mm_encoder': False, 'compile_sizes': [], 'compile_ranges_split_points': [8192], 'inductor_compile_config': {'enable_auto_functionalized_v2': False, 'combo_kernels': True, 'benchmark_combo_kernel': True}, 'inductor_passes': {}, 'cudagraph_mode': , 'cudagraph_num_of_warmups': 1, 'cudagraph_capture_sizes': [1, 2, 4, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128, 136, 144, 152, 160, 168, 176, 184, 192, 200, 208, 216, 224, 232, 240, 248, 256, 272, 288, 304, 320, 336, 352, 368, 384, 400, 416, 432, 448, 464, 480, 496, 512], 'cudagraph_copy_inputs': False, 'cudagraph_specialize_lora': True, 'use_inductor_graph_partition': False, 'pass_config': {'fuse_norm_quant': False, 'fuse_act_quant': False, 'fuse_attn_quant': False, 'eliminate_noops': True, 'enable_sp': False, 'fuse_gemm_comms': False, 'fuse_allreduce_rms': False}, 'max_cudagraph_capture_size': 512, 'dynamic_shapes_config': {'type': , 'evaluate_guards': False}, 'local_cache_dir': None}\n", + "\u001b[0;36m(EngineCore_DP0 pid=2319)\u001b[0;0m INFO 01-11 02:53:52 [parallel_state.py:1203] world_size=1 rank=0 local_rank=0 distributed_init_method=tcp://10.42.33.28:46121 backend=nccl\n", + "\u001b[0;36m(EngineCore_DP0 pid=2319)\u001b[0;0m INFO 01-11 02:53:52 [parallel_state.py:1411] rank 0 in world size 1 is assigned as DP rank 0, PP rank 0, PCP rank 0, TP rank 0, EP rank 0\n", + "\u001b[0;36m(EngineCore_DP0 pid=2319)\u001b[0;0m INFO 01-11 02:53:53 [gpu_model_runner.py:3562] Starting to load model Qwen/Qwen2.5-3B-Instruct...\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[0;36m(EngineCore_DP0 pid=2319)\u001b[0;0m /opt/conda/lib/python3.13/site-packages/tvm_ffi/_optional_torch_c_dlpack.py:174: UserWarning: Failed to JIT torch c dlpack extension, EnvTensorAllocator will not be enabled.\n", + "\u001b[0;36m(EngineCore_DP0 pid=2319)\u001b[0;0m We recommend installing via `pip install torch-c-dlpack-ext`\n", + "\u001b[0;36m(EngineCore_DP0 pid=2319)\u001b[0;0m warnings.warn(\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[0;36m(EngineCore_DP0 pid=2319)\u001b[0;0m INFO 01-11 02:53:55 [cuda.py:351] Using FLASH_ATTN attention backend out of potential backends: ('FLASH_ATTN', 'FLASHINFER', 'TRITON_ATTN', 'FLEX_ATTENTION')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Loading safetensors checkpoint shards: 0% Completed | 0/2 [00:00 \n", + "Given the complexity of these clues, we need to connect the dots and narrow down the locations for each person based on the spatial placement and the details provided. Clue 10 establishes a specific position regarding the pianist and the person who likes green. Clue 11 places someone living somewhere to the left of the person who had pizza. Clue 12 places the person who has a bouquet of lilies to the right of the violinist and uses Clue 9 to place the person who has a bouquet of lilies to the right of a more specific condition regarding the pianist and the person who likes white. Clues 4 and 3 provide information about the positions of the running shoes and stew eater compared to the person who ate spaghetti. Clues 8 and 9 together help identify a specific position for the guitarist. Clue 7 and Clue 6 provide information around the house where the person is wearing loafers, which is in the fourth house. Clue 5 further details the positions of the pizza eater and the person who has a bouquet of daffodils. This leaves us focusing on Clue 2 and Clue 1 to establish the positions of the person who has stew versus the pizza eater.\n", + "\n", + "To better understand the spatial relationship, let's organize clues and positions we've deduced thus far:\n", + "\n", + "Position 1: The guitar player\n", + "Position 2: The violinist\n", + "Position 3: The pianist\n", + "Position 4: The runner\n", + "\n", + "Position 1: The person who likes white is to the right of the person who wears running shoes (Clue 2).\n", + "Position 2: The pianist is somewhere to the right of the person who likes green (Clue 10).\n", + "Position 3: The pianist is somewhere to the left of the guitarist (Clue 8).\n", + "Position 4: The loafers wearer is the fourth person.\n", + "\n", + "From the Stoichiometry of the remaining positions and Clue 2, we can formulate a deduction and use Clue 11 and 12 to eliminate or narrow down possibilities, leaving us to solve it piece-by-piece. Let’s use a LLM-Name:Your-Query \n", + "LLM Name Error\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Processing: 100%|██████████████████████████████████████████████████| 1/1 [00:00<00:00, 16710.37it/s]\n", + "Adding requests: 100%|██████████| 1/1 [00:00<00:00, 218.12it/s]\n", + "Processed prompts: 100%|██████████| 1/1 [00:03<00:00, 3.25s/it, est. speed input: 622.74 toks/s, output: 137.77 toks/s]\n", + "[rank0]:[W111 02:55:01.375684011 ProcessGroupNCCL.cpp:1524] Warning: WARNING: destroy_process_group() was not called before program exit, which can leak resources. For more info, please see https://pytorch.org/docs/stable/distributed.html#shutdown (function operator())\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[Generation 1] Output:\n", + "\n", + "It seems there was an oversight. Let's tackle this logically using the provided clues, symbolize the positions and remove contradictions step by step without requiring external model calls that might further obscure the manual deduction process.\n", + "\n", + "Based on the above deductions, we can assign positions logically. Let's follow Clue 1 to establish the relationship between the sandals wearer and the person with stew. Clue 3 rules out the first house, so the guitarist must live in the first house (since the rose bouquet holder can't be in the first house). Clue 10 says the pianist must be to the right of the person who likes green. Clue 9 says the pianist and person who likes green must have two houses between them, so they can't be in Houses 1 and 2. Let's try to deduce the positions systematically.\n", + "\n", + "Given Clue 3, the person who likes white must be somewhere to the right of the person who wears running shoes. So, let's use Clue 11 and 12 to fill in the remaining positions while reconciling all clues.\n", + "\n", + "Let's deduce the positions:\n", + "\n", + "House 1: Guitarist\n", + "House 2: Violinist\n", + "House 3: Pianist\n", + "House 4: Person with loafers\n", + "Substituting the other clues fitting:\n", + "\n", + "Person with stew (House 2) and the person wearing the sandals belong to houses with logical remaining sites. \n", + "\n", + "Let's conclude step-by-step assigning the pieces logically:\n", + "House 2 sits with the violinist, House 3 for the pianist, and the loafers in House 4 placements align correct distancing.\n", + "Given:\n", + "- One person likes white lives to right of Strand runners.\n", + "- The green lady places 2 houses before the pianist.\n", + "- The white lady must be located right of the salad.\n", + "\n", + "Thus:\n", + "\n", + "House 3: Violinist\n", + "House 4: Guitarist (loafers)\n", + "House 2: Pianist\n", + "House 1: Violinist (sandals on right)\n", + "\n", + "Finally, by recalling all clues logically reassessed, we see the last person with loafers which completes the sequence directly:\n", + "\n", + "4\n", + "\n", + "\n", + "################# [Output] ##################\n", + "\n", + "\n", + "\n", + "To determine the house where the person who likes white lives, let's use the given clues and step-by-step reasoning. \n", + "\n", + " \n", + "Given the complexity of these clues, we need to connect the dots and narrow down the locations for each person based on the spatial placement and the details provided. Clue 10 establishes a specific position regarding the pianist and the person who likes green. Clue 11 places someone living somewhere to the left of the person who had pizza. Clue 12 places the person who has a bouquet of lilies to the right of the violinist and uses Clue 9 to place the person who has a bouquet of lilies to the right of a more specific condition regarding the pianist and the person who likes white. Clues 4 and 3 provide information about the positions of the running shoes and stew eater compared to the person who ate spaghetti. Clues 8 and 9 together help identify a specific position for the guitarist. Clue 7 and Clue 6 provide information around the house where the person is wearing loafers, which is in the fourth house. Clue 5 further details the positions of the pizza eater and the person who has a bouquet of daffodils. This leaves us focusing on Clue 2 and Clue 1 to establish the positions of the person who has stew versus the pizza eater.\n", + "\n", + "To better understand the spatial relationship, let's organize clues and positions we've deduced thus far:\n", + "\n", + "Position 1: The guitar player\n", + "Position 2: The violinist\n", + "Position 3: The pianist\n", + "Position 4: The runner\n", + "\n", + "Position 1: The person who likes white is to the right of the person who wears running shoes (Clue 2).\n", + "Position 2: The pianist is somewhere to the right of the person who likes green (Clue 10).\n", + "Position 3: The pianist is somewhere to the left of the guitarist (Clue 8).\n", + "Position 4: The loafers wearer is the fourth person.\n", + "\n", + "From the Stoichiometry of the remaining positions and Clue 2, we can formulate a deduction and use Clue 11 and 12 to eliminate or narrow down possibilities, leaving us to solve it piece-by-piece. Let’s use a LLM-Name:Your-Query \n", + "LLM Name Error\n", + "\n", + "It seems there was an oversight. Let's tackle this logically using the provided clues, symbolize the positions and remove contradictions step by step without requiring external model calls that might further obscure the manual deduction process.\n", + "\n", + "Based on the above deductions, we can assign positions logically. Let's follow Clue 1 to establish the relationship between the sandals wearer and the person with stew. Clue 3 rules out the first house, so the guitarist must live in the first house (since the rose bouquet holder can't be in the first house). Clue 10 says the pianist must be to the right of the person who likes green. Clue 9 says the pianist and person who likes green must have two houses between them, so they can't be in Houses 1 and 2. Let's try to deduce the positions systematically.\n", + "\n", + "Given Clue 3, the person who likes white must be somewhere to the right of the person who wears running shoes. So, let's use Clue 11 and 12 to fill in the remaining positions while reconciling all clues.\n", + "\n", + "Let's deduce the positions:\n", + "\n", + "House 1: Guitarist\n", + "House 2: Violinist\n", + "House 3: Pianist\n", + "House 4: Person with loafers\n", + "Substituting the other clues fitting:\n", + "\n", + "Person with stew (House 2) and the person wearing the sandals belong to houses with logical remaining sites. \n", + "\n", + "Let's conclude step-by-step assigning the pieces logically:\n", + "House 2 sits with the violinist, House 3 for the pianist, and the loafers in House 4 placements align correct distancing.\n", + "Given:\n", + "- One person likes white lives to right of Strand runners.\n", + "- The green lady places 2 houses before the pianist.\n", + "- The white lady must be located right of the salad.\n", + "\n", + "Thus:\n", + "\n", + "House 3: Violinist\n", + "House 4: Guitarist (loafers)\n", + "House 2: Pianist\n", + "House 1: Violinist (sandals on right)\n", + "\n", + "Finally, by recalling all clues logically reassessed, we see the last person with loafers which completes the sequence directly:\n", + "\n", + "4\n", + "\n", + "\n", + "\n", + "################# [Output] ##################\n", + "\n", + "\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "INFO 01-11 02:55:03 [utils.py:253] non-default args: {'dtype': 'float16', 'disable_log_stats': True, 'model': 'Qwen/Qwen2.5-3B-Instruct'}\n", + "INFO 01-11 02:55:04 [model.py:514] Resolved architecture: Qwen2ForCausalLM\n", + "WARNING 01-11 02:55:04 [model.py:2005] Casting torch.bfloat16 to torch.float16.\n", + "INFO 01-11 02:55:04 [model.py:1661] Using max model len 32768\n", + "INFO 01-11 02:55:04 [scheduler.py:230] Chunked prefill is enabled with max_num_batched_tokens=8192.\n", + "\u001b[0;36m(EngineCore_DP0 pid=2522)\u001b[0;0m INFO 01-11 02:55:13 [core.py:93] Initializing a V1 LLM engine (v0.13.0) with config: model='Qwen/Qwen2.5-3B-Instruct', speculative_config=None, tokenizer='Qwen/Qwen2.5-3B-Instruct', skip_tokenizer_init=False, tokenizer_mode=auto, revision=None, tokenizer_revision=None, trust_remote_code=False, dtype=torch.float16, max_seq_len=32768, download_dir=None, load_format=auto, tensor_parallel_size=1, pipeline_parallel_size=1, data_parallel_size=1, disable_custom_all_reduce=False, quantization=None, enforce_eager=False, kv_cache_dtype=auto, device_config=cuda, structured_outputs_config=StructuredOutputsConfig(backend='auto', disable_fallback=False, disable_any_whitespace=False, disable_additional_properties=False, reasoning_parser='', reasoning_parser_plugin='', enable_in_reasoning=False), observability_config=ObservabilityConfig(show_hidden_metrics_for_version=None, otlp_traces_endpoint=None, collect_detailed_traces=None, kv_cache_metrics=False, kv_cache_metrics_sample=0.01, cudagraph_metrics=False, enable_layerwise_nvtx_tracing=False), seed=0, served_model_name=Qwen/Qwen2.5-3B-Instruct, enable_prefix_caching=True, enable_chunked_prefill=True, pooler_config=None, compilation_config={'level': None, 'mode': , 'debug_dump_path': None, 'cache_dir': '', 'compile_cache_save_format': 'binary', 'backend': 'inductor', 'custom_ops': ['none'], 'splitting_ops': ['vllm::unified_attention', 'vllm::unified_attention_with_output', 'vllm::unified_mla_attention', 'vllm::unified_mla_attention_with_output', 'vllm::mamba_mixer2', 'vllm::mamba_mixer', 'vllm::short_conv', 'vllm::linear_attention', 'vllm::plamo2_mamba_mixer', 'vllm::gdn_attention_core', 'vllm::kda_attention', 'vllm::sparse_attn_indexer'], 'compile_mm_encoder': False, 'compile_sizes': [], 'compile_ranges_split_points': [8192], 'inductor_compile_config': {'enable_auto_functionalized_v2': False, 'combo_kernels': True, 'benchmark_combo_kernel': True}, 'inductor_passes': {}, 'cudagraph_mode': , 'cudagraph_num_of_warmups': 1, 'cudagraph_capture_sizes': [1, 2, 4, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128, 136, 144, 152, 160, 168, 176, 184, 192, 200, 208, 216, 224, 232, 240, 248, 256, 272, 288, 304, 320, 336, 352, 368, 384, 400, 416, 432, 448, 464, 480, 496, 512], 'cudagraph_copy_inputs': False, 'cudagraph_specialize_lora': True, 'use_inductor_graph_partition': False, 'pass_config': {'fuse_norm_quant': False, 'fuse_act_quant': False, 'fuse_attn_quant': False, 'eliminate_noops': True, 'enable_sp': False, 'fuse_gemm_comms': False, 'fuse_allreduce_rms': False}, 'max_cudagraph_capture_size': 512, 'dynamic_shapes_config': {'type': , 'evaluate_guards': False}, 'local_cache_dir': None}\n", + "\u001b[0;36m(EngineCore_DP0 pid=2522)\u001b[0;0m INFO 01-11 02:55:14 [parallel_state.py:1203] world_size=1 rank=0 local_rank=0 distributed_init_method=tcp://10.42.33.28:33809 backend=nccl\n", + "\u001b[0;36m(EngineCore_DP0 pid=2522)\u001b[0;0m INFO 01-11 02:55:14 [parallel_state.py:1411] rank 0 in world size 1 is assigned as DP rank 0, PP rank 0, PCP rank 0, TP rank 0, EP rank 0\n", + "\u001b[0;36m(EngineCore_DP0 pid=2522)\u001b[0;0m INFO 01-11 02:55:15 [gpu_model_runner.py:3562] Starting to load model Qwen/Qwen2.5-3B-Instruct...\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[0;36m(EngineCore_DP0 pid=2522)\u001b[0;0m /opt/conda/lib/python3.13/site-packages/tvm_ffi/_optional_torch_c_dlpack.py:174: UserWarning: Failed to JIT torch c dlpack extension, EnvTensorAllocator will not be enabled.\n", + "\u001b[0;36m(EngineCore_DP0 pid=2522)\u001b[0;0m We recommend installing via `pip install torch-c-dlpack-ext`\n", + "\u001b[0;36m(EngineCore_DP0 pid=2522)\u001b[0;0m warnings.warn(\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[0;36m(EngineCore_DP0 pid=2522)\u001b[0;0m INFO 01-11 02:55:17 [cuda.py:351] Using FLASH_ATTN attention backend out of potential backends: ('FLASH_ATTN', 'FLASHINFER', 'TRITON_ATTN', 'FLEX_ATTENTION')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Loading safetensors checkpoint shards: 0% Completed | 0/2 [00:00 Given the clues, we need to determine the placement of the houses according to the people's characteristics. We know the following:\n", + "\n", + "1. Green lives to the left of the history book buff.\n", + "2. Romance lives directly to the left of the history book buff.\n", + "3. Pizza eater does not live in the first house.\n", + "4. Yellow lives in the first house.\n", + "5. Fried rice eater lives to the right of the romance book lover.\n", + "\n", + "Start with the house numbers \\(1, 2, 3\\) and assign the characteristics sequentially based on the clues.\n", + "\n", + "First, from clue 4, \"Yellow lives in the first house,\" so we place \"Yellow\" in house \\(1\\).\n", + "\n", + "Now we know:\n", + "- Yellow lives in house \\(1\\).\n", + "\n", + "The remaining characteristics are the Romance book lover and the history book buff. Clue 2 states that \"Romance lives directly to the left of the history book buff,\" and from clue 1, we know that \"Green lives to the left of the history book buff.\"\n", + "\n", + "That means:\n", + "- Green lives in house \\(1\\) (Note: We still have the Romance book lover to place, and they need to be to the left of Green).\n", + "\n", + "Since Yellow (in house \\(1\\)) can’t be part of the formation where Green (to the left of History), and we already placed Yellow, there must be another option for Green and the Romance book lover:\n", + "\n", + "- House 1 is now occupied by either Romance or Yellow.\n", + "- Romance is to the left of History, so History could either be in houses \\(3\\) or another house where Green lives to the left of History.\n", + "\n", + "Now, let's place Green. If Green hypothetically is in house \\(2\\):\n", + "\n", + "- This leaves the possibility and placement:\n", + " - Green in house \\(2\\)\n", + " - Romance needs to be in house \\(1\\), contradicts the Serial Position: Romance is to the right of Green.\n", + "\n", + "Let's continue testing outcomes:\n", + "\n", + "- Upgrade to thinking, if Green is not in house 2, and ideally place the individual histories in logical alignment, say placing Green in house \\(2\\) leads others to:\n", + "\n", + "Now we know:\n", + "- Green in house \\(2\\)\n", + "- Romance needs to be in the other house next, to agree with:\n", + " - Say if romance in house \\(3\\),\n", + "\n", + " - History comes next, symbolize putting it logically:\n", + " - History: House left red so\n", + " - Goes last, to requirement visually correct existing sequence:\n", + "\n", + "Grouping correctly Houses 1, 2, and three attach meticulously easily find correct.\n", + "\n", + "Thus, yellow:\n", + "Final Allocation Manually resolving Yellow indeed lives in House number: \n", + "\n", + "1.\n", + "\n", + "Final answer, ensuring all fits requirements.1\n", + "\n", + "\n", + "################# [Output] ##################\n", + "\n", + "\n", + "To determine the house where the person who likes yellow lives, let's use the given clues and step by step analyze each one.\n", + "\n", + " Given the clues, we need to determine the placement of the houses according to the people's characteristics. We know the following:\n", + "\n", + "1. Green lives to the left of the history book buff.\n", + "2. Romance lives directly to the left of the history book buff.\n", + "3. Pizza eater does not live in the first house.\n", + "4. Yellow lives in the first house.\n", + "5. Fried rice eater lives to the right of the romance book lover.\n", + "\n", + "Start with the house numbers \\(1, 2, 3\\) and assign the characteristics sequentially based on the clues.\n", + "\n", + "First, from clue 4, \"Yellow lives in the first house,\" so we place \"Yellow\" in house \\(1\\).\n", + "\n", + "Now we know:\n", + "- Yellow lives in house \\(1\\).\n", + "\n", + "The remaining characteristics are the Romance book lover and the history book buff. Clue 2 states that \"Romance lives directly to the left of the history book buff,\" and from clue 1, we know that \"Green lives to the left of the history book buff.\"\n", + "\n", + "That means:\n", + "- Green lives in house \\(1\\) (Note: We still have the Romance book lover to place, and they need to be to the left of Green).\n", + "\n", + "Since Yellow (in house \\(1\\)) can’t be part of the formation where Green (to the left of History), and we already placed Yellow, there must be another option for Green and the Romance book lover:\n", + "\n", + "- House 1 is now occupied by either Romance or Yellow.\n", + "- Romance is to the left of History, so History could either be in houses \\(3\\) or another house where Green lives to the left of History.\n", + "\n", + "Now, let's place Green. If Green hypothetically is in house \\(2\\):\n", + "\n", + "- This leaves the possibility and placement:\n", + " - Green in house \\(2\\)\n", + " - Romance needs to be in house \\(1\\), contradicts the Serial Position: Romance is to the right of Green.\n", + "\n", + "Let's continue testing outcomes:\n", + "\n", + "- Upgrade to thinking, if Green is not in house 2, and ideally place the individual histories in logical alignment, say placing Green in house \\(2\\) leads others to:\n", + "\n", + "Now we know:\n", + "- Green in house \\(2\\)\n", + "- Romance needs to be in the other house next, to agree with:\n", + " - Say if romance in house \\(3\\),\n", + "\n", + " - History comes next, symbolize putting it logically:\n", + " - History: House left red so\n", + " - Goes last, to requirement visually correct existing sequence:\n", + "\n", + "Grouping correctly Houses 1, 2, and three attach meticulously easily find correct.\n", + "\n", + "Thus, yellow:\n", + "Final Allocation Manually resolving Yellow indeed lives in House number: \n", + "\n", + "1.\n", + "\n", + "Final answer, ensuring all fits requirements.1\n", + "\n", + "\n", + "\n", + "################# [Output] ##################\n", + "\n", + "\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "INFO 01-11 02:56:29 [utils.py:253] non-default args: {'dtype': 'float16', 'disable_log_stats': True, 'model': 'Qwen/Qwen2.5-3B-Instruct'}\n", + "INFO 01-11 02:56:29 [model.py:514] Resolved architecture: Qwen2ForCausalLM\n", + "WARNING 01-11 02:56:29 [model.py:2005] Casting torch.bfloat16 to torch.float16.\n", + "INFO 01-11 02:56:29 [model.py:1661] Using max model len 32768\n", + "INFO 01-11 02:56:29 [scheduler.py:230] Chunked prefill is enabled with max_num_batched_tokens=8192.\n", + "\u001b[0;36m(EngineCore_DP0 pid=2714)\u001b[0;0m INFO 01-11 02:56:42 [core.py:93] Initializing a V1 LLM engine (v0.13.0) with config: model='Qwen/Qwen2.5-3B-Instruct', speculative_config=None, tokenizer='Qwen/Qwen2.5-3B-Instruct', skip_tokenizer_init=False, tokenizer_mode=auto, revision=None, tokenizer_revision=None, trust_remote_code=False, dtype=torch.float16, max_seq_len=32768, download_dir=None, load_format=auto, tensor_parallel_size=1, pipeline_parallel_size=1, data_parallel_size=1, disable_custom_all_reduce=False, quantization=None, enforce_eager=False, kv_cache_dtype=auto, device_config=cuda, structured_outputs_config=StructuredOutputsConfig(backend='auto', disable_fallback=False, disable_any_whitespace=False, disable_additional_properties=False, reasoning_parser='', reasoning_parser_plugin='', enable_in_reasoning=False), observability_config=ObservabilityConfig(show_hidden_metrics_for_version=None, otlp_traces_endpoint=None, collect_detailed_traces=None, kv_cache_metrics=False, kv_cache_metrics_sample=0.01, cudagraph_metrics=False, enable_layerwise_nvtx_tracing=False), seed=0, served_model_name=Qwen/Qwen2.5-3B-Instruct, enable_prefix_caching=True, enable_chunked_prefill=True, pooler_config=None, compilation_config={'level': None, 'mode': , 'debug_dump_path': None, 'cache_dir': '', 'compile_cache_save_format': 'binary', 'backend': 'inductor', 'custom_ops': ['none'], 'splitting_ops': ['vllm::unified_attention', 'vllm::unified_attention_with_output', 'vllm::unified_mla_attention', 'vllm::unified_mla_attention_with_output', 'vllm::mamba_mixer2', 'vllm::mamba_mixer', 'vllm::short_conv', 'vllm::linear_attention', 'vllm::plamo2_mamba_mixer', 'vllm::gdn_attention_core', 'vllm::kda_attention', 'vllm::sparse_attn_indexer'], 'compile_mm_encoder': False, 'compile_sizes': [], 'compile_ranges_split_points': [8192], 'inductor_compile_config': {'enable_auto_functionalized_v2': False, 'combo_kernels': True, 'benchmark_combo_kernel': True}, 'inductor_passes': {}, 'cudagraph_mode': , 'cudagraph_num_of_warmups': 1, 'cudagraph_capture_sizes': [1, 2, 4, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128, 136, 144, 152, 160, 168, 176, 184, 192, 200, 208, 216, 224, 232, 240, 248, 256, 272, 288, 304, 320, 336, 352, 368, 384, 400, 416, 432, 448, 464, 480, 496, 512], 'cudagraph_copy_inputs': False, 'cudagraph_specialize_lora': True, 'use_inductor_graph_partition': False, 'pass_config': {'fuse_norm_quant': False, 'fuse_act_quant': False, 'fuse_attn_quant': False, 'eliminate_noops': True, 'enable_sp': False, 'fuse_gemm_comms': False, 'fuse_allreduce_rms': False}, 'max_cudagraph_capture_size': 512, 'dynamic_shapes_config': {'type': , 'evaluate_guards': False}, 'local_cache_dir': None}\n", + "\u001b[0;36m(EngineCore_DP0 pid=2714)\u001b[0;0m INFO 01-11 02:56:43 [parallel_state.py:1203] world_size=1 rank=0 local_rank=0 distributed_init_method=tcp://10.42.33.28:39819 backend=nccl\n", + "\u001b[0;36m(EngineCore_DP0 pid=2714)\u001b[0;0m INFO 01-11 02:56:43 [parallel_state.py:1411] rank 0 in world size 1 is assigned as DP rank 0, PP rank 0, PCP rank 0, TP rank 0, EP rank 0\n", + "\u001b[0;36m(EngineCore_DP0 pid=2714)\u001b[0;0m INFO 01-11 02:56:44 [gpu_model_runner.py:3562] Starting to load model Qwen/Qwen2.5-3B-Instruct...\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[0;36m(EngineCore_DP0 pid=2714)\u001b[0;0m /opt/conda/lib/python3.13/site-packages/tvm_ffi/_optional_torch_c_dlpack.py:174: UserWarning: Failed to JIT torch c dlpack extension, EnvTensorAllocator will not be enabled.\n", + "\u001b[0;36m(EngineCore_DP0 pid=2714)\u001b[0;0m We recommend installing via `pip install torch-c-dlpack-ext`\n", + "\u001b[0;36m(EngineCore_DP0 pid=2714)\u001b[0;0m warnings.warn(\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[0;36m(EngineCore_DP0 pid=2714)\u001b[0;0m INFO 01-11 02:56:47 [cuda.py:351] Using FLASH_ATTN attention backend out of potential backends: ('FLASH_ATTN', 'FLASHINFER', 'TRITON_ATTN', 'FLEX_ATTENTION')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Loading safetensors checkpoint shards: 0% Completed | 0/2 [00:00 Given the clues, we need to determine the placement of each person’s meals and footwear. Let's start with the first clue: \"The person who ate fried rice does not live in the second house.\" We already know the fried rice eater is living in the first house. Given the remaining options for the second and third houses, we get positions for fried rice.\n", + "\n", + "The second clue tells us \"The person who is wearing boots lives directly left of the person who ate fried rice.\" Since the fried rice eater is in the first house, the person wearing boots must be in the second house because they are left of the first house.\n", + "\n", + "Given that the third house is left of the second house, the only remaining option for footwear outside of boots and anything else, considering everybody shares different types of shoes and meals, boots must be on the 2nd house, leaving loafers as possible on the third house if it is the only house left for loafers, and we can use this.\n", + "\n", + "Now we know the fried rice is in the first house and boots are in the second house. The only person with loafers left will be the one living in the third house. \n", + "\n", + "Now we can place all clues step-by-step:\n", + "1. Fried rice is in the first house.\n", + "2. Boots live directly left of fried rice, so Boots in the second house.\n", + "3. Spaghetti is in the first house.\n", + "4. The loafers person cannot live in the first house, so the loafers must be living in the third house.\n", + "\n", + "So,\n", + "\n", + " 3 \n", + "\n", + "\n", + "################# [Output] ##################\n", + "\n", + "\n", + "To determine the house where the person wearing loafers lives, let's use the given clues and step by step analyze each one.\n", + "\n", + " Given the clues, we need to determine the placement of each person’s meals and footwear. Let's start with the first clue: \"The person who ate fried rice does not live in the second house.\" We already know the fried rice eater is living in the first house. Given the remaining options for the second and third houses, we get positions for fried rice.\n", + "\n", + "The second clue tells us \"The person who is wearing boots lives directly left of the person who ate fried rice.\" Since the fried rice eater is in the first house, the person wearing boots must be in the second house because they are left of the first house.\n", + "\n", + "Given that the third house is left of the second house, the only remaining option for footwear outside of boots and anything else, considering everybody shares different types of shoes and meals, boots must be on the 2nd house, leaving loafers as possible on the third house if it is the only house left for loafers, and we can use this.\n", + "\n", + "Now we know the fried rice is in the first house and boots are in the second house. The only person with loafers left will be the one living in the third house. \n", + "\n", + "Now we can place all clues step-by-step:\n", + "1. Fried rice is in the first house.\n", + "2. Boots live directly left of fried rice, so Boots in the second house.\n", + "3. Spaghetti is in the first house.\n", + "4. The loafers person cannot live in the first house, so the loafers must be living in the third house.\n", + "\n", + "So,\n", + "\n", + " 3 \n", + "\n", + "\n", + "\n", + "################# [Output] ##################\n", + "\n", + "\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "INFO 01-11 02:57:54 [utils.py:253] non-default args: {'dtype': 'float16', 'disable_log_stats': True, 'model': 'Qwen/Qwen2.5-3B-Instruct'}\n", + "INFO 01-11 02:57:54 [model.py:514] Resolved architecture: Qwen2ForCausalLM\n", + "WARNING 01-11 02:57:54 [model.py:2005] Casting torch.bfloat16 to torch.float16.\n", + "INFO 01-11 02:57:54 [model.py:1661] Using max model len 32768\n", + "INFO 01-11 02:57:54 [scheduler.py:230] Chunked prefill is enabled with max_num_batched_tokens=8192.\n", + "\u001b[0;36m(EngineCore_DP0 pid=2901)\u001b[0;0m INFO 01-11 02:58:04 [core.py:93] Initializing a V1 LLM engine (v0.13.0) with config: model='Qwen/Qwen2.5-3B-Instruct', speculative_config=None, tokenizer='Qwen/Qwen2.5-3B-Instruct', skip_tokenizer_init=False, tokenizer_mode=auto, revision=None, tokenizer_revision=None, trust_remote_code=False, dtype=torch.float16, max_seq_len=32768, download_dir=None, load_format=auto, tensor_parallel_size=1, pipeline_parallel_size=1, data_parallel_size=1, disable_custom_all_reduce=False, quantization=None, enforce_eager=False, kv_cache_dtype=auto, device_config=cuda, structured_outputs_config=StructuredOutputsConfig(backend='auto', disable_fallback=False, disable_any_whitespace=False, disable_additional_properties=False, reasoning_parser='', reasoning_parser_plugin='', enable_in_reasoning=False), observability_config=ObservabilityConfig(show_hidden_metrics_for_version=None, otlp_traces_endpoint=None, collect_detailed_traces=None, kv_cache_metrics=False, kv_cache_metrics_sample=0.01, cudagraph_metrics=False, enable_layerwise_nvtx_tracing=False), seed=0, served_model_name=Qwen/Qwen2.5-3B-Instruct, enable_prefix_caching=True, enable_chunked_prefill=True, pooler_config=None, compilation_config={'level': None, 'mode': , 'debug_dump_path': None, 'cache_dir': '', 'compile_cache_save_format': 'binary', 'backend': 'inductor', 'custom_ops': ['none'], 'splitting_ops': ['vllm::unified_attention', 'vllm::unified_attention_with_output', 'vllm::unified_mla_attention', 'vllm::unified_mla_attention_with_output', 'vllm::mamba_mixer2', 'vllm::mamba_mixer', 'vllm::short_conv', 'vllm::linear_attention', 'vllm::plamo2_mamba_mixer', 'vllm::gdn_attention_core', 'vllm::kda_attention', 'vllm::sparse_attn_indexer'], 'compile_mm_encoder': False, 'compile_sizes': [], 'compile_ranges_split_points': [8192], 'inductor_compile_config': {'enable_auto_functionalized_v2': False, 'combo_kernels': True, 'benchmark_combo_kernel': True}, 'inductor_passes': {}, 'cudagraph_mode': , 'cudagraph_num_of_warmups': 1, 'cudagraph_capture_sizes': [1, 2, 4, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128, 136, 144, 152, 160, 168, 176, 184, 192, 200, 208, 216, 224, 232, 240, 248, 256, 272, 288, 304, 320, 336, 352, 368, 384, 400, 416, 432, 448, 464, 480, 496, 512], 'cudagraph_copy_inputs': False, 'cudagraph_specialize_lora': True, 'use_inductor_graph_partition': False, 'pass_config': {'fuse_norm_quant': False, 'fuse_act_quant': False, 'fuse_attn_quant': False, 'eliminate_noops': True, 'enable_sp': False, 'fuse_gemm_comms': False, 'fuse_allreduce_rms': False}, 'max_cudagraph_capture_size': 512, 'dynamic_shapes_config': {'type': , 'evaluate_guards': False}, 'local_cache_dir': None}\n", + "\u001b[0;36m(EngineCore_DP0 pid=2901)\u001b[0;0m INFO 01-11 02:58:05 [parallel_state.py:1203] world_size=1 rank=0 local_rank=0 distributed_init_method=tcp://10.42.33.28:51151 backend=nccl\n", + "\u001b[0;36m(EngineCore_DP0 pid=2901)\u001b[0;0m INFO 01-11 02:58:05 [parallel_state.py:1411] rank 0 in world size 1 is assigned as DP rank 0, PP rank 0, PCP rank 0, TP rank 0, EP rank 0\n", + "\u001b[0;36m(EngineCore_DP0 pid=2901)\u001b[0;0m INFO 01-11 02:58:06 [gpu_model_runner.py:3562] Starting to load model Qwen/Qwen2.5-3B-Instruct...\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[0;36m(EngineCore_DP0 pid=2901)\u001b[0;0m /opt/conda/lib/python3.13/site-packages/tvm_ffi/_optional_torch_c_dlpack.py:174: UserWarning: Failed to JIT torch c dlpack extension, EnvTensorAllocator will not be enabled.\n", + "\u001b[0;36m(EngineCore_DP0 pid=2901)\u001b[0;0m We recommend installing via `pip install torch-c-dlpack-ext`\n", + "\u001b[0;36m(EngineCore_DP0 pid=2901)\u001b[0;0m warnings.warn(\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[0;36m(EngineCore_DP0 pid=2901)\u001b[0;0m INFO 01-11 02:58:09 [cuda.py:351] Using FLASH_ATTN attention backend out of potential backends: ('FLASH_ATTN', 'FLASHINFER', 'TRITON_ATTN', 'FLEX_ATTENTION')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Loading safetensors checkpoint shards: 0% Completed | 0/2 [00:00\n", + "From the clues, we know the following:\n", + "1. Someone drinks root beer (RB) and someone has a rose bouquet (RBG).\n", + "2. The root beer lover (RB) is to the right of the person with a rose bouquet (RBG).\n", + "3. The soccer player (SP) is directly left of the mystery book reader (MBR).\n", + "4. The person with a gameboy (GB) is somewhere to the left of the mystery book reader (MBR).\n", + "\n", + "Let's denote the houses as House 1 and House 2, where House 1 is to the left and House 2 is to the right.\n", + "\n", + "Based on clue 3, we know the person with a gameboy (GB) must be to the left of the person who is a mystery book reader (MBR). So if we place the MBR in House 1, the GB is in House 2. If we place the MBR in House 2, the GB is in House 1.\n", + "Given clue 2, the soccer player (SP) is directly left of the MBR. \n", + "\n", + "Let's place the MBR in both potential positions (implying clue 2 would have to be satisfied):\n", + "1. MBR in House 1 -> SP must be in House 2 (and GB left of MBR and not MBR)\n", + "2. MBR in House 2 -> SP must be in House 1 (and GB left of MBR and not MBR)\n", + "\n", + "Clue 3 further constrains that if MBR is in House 1 -> GB should be in House 2 (as hypothetically based on previous constraints), so SP is in House 2 and GB is in House 1.\n", + "\n", + "Now, let's fit the remaining needs:\n", + "\n", + "- There is a root beer lover (RB) and a coffee drinker (CD).\n", + "- The root beer lover (RB) is to the right of the rose bouquet (RBG) and should be in House 2 and the rose bouquet (RBG) should be somewhere to the left of them.\n", + "\n", + "From clue 1, we get the possibilities now:\n", + "- Person with root beer in House 2 cannot be next to an MBR/Mystery; so if MBR is in House 1 (which is hypothetical placing right symbolically before final checking) -> the root beer lover should not be adjacent or compelled further by clues.\n", + "- So we logically assume the above positions.\n", + "\n", + "Given such contradictions by standard placement lines, the attachable easily find the location is:\n", + "\n", + "Putting:\n", + "1. Man in House 1 lives with Buttle( MBR)\n", + "2. Waterbe falladow\":\n", + "Hence:\n", + "\n", + " House 1 \n", + "\n", + "\n", + "################# [Output] ##################\n", + "\n", + "\n", + "To determine the house where the person who has a rose bouquet lives, let's analyze the given clues step by step and use logical reasoning first, then verify with an external model if needed.\n", + "\n", + "\n", + "From the clues, we know the following:\n", + "1. Someone drinks root beer (RB) and someone has a rose bouquet (RBG).\n", + "2. The root beer lover (RB) is to the right of the person with a rose bouquet (RBG).\n", + "3. The soccer player (SP) is directly left of the mystery book reader (MBR).\n", + "4. The person with a gameboy (GB) is somewhere to the left of the mystery book reader (MBR).\n", + "\n", + "Let's denote the houses as House 1 and House 2, where House 1 is to the left and House 2 is to the right.\n", + "\n", + "Based on clue 3, we know the person with a gameboy (GB) must be to the left of the person who is a mystery book reader (MBR). So if we place the MBR in House 1, the GB is in House 2. If we place the MBR in House 2, the GB is in House 1.\n", + "Given clue 2, the soccer player (SP) is directly left of the MBR. \n", + "\n", + "Let's place the MBR in both potential positions (implying clue 2 would have to be satisfied):\n", + "1. MBR in House 1 -> SP must be in House 2 (and GB left of MBR and not MBR)\n", + "2. MBR in House 2 -> SP must be in House 1 (and GB left of MBR and not MBR)\n", + "\n", + "Clue 3 further constrains that if MBR is in House 1 -> GB should be in House 2 (as hypothetically based on previous constraints), so SP is in House 2 and GB is in House 1.\n", + "\n", + "Now, let's fit the remaining needs:\n", + "\n", + "- There is a root beer lover (RB) and a coffee drinker (CD).\n", + "- The root beer lover (RB) is to the right of the rose bouquet (RBG) and should be in House 2 and the rose bouquet (RBG) should be somewhere to the left of them.\n", + "\n", + "From clue 1, we get the possibilities now:\n", + "- Person with root beer in House 2 cannot be next to an MBR/Mystery; so if MBR is in House 1 (which is hypothetical placing right symbolically before final checking) -> the root beer lover should not be adjacent or compelled further by clues.\n", + "- So we logically assume the above positions.\n", + "\n", + "Given such contradictions by standard placement lines, the attachable easily find the location is:\n", + "\n", + "Putting:\n", + "1. Man in House 1 lives with Buttle( MBR)\n", + "2. Waterbe falladow\":\n", + "Hence:\n", + "\n", + " House 1 \n", + "\n", + "\n", + "\n", + "################# [Output] ##################\n", + "\n", + "\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "INFO 01-11 02:59:23 [utils.py:253] non-default args: {'dtype': 'float16', 'disable_log_stats': True, 'model': 'Qwen/Qwen2.5-3B-Instruct'}\n", + "INFO 01-11 02:59:23 [model.py:514] Resolved architecture: Qwen2ForCausalLM\n", + "WARNING 01-11 02:59:23 [model.py:2005] Casting torch.bfloat16 to torch.float16.\n", + "INFO 01-11 02:59:23 [model.py:1661] Using max model len 32768\n", + "INFO 01-11 02:59:23 [scheduler.py:230] Chunked prefill is enabled with max_num_batched_tokens=8192.\n", + "\u001b[0;36m(EngineCore_DP0 pid=3093)\u001b[0;0m INFO 01-11 02:59:33 [core.py:93] Initializing a V1 LLM engine (v0.13.0) with config: model='Qwen/Qwen2.5-3B-Instruct', speculative_config=None, tokenizer='Qwen/Qwen2.5-3B-Instruct', skip_tokenizer_init=False, tokenizer_mode=auto, revision=None, tokenizer_revision=None, trust_remote_code=False, dtype=torch.float16, max_seq_len=32768, download_dir=None, load_format=auto, tensor_parallel_size=1, pipeline_parallel_size=1, data_parallel_size=1, disable_custom_all_reduce=False, quantization=None, enforce_eager=False, kv_cache_dtype=auto, device_config=cuda, structured_outputs_config=StructuredOutputsConfig(backend='auto', disable_fallback=False, disable_any_whitespace=False, disable_additional_properties=False, reasoning_parser='', reasoning_parser_plugin='', enable_in_reasoning=False), observability_config=ObservabilityConfig(show_hidden_metrics_for_version=None, otlp_traces_endpoint=None, collect_detailed_traces=None, kv_cache_metrics=False, kv_cache_metrics_sample=0.01, cudagraph_metrics=False, enable_layerwise_nvtx_tracing=False), seed=0, served_model_name=Qwen/Qwen2.5-3B-Instruct, enable_prefix_caching=True, enable_chunked_prefill=True, pooler_config=None, compilation_config={'level': None, 'mode': , 'debug_dump_path': None, 'cache_dir': '', 'compile_cache_save_format': 'binary', 'backend': 'inductor', 'custom_ops': ['none'], 'splitting_ops': ['vllm::unified_attention', 'vllm::unified_attention_with_output', 'vllm::unified_mla_attention', 'vllm::unified_mla_attention_with_output', 'vllm::mamba_mixer2', 'vllm::mamba_mixer', 'vllm::short_conv', 'vllm::linear_attention', 'vllm::plamo2_mamba_mixer', 'vllm::gdn_attention_core', 'vllm::kda_attention', 'vllm::sparse_attn_indexer'], 'compile_mm_encoder': False, 'compile_sizes': [], 'compile_ranges_split_points': [8192], 'inductor_compile_config': {'enable_auto_functionalized_v2': False, 'combo_kernels': True, 'benchmark_combo_kernel': True}, 'inductor_passes': {}, 'cudagraph_mode': , 'cudagraph_num_of_warmups': 1, 'cudagraph_capture_sizes': [1, 2, 4, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128, 136, 144, 152, 160, 168, 176, 184, 192, 200, 208, 216, 224, 232, 240, 248, 256, 272, 288, 304, 320, 336, 352, 368, 384, 400, 416, 432, 448, 464, 480, 496, 512], 'cudagraph_copy_inputs': False, 'cudagraph_specialize_lora': True, 'use_inductor_graph_partition': False, 'pass_config': {'fuse_norm_quant': False, 'fuse_act_quant': False, 'fuse_attn_quant': False, 'eliminate_noops': True, 'enable_sp': False, 'fuse_gemm_comms': False, 'fuse_allreduce_rms': False}, 'max_cudagraph_capture_size': 512, 'dynamic_shapes_config': {'type': , 'evaluate_guards': False}, 'local_cache_dir': None}\n", + "\u001b[0;36m(EngineCore_DP0 pid=3093)\u001b[0;0m INFO 01-11 02:59:34 [parallel_state.py:1203] world_size=1 rank=0 local_rank=0 distributed_init_method=tcp://10.42.33.28:35297 backend=nccl\n", + "\u001b[0;36m(EngineCore_DP0 pid=3093)\u001b[0;0m INFO 01-11 02:59:34 [parallel_state.py:1411] rank 0 in world size 1 is assigned as DP rank 0, PP rank 0, PCP rank 0, TP rank 0, EP rank 0\n", + "\u001b[0;36m(EngineCore_DP0 pid=3093)\u001b[0;0m INFO 01-11 02:59:35 [gpu_model_runner.py:3562] Starting to load model Qwen/Qwen2.5-3B-Instruct...\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[0;36m(EngineCore_DP0 pid=3093)\u001b[0;0m /opt/conda/lib/python3.13/site-packages/tvm_ffi/_optional_torch_c_dlpack.py:174: UserWarning: Failed to JIT torch c dlpack extension, EnvTensorAllocator will not be enabled.\n", + "\u001b[0;36m(EngineCore_DP0 pid=3093)\u001b[0;0m We recommend installing via `pip install torch-c-dlpack-ext`\n", + "\u001b[0;36m(EngineCore_DP0 pid=3093)\u001b[0;0m warnings.warn(\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[0;36m(EngineCore_DP0 pid=3093)\u001b[0;0m INFO 01-11 02:59:37 [cuda.py:351] Using FLASH_ATTN attention backend out of potential backends: ('FLASH_ATTN', 'FLASHINFER', 'TRITON_ATTN', 'FLEX_ATTENTION')\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Loading safetensors checkpoint shards: 0% Completed | 0/2 [00:002\n", + "\n", + "\n", + "################# [Output] ##################\n", + "\n", + "\n", + "To determine the house where the person who drives a truck lives, I need to gather information about the distribution of the cars and their corresponding locations based on the given clues.\n", + "\n", + "### Step-by-step reasoning:\n", + "\n", + "First, let's denote people with the different characteristics:\n", + "1. Person A: drives a convertible\n", + "2. Person B: drives a minivan\n", + "3. Person C: drives a truck\n", + "4. Person D: ate grilled cheese\n", + "5. Person E: ate spaghetti\n", + "6. Person F: had stew\n", + "7. Person G: likes blue\n", + "8. Person H: likes red\n", + "9. Person I: has blue\n", + "10. Person J: has a rose bouquet\n", + "11. Person K: has a bouquet of daffodils\n", + "12. Person L: has a bouquet of lilies\n", + "\n", + "### Clue Analysis:\n", + "\n", + "#### Clue 1: The person who likes blue lives directly left of the person who ate spaghetti.\n", + "- Someone (I) likes blue (blue).\n", + "- Someone (J) ate spaghetti (spaghetti).\n", + "\n", + "Possible positions for I and J:\n", + "- (I, J): blue then spaghetti\n", + "- (J, I): spaghetti then blue\n", + "\n", + "#### Clue 2: The person who had stew lives somewhere to the left of the person who has a bouquet of lilies.\n", + "- Someone (E) had stew (stew).\n", + "- Someone (K) has lilies (lilies).\n", + "\n", + "Possible positions for E and K:\n", + "- (E, K): stew then lilies\n", + "- (K, E): lilies then stew\n", + "\n", + "This implies:\n", + "- (I, J): blue then spaghetti (E has stew) → (J, I): spaghetti then blue (E has stew)\n", + "- (K, E): lilies then stew → E (has stew) then (K, E): lilies then stew\n", + "\n", + "#### Clue 3: The person who has a rose bouquet lives directly left of the person who drives a truck.\n", + "- Someone (J) has a rose bouquet (rose).\n", + "\n", + "Possible positions for J and C:\n", + "- (J, C): rose then truck, or (C, J): truck then rose\n", + "\n", + "#### Clue 4: The person who drives a minivan lives directly left of the person who had stew.\n", + "- Someone (B) drives a minivan (minivan).\n", + "- Someone (E) had stew (stew).\n", + "\n", + "Possible positions for B and E:\n", + "- (B, E): minivan then stew, or (E, B): stew then minivan\n", + "\n", + "#### Clue 5: The person who likes red does not live in the first house.\n", + "- Someone (H) has red hat (red).\n", + "- Has to live in either 2nd or 3rd house.\n", + "\n", + "#### Clue 6: The person who drives a minivan lives directly left of the person who has a bouquet of daffodils.\n", + "- Someone (B) drives a minivan.\n", + "- Someone (K) has daffodils.\n", + "\n", + "Possible positions for B and K:\n", + "- (B, K): minivan then daffodils, or (K, B): daffodils then minivan\n", + "\n", + "### Putting all Clues Together:\n", + "\n", + "From Clues 1 and 2:\n", + "- The sequence (I, J): blue then spaghetti\n", + "- The sequence (K, E): lilies then stew\n", + "\n", + "From Clues 3, 4, 5, and 6:\n", + "- (B, K): minivan then daffodil\n", + "- Person H lives in the 2nd or 3rd house (exclusion of red hat).\n", + "\n", + "Based on these constraints, let's fit the person who drives a truck into the sequence fitting with the given relationships:\n", + "- The person who has the truck (C) shifts to positions with remaining remaining clues logic fit.\n", + "\n", + "Without a step-by-step binding all pieces together:\n", + "- Finds truck sits in middle:\n", + " - House 2 for:j resp person\n", + "\n", + "### Final Answer:\n", + "The person who drives a truck lives in House 2. Thus, my final answer is: 2\n", + "\n", + "\n", + "\n", + "################# [Output] ##################\n", + "\n", + "\n", + "Routed 5 queries\n", + "Results saved to: outputs/router_r1_results.jsonl\n", + "\n", + "Sample results:\n", + " 1. Q: There are 4 houses in a row, numbered...\n", + " Success: True\n", + " 2. Q: There are 3 houses in a row, numbered...\n", + " Success: True\n", + " 3. Q: There are 3 houses in a row, numbered...\n", + " Success: True\n" + ] + } + ], + "source": [ + "import json\n", + "\n", + "# Load queries from a JSONL file\n", + "def load_queries_from_file(file_path):\n", + " \"\"\"Load queries from a JSONL file.\"\"\"\n", + " queries = []\n", + " with open(file_path, 'r', encoding='utf-8') as f:\n", + " for line in f:\n", + " if line.strip():\n", + " queries.append(json.loads(line))\n", + " return queries\n", + "\n", + "# Save results to a JSONL file\n", + "def save_results_to_file(results, output_path):\n", + " \"\"\"Save routing results to a JSONL file.\"\"\"\n", + " os.makedirs(os.path.dirname(output_path), exist_ok=True)\n", + " with open(output_path, 'w', encoding='utf-8') as f:\n", + " for result in results:\n", + " f.write(json.dumps(result, ensure_ascii=False) + '\\n')\n", + " print(f\"Results saved to: {output_path}\")\n", + "\n", + "# Example: Load from default query file\n", + "QUERY_FILE = \"LLMRouter/data/example_data/query_data/default_query_test.jsonl\"\n", + "OUTPUT_FILE = \"outputs/router_r1_results.jsonl\"\n", + "\n", + "if os.path.exists(QUERY_FILE):\n", + " # Load queries\n", + " file_queries = load_queries_from_file(QUERY_FILE)\n", + " print(f\"Loaded {len(file_queries)} queries from: {QUERY_FILE}\")\n", + " \n", + " # Route queries (limit to 5 for demo due to API costs)\n", + " try:\n", + " file_results = router.route_batch(file_queries[:5])\n", + " print(f\"Routed {len(file_results)} queries\")\n", + " \n", + " # Save results\n", + " save_results_to_file(file_results, OUTPUT_FILE)\n", + " \n", + " # Show sample results\n", + " print(f\"\\nSample results:\")\n", + " for i, result in enumerate(file_results[:3], 1):\n", + " print(f\" {i}. {result.get('query', '')[:40]}...\")\n", + " print(f\" Success: {result.get('success', False)}\")\n", + " except Exception as e:\n", + " print(f\"Error during batch routing: {e}\")\n", + "else:\n", + " print(f\"Query file not found: {QUERY_FILE}\")\n", + " print(\"Create a JSONL file with format: {\\\"query\\\": \\\"Your question\\\"}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "**RouterR1 Key Points**:\n", + "- Pre-trained agentic router (no training required)\n", + "- Uses vLLM for efficient GPU inference\n", + "- Iterative reasoning with external routing API\n", + "- Supports task-specific query formatting\n", + "- Token tracking for cost analysis\n", + "\n", + "**Requirements**:\n", + "- CUDA-enabled GPU (vLLM requirement)\n", + "- Valid API credentials for routing service\n", + "- Sufficient GPU memory for the base model\n", + "\n", + "**Use Cases**:\n", + "- Complex queries requiring reasoning\n", + "- Multi-step question answering\n", + "- Research and evaluation on routing strategies" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "gpuType": "T4" + }, + "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.13.11" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/colab_notebooks/smallest_llm/01_smallest_llm_inference.ipynb b/colab_notebooks/smallest_llm/01_smallest_llm_inference.ipynb new file mode 100644 index 0000000..8e08078 --- /dev/null +++ b/colab_notebooks/smallest_llm/01_smallest_llm_inference.ipynb @@ -0,0 +1,704 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# SmallestLLM Router - Inference\n", + "\n", + "This notebook demonstrates the **SmallestLLM** baseline router.\n", + "\n", + "## Overview\n", + "\n", + "SmallestLLM is a simple baseline router that always routes queries to the smallest model in the candidate pool.\n", + "This serves as a lower bound for routing performance and an upper bound for cost efficiency.\n", + "\n", + "**Key Characteristics**:\n", + "- No training required (deterministic baseline)\n", + "- Always selects the smallest model by parameter size\n", + "- Useful for cost-efficiency benchmarking\n", + "- Lowest latency routing" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Environment Setup" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cloning into 'LLMRouter'...\n", + "remote: Enumerating objects: 6017, done.\u001b[K\n", + "remote: Counting objects: 100% (180/180), done.\u001b[K\n", + "remote: Compressing objects: 100% (90/90), done.\u001b[K\n", + "remote: Total 6017 (delta 105), reused 96 (delta 90), pack-reused 5837 (from 2)\u001b[K\n", + "Receiving objects: 100% (6017/6017), 89.41 MiB | 57.87 MiB/s, done.\n", + "Resolving deltas: 100% (2946/2946), done.\n", + "Updating files: 100% (288/288), done.\n", + "/home/zhongjie/LLMRouter\n", + "Obtaining file:///home/zhongjie/LLMRouter\n", + " Installing build dependencies ... \u001b[?25ldone\n", + "\u001b[?25h Checking if build backend supports build_editable ... \u001b[?25ldone\n", + "\u001b[?25h Getting requirements to build editable ... \u001b[?25ldone\n", + "\u001b[?25h Preparing editable metadata (pyproject.toml) ... \u001b[?25ldone\n", + "\u001b[?25hRequirement already satisfied: torch>=2.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.9.1)\n", + "Requirement already satisfied: transformers>=4.40 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (4.57.6)\n", + "Requirement already satisfied: sentencepiece>=0.1.99 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (0.2.1)\n", + "Requirement already satisfied: numpy>=1.21 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.3.5)\n", + "Requirement already satisfied: pandas>=1.5 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.3.3)\n", + "Requirement already satisfied: scikit-learn>=1.2 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (1.8.0)\n", + "Requirement already satisfied: pyyaml>=6.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (6.0.3)\n", + "Requirement already satisfied: tqdm>=4.65 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (4.67.1)\n", + "Requirement already satisfied: datasets>=2.14 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (4.5.0)\n", + "Requirement already satisfied: pydantic>=2.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.12.5)\n", + "Requirement already satisfied: gradio>=4.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (6.3.0)\n", + "Requirement already satisfied: litellm>=1.0 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (1.81.0)\n", + "Requirement already satisfied: peft>=0.7 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (0.18.1)\n", + "Requirement already satisfied: torch-geometric>=2.3 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (2.7.0)\n", + "Requirement already satisfied: scipy>=1.10 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (1.16.3)\n", + "Requirement already satisfied: protobuf>=3.20 in /opt/conda/lib/python3.13/site-packages (from llmrouter-lib==0.2.0) (6.31.1)\n", + "Requirement already satisfied: filelock in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (3.20.2)\n", + "Requirement already satisfied: pyarrow>=21.0.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (22.0.0)\n", + "Requirement already satisfied: dill<0.4.1,>=0.3.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (0.4.0)\n", + "Requirement already satisfied: requests>=2.32.2 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (2.32.5)\n", + "Requirement already satisfied: httpx<1.0.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (0.28.1)\n", + "Requirement already satisfied: xxhash in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (3.6.0)\n", + "Requirement already satisfied: multiprocess<0.70.19 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (0.70.18)\n", + "Requirement already satisfied: fsspec<=2025.10.0,>=2023.1.0 in /opt/conda/lib/python3.13/site-packages (from fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (2025.10.0)\n", + "Requirement already satisfied: huggingface-hub<2.0,>=0.25.0 in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (0.36.0)\n", + "Requirement already satisfied: packaging in /opt/conda/lib/python3.13/site-packages (from datasets>=2.14->llmrouter-lib==0.2.0) (25.0)\n", + "Requirement already satisfied: aiohttp!=4.0.0a0,!=4.0.0a1 in /opt/conda/lib/python3.13/site-packages (from fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (3.13.3)\n", + "Requirement already satisfied: anyio in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (4.12.0)\n", + "Requirement already satisfied: certifi in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (2026.1.4)\n", + "Requirement already satisfied: httpcore==1.* in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.0.9)\n", + "Requirement already satisfied: idna in /opt/conda/lib/python3.13/site-packages (from httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (3.11)\n", + "Requirement already satisfied: h11>=0.16 in /opt/conda/lib/python3.13/site-packages (from httpcore==1.*->httpx<1.0.0->datasets>=2.14->llmrouter-lib==0.2.0) (0.16.0)\n", + "Requirement already satisfied: typing-extensions>=3.7.4.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<2.0,>=0.25.0->datasets>=2.14->llmrouter-lib==0.2.0) (4.15.0)\n", + "Requirement already satisfied: hf-xet<2.0.0,>=1.1.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<2.0,>=0.25.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.2.0)\n", + "Requirement already satisfied: aiohappyeyeballs>=2.5.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (2.6.1)\n", + "Requirement already satisfied: aiosignal>=1.4.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.4.0)\n", + "Requirement already satisfied: attrs>=17.3.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (25.4.0)\n", + "Requirement already satisfied: frozenlist>=1.1.1 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.8.0)\n", + "Requirement already satisfied: multidict<7.0,>=4.5 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (6.7.0)\n", + "Requirement already satisfied: propcache>=0.2.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (0.4.1)\n", + "Requirement already satisfied: yarl<2.0,>=1.17.0 in /opt/conda/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.10.0,>=2023.1.0->datasets>=2.14->llmrouter-lib==0.2.0) (1.22.0)\n", + "Requirement already satisfied: aiofiles<25.0,>=22.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (24.1.0)\n", + "Requirement already satisfied: audioop-lts<1.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.2.2)\n", + "Requirement already satisfied: brotli>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (1.2.0)\n", + "Requirement already satisfied: fastapi<1.0,>=0.115.2 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.128.0)\n", + "Requirement already satisfied: ffmpy in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (1.0.0)\n", + "Requirement already satisfied: gradio-client==2.0.3 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (2.0.3)\n", + "Requirement already satisfied: groovy~=0.1 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.1.2)\n", + "Requirement already satisfied: jinja2<4.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (3.1.6)\n", + "Requirement already satisfied: markupsafe<4.0,>=2.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (3.0.3)\n", + "Requirement already satisfied: orjson~=3.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (3.11.5)\n", + "Requirement already satisfied: pillow<13.0,>=8.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (12.1.0)\n", + "Requirement already satisfied: pydub in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.25.1)\n", + "Requirement already satisfied: python-multipart>=0.0.18 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.0.21)\n", + "Requirement already satisfied: safehttpx<0.2.0,>=0.1.7 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.1.7)\n", + "Requirement already satisfied: semantic-version~=2.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (2.10.0)\n", + "Requirement already satisfied: starlette<1.0,>=0.40.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.50.0)\n", + "Requirement already satisfied: tomlkit<0.14.0,>=0.12.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.13.3)\n", + "Requirement already satisfied: typer<1.0,>=0.12 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.21.1)\n", + "Requirement already satisfied: uvicorn>=0.14.0 in /opt/conda/lib/python3.13/site-packages (from gradio>=4.0->llmrouter-lib==0.2.0) (0.40.0)\n", + "Requirement already satisfied: annotated-doc>=0.0.2 in /opt/conda/lib/python3.13/site-packages (from fastapi<1.0,>=0.115.2->gradio>=4.0->llmrouter-lib==0.2.0) (0.0.4)\n", + "Requirement already satisfied: python-dateutil>=2.8.2 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.2.0) (2.9.0.post0)\n", + "Requirement already satisfied: pytz>=2020.1 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.2.0) (2025.2)\n", + "Requirement already satisfied: tzdata>=2022.7 in /opt/conda/lib/python3.13/site-packages (from pandas>=1.5->llmrouter-lib==0.2.0) (2025.3)\n", + "Requirement already satisfied: annotated-types>=0.6.0 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.2.0) (0.7.0)\n", + "Requirement already satisfied: pydantic-core==2.41.5 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.2.0) (2.41.5)\n", + "Requirement already satisfied: typing-inspection>=0.4.2 in /opt/conda/lib/python3.13/site-packages (from pydantic>=2.0->llmrouter-lib==0.2.0) (0.4.2)\n", + "Requirement already satisfied: click>=8.0.0 in /opt/conda/lib/python3.13/site-packages (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (8.3.1)\n", + "Requirement already satisfied: shellingham>=1.3.0 in /opt/conda/lib/python3.13/site-packages (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (1.5.4)\n", + "Requirement already satisfied: rich>=10.11.0 in /opt/conda/lib/python3.13/site-packages (from typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (14.2.0)\n", + "Requirement already satisfied: fastuuid>=0.13.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (0.14.0)\n", + "Requirement already satisfied: grpcio!=1.68.*,!=1.69.*,!=1.70.*,!=1.71.0,!=1.71.1,!=1.72.0,!=1.72.1,!=1.73.0,>=1.62.3 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (1.76.0)\n", + "Requirement already satisfied: importlib-metadata>=6.8.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (8.7.0)\n", + "Requirement already satisfied: jsonschema<5.0.0,>=4.23.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (4.25.1)\n", + "Requirement already satisfied: openai>=2.8.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (2.15.0)\n", + "Requirement already satisfied: python-dotenv>=0.2.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (1.2.1)\n", + "Requirement already satisfied: tiktoken>=0.7.0 in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (0.12.0)\n", + "Requirement already satisfied: tokenizers in /opt/conda/lib/python3.13/site-packages (from litellm>=1.0->llmrouter-lib==0.2.0) (0.22.2)\n", + "Requirement already satisfied: jsonschema-specifications>=2023.03.6 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.2.0) (2025.9.1)\n", + "Requirement already satisfied: referencing>=0.28.4 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.2.0) (0.37.0)\n", + "Requirement already satisfied: rpds-py>=0.7.1 in /opt/conda/lib/python3.13/site-packages (from jsonschema<5.0.0,>=4.23.0->litellm>=1.0->llmrouter-lib==0.2.0) (0.30.0)\n", + "Requirement already satisfied: zipp>=3.20 in /opt/conda/lib/python3.13/site-packages (from importlib-metadata>=6.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (3.23.0)\n", + "Requirement already satisfied: distro<2,>=1.7.0 in /opt/conda/lib/python3.13/site-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (1.9.0)\n", + "Requirement already satisfied: jiter<1,>=0.10.0 in /opt/conda/lib/python3.13/site-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (0.12.0)\n", + "Requirement already satisfied: sniffio in /opt/conda/lib/python3.13/site-packages (from openai>=2.8.0->litellm>=1.0->llmrouter-lib==0.2.0) (1.3.1)\n", + "Requirement already satisfied: psutil in /opt/conda/lib/python3.13/site-packages (from peft>=0.7->llmrouter-lib==0.2.0) (7.2.1)\n", + "Requirement already satisfied: accelerate>=0.21.0 in /opt/conda/lib/python3.13/site-packages (from peft>=0.7->llmrouter-lib==0.2.0) (1.12.0)\n", + "Requirement already satisfied: safetensors in /opt/conda/lib/python3.13/site-packages (from peft>=0.7->llmrouter-lib==0.2.0) (0.7.0)\n", + "Requirement already satisfied: six>=1.5 in /opt/conda/lib/python3.13/site-packages (from python-dateutil>=2.8.2->pandas>=1.5->llmrouter-lib==0.2.0) (1.17.0)\n", + "Requirement already satisfied: charset_normalizer<4,>=2 in /opt/conda/lib/python3.13/site-packages (from requests>=2.32.2->datasets>=2.14->llmrouter-lib==0.2.0) (3.4.4)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/conda/lib/python3.13/site-packages (from requests>=2.32.2->datasets>=2.14->llmrouter-lib==0.2.0) (2.6.2)\n", + "Requirement already satisfied: markdown-it-py>=2.2.0 in /opt/conda/lib/python3.13/site-packages (from rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (4.0.0)\n", + "Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /opt/conda/lib/python3.13/site-packages (from rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (2.19.2)\n", + "Requirement already satisfied: mdurl~=0.1 in /opt/conda/lib/python3.13/site-packages (from markdown-it-py>=2.2.0->rich>=10.11.0->typer<1.0,>=0.12->gradio>=4.0->llmrouter-lib==0.2.0) (0.1.2)\n", + "Requirement already satisfied: joblib>=1.3.0 in /opt/conda/lib/python3.13/site-packages (from scikit-learn>=1.2->llmrouter-lib==0.2.0) (1.5.3)\n", + "Requirement already satisfied: threadpoolctl>=3.2.0 in /opt/conda/lib/python3.13/site-packages (from scikit-learn>=1.2->llmrouter-lib==0.2.0) (3.6.0)\n", + "Requirement already satisfied: regex>=2022.1.18 in /opt/conda/lib/python3.13/site-packages (from tiktoken>=0.7.0->litellm>=1.0->llmrouter-lib==0.2.0) (2026.1.15)\n", + "Requirement already satisfied: setuptools in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (80.9.0)\n", + "Requirement already satisfied: sympy>=1.13.3 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (1.14.0)\n", + "Requirement already satisfied: networkx>=2.5.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (3.6.1)\n", + "Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.93)\n", + "Requirement already satisfied: nvidia-cuda-runtime-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.90)\n", + "Requirement already satisfied: nvidia-cuda-cupti-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.90)\n", + "Requirement already satisfied: nvidia-cudnn-cu12==9.10.2.21 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (9.10.2.21)\n", + "Requirement already satisfied: nvidia-cublas-cu12==12.8.4.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.4.1)\n", + "Requirement already satisfied: nvidia-cufft-cu12==11.3.3.83 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (11.3.3.83)\n", + "Requirement already satisfied: nvidia-curand-cu12==10.3.9.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (10.3.9.90)\n", + "Requirement already satisfied: nvidia-cusolver-cu12==11.7.3.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (11.7.3.90)\n", + "Requirement already satisfied: nvidia-cusparse-cu12==12.5.8.93 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.5.8.93)\n", + "Requirement already satisfied: nvidia-cusparselt-cu12==0.7.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (0.7.1)\n", + "Requirement already satisfied: nvidia-nccl-cu12==2.27.5 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (2.27.5)\n", + "Requirement already satisfied: nvidia-nvshmem-cu12==3.3.20 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (3.3.20)\n", + "Requirement already satisfied: nvidia-nvtx-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.90)\n", + "Requirement already satisfied: nvidia-nvjitlink-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (12.8.93)\n", + "Requirement already satisfied: nvidia-cufile-cu12==1.13.1.3 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (1.13.1.3)\n", + "Requirement already satisfied: triton==3.5.1 in /opt/conda/lib/python3.13/site-packages (from torch>=2.0->llmrouter-lib==0.2.0) (3.5.1)\n", + "Requirement already satisfied: mpmath<1.4,>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from sympy>=1.13.3->torch>=2.0->llmrouter-lib==0.2.0) (1.3.0)\n", + "Requirement already satisfied: pyparsing in /opt/conda/lib/python3.13/site-packages (from torch-geometric>=2.3->llmrouter-lib==0.2.0) (3.3.1)\n", + "Building wheels for collected packages: llmrouter-lib\n", + " Building editable for llmrouter-lib (pyproject.toml) ... \u001b[?25ldone\n", + "\u001b[?25h Created wheel for llmrouter-lib: filename=llmrouter_lib-0.2.0-0.editable-py3-none-any.whl size=15709 sha256=20817fa2d39f97aca0071e63a8d5e9d80cbed5f0251b4de4027d658770e8ef2f\n", + " Stored in directory: /tmp/pip-ephem-wheel-cache-z61dz9yl/wheels/82/4a/fd/59c4aec93c356c380d86a88ab74bece164c0aa855d68b155e4\n", + "Successfully built llmrouter-lib\n", + "Installing collected packages: llmrouter-lib\n", + " Attempting uninstall: llmrouter-lib\n", + " Found existing installation: llmrouter-lib 0.2.0\n", + " Uninstalling llmrouter-lib-0.2.0:\n", + " Successfully uninstalled llmrouter-lib-0.2.0\n", + "Successfully installed llmrouter-lib-0.2.0\n", + "Requirement already satisfied: pyyaml in /opt/conda/lib/python3.13/site-packages (6.0.3)\n", + "Requirement already satisfied: transformers in /opt/conda/lib/python3.13/site-packages (4.57.6)\n", + "Requirement already satisfied: torch in /opt/conda/lib/python3.13/site-packages (2.9.1)\n", + "Requirement already satisfied: filelock in /opt/conda/lib/python3.13/site-packages (from transformers) (3.20.2)\n", + "Requirement already satisfied: huggingface-hub<1.0,>=0.34.0 in /opt/conda/lib/python3.13/site-packages (from transformers) (0.36.0)\n", + "Requirement already satisfied: numpy>=1.17 in /opt/conda/lib/python3.13/site-packages (from transformers) (2.3.5)\n", + "Requirement already satisfied: packaging>=20.0 in /opt/conda/lib/python3.13/site-packages (from transformers) (25.0)\n", + "Requirement already satisfied: regex!=2019.12.17 in /opt/conda/lib/python3.13/site-packages (from transformers) (2026.1.15)\n", + "Requirement already satisfied: requests in /opt/conda/lib/python3.13/site-packages (from transformers) (2.32.5)\n", + "Requirement already satisfied: tokenizers<=0.23.0,>=0.22.0 in /opt/conda/lib/python3.13/site-packages (from transformers) (0.22.2)\n", + "Requirement already satisfied: safetensors>=0.4.3 in /opt/conda/lib/python3.13/site-packages (from transformers) (0.7.0)\n", + "Requirement already satisfied: tqdm>=4.27 in /opt/conda/lib/python3.13/site-packages (from transformers) (4.67.1)\n", + "Requirement already satisfied: fsspec>=2023.5.0 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (2025.10.0)\n", + "Requirement already satisfied: typing-extensions>=3.7.4.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (4.15.0)\n", + "Requirement already satisfied: hf-xet<2.0.0,>=1.1.3 in /opt/conda/lib/python3.13/site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (1.2.0)\n", + "Requirement already satisfied: setuptools in /opt/conda/lib/python3.13/site-packages (from torch) (80.9.0)\n", + "Requirement already satisfied: sympy>=1.13.3 in /opt/conda/lib/python3.13/site-packages (from torch) (1.14.0)\n", + "Requirement already satisfied: networkx>=2.5.1 in /opt/conda/lib/python3.13/site-packages (from torch) (3.6.1)\n", + "Requirement already satisfied: jinja2 in /opt/conda/lib/python3.13/site-packages (from torch) (3.1.6)\n", + "Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.93)\n", + "Requirement already satisfied: nvidia-cuda-runtime-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-cuda-cupti-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-cudnn-cu12==9.10.2.21 in /opt/conda/lib/python3.13/site-packages (from torch) (9.10.2.21)\n", + "Requirement already satisfied: nvidia-cublas-cu12==12.8.4.1 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.4.1)\n", + "Requirement already satisfied: nvidia-cufft-cu12==11.3.3.83 in /opt/conda/lib/python3.13/site-packages (from torch) (11.3.3.83)\n", + "Requirement already satisfied: nvidia-curand-cu12==10.3.9.90 in /opt/conda/lib/python3.13/site-packages (from torch) (10.3.9.90)\n", + "Requirement already satisfied: nvidia-cusolver-cu12==11.7.3.90 in /opt/conda/lib/python3.13/site-packages (from torch) (11.7.3.90)\n", + "Requirement already satisfied: nvidia-cusparse-cu12==12.5.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.5.8.93)\n", + "Requirement already satisfied: nvidia-cusparselt-cu12==0.7.1 in /opt/conda/lib/python3.13/site-packages (from torch) (0.7.1)\n", + "Requirement already satisfied: nvidia-nccl-cu12==2.27.5 in /opt/conda/lib/python3.13/site-packages (from torch) (2.27.5)\n", + "Requirement already satisfied: nvidia-nvshmem-cu12==3.3.20 in /opt/conda/lib/python3.13/site-packages (from torch) (3.3.20)\n", + "Requirement already satisfied: nvidia-nvtx-cu12==12.8.90 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.90)\n", + "Requirement already satisfied: nvidia-nvjitlink-cu12==12.8.93 in /opt/conda/lib/python3.13/site-packages (from torch) (12.8.93)\n", + "Requirement already satisfied: nvidia-cufile-cu12==1.13.1.3 in /opt/conda/lib/python3.13/site-packages (from torch) (1.13.1.3)\n", + "Requirement already satisfied: triton==3.5.1 in /opt/conda/lib/python3.13/site-packages (from torch) (3.5.1)\n", + "Requirement already satisfied: mpmath<1.4,>=1.1.0 in /opt/conda/lib/python3.13/site-packages (from sympy>=1.13.3->torch) (1.3.0)\n", + "Requirement already satisfied: MarkupSafe>=2.0 in /opt/conda/lib/python3.13/site-packages (from jinja2->torch) (3.0.3)\n", + "Requirement already satisfied: charset_normalizer<4,>=2 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (3.4.4)\n", + "Requirement already satisfied: idna<4,>=2.5 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (3.11)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (2.6.2)\n", + "Requirement already satisfied: certifi>=2017.4.17 in /opt/conda/lib/python3.13/site-packages (from requests->transformers) (2026.1.4)\n" + ] + } + ], + "source": [ + "# For Google Colab: Clone repository and install dependencies\n", + "import os\n", + "\n", + "!git clone https://github.com/ulab-uiuc/LLMRouter.git\n", + "%cd LLMRouter\n", + "!pip install -e .\n", + "!pip install pyyaml transformers torch" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "os.environ['OPENAI_API_KEY'] = 'your-key'\n", + "os.environ['ANTHROPIC_API_KEY'] = 'your-key'\n", + "# Or for multiple keys:\n", + "os.environ['API_KEYS'] = '[\"key1\", \"key2\"]'" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/zhongjie/LLMRouter/llmrouter/utils/evaluation.py:358: SyntaxWarning: invalid escape sequence '\\%'\n", + " string = string.replace(\"\\%\", \"\") # noqa: W605\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Environment setup complete!\n" + ] + } + ], + "source": [ + "from llmrouter.models.smallest_llm import SmallestLLM\n", + "from llmrouter.utils import setup_environment\n", + "import yaml\n", + "\n", + "setup_environment()\n", + "print(\"Environment setup complete!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Configuration\n", + "\n", + "SmallestLLM router requires only data paths - no hyperparameters.\n", + "\n", + "| Parameter | Description |\n", + "|-----------|-------------|\n", + "| `llm_data` | Path to LLM candidate metadata |\n", + "| `routing_data_test` | Path to test routing data |" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Current Configuration:\n", + "==================================================\n", + "data_path:\n", + " llm_data: data/example_data/llm_candidates/default_llm.json\n", + " llm_embedding_data: data/example_data/llm_candidates/default_llm_embeddings.json\n", + " query_data_test: data/example_data/query_data/default_query_test.jsonl\n", + " query_data_train: data/example_data/query_data/default_query_train.jsonl\n", + " query_embedding_data: data/example_data/routing_data/query_embeddings_longformer.pt\n", + " routing_data_test: data/example_data/routing_data/default_routing_test_data.jsonl\n", + " routing_data_train: data/example_data/routing_data/default_routing_train_data.jsonl\n", + "metric:\n", + " weights:\n", + " cost: 0\n", + " llm_judge: 0\n", + " performance: 1\n", + "\n" + ] + } + ], + "source": [ + "CONFIG_PATH = \"configs/model_config_test/smallest_llm.yaml\"\n", + "\n", + "with open(CONFIG_PATH, 'r') as f:\n", + " config = yaml.safe_load(f)\n", + "\n", + "print(\"Current Configuration:\")\n", + "print(\"=\" * 50)\n", + "print(yaml.dump(config, default_flow_style=False))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Initialize Router" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "✅ MetaRouter initialized successfully (YAML + data loaded).\n", + "✅ SmallestLLM initialized successfully\n", + "Router initialized successfully!\n", + "Number of LLM candidates: 7\n" + ] + } + ], + "source": [ + "router = SmallestLLM(yaml_path=CONFIG_PATH)\n", + "\n", + "print(\"Router initialized successfully!\")\n", + "print(f\"Number of LLM candidates: {len(router.llm_data)}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Available LLM Candidates (by size):\n", + "============================================================\n", + "1. qwen2.5-7b-instruct: 7BB parameters <- SMALLEST\n", + "2. llama-3.1-8b-instruct: 8BB parameters\n", + "3. mistral-7b-instruct-v0.3: 7BB parameters\n", + "4. llama-3.3-nemotron-super-49b-v1: 49BB parameters\n", + "5. llama3-70b-instruct: 70BB parameters\n", + "6. mixtral-8x7b-instruct-v0.1: 45BB parameters\n", + "7. mixtral-8x22b-instruct-v0.1: 141BB parameters\n" + ] + } + ], + "source": [ + "# Display available LLM candidates sorted by size\n", + "print(\"Available LLM Candidates (by size):\")\n", + "print(\"=\" * 60)\n", + "\n", + "llm_list = [(name, info.get('size', 'unknown')) for name, info in router.llm_data.items()]\n", + "llm_list_sorted = sorted(llm_list, key=lambda x: float(x[1]) if isinstance(x[1], (int, float)) else 0)\n", + "\n", + "for i, (name, size) in enumerate(llm_list_sorted, 1):\n", + " marker = \" <- SMALLEST\" if i == 1 else \"\"\n", + " print(f\"{i}. {name}: {size}B parameters{marker}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Query Routing\n", + "\n", + "SmallestLLM always routes to the smallest model, regardless of query complexity." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Routing Results:\n", + "============================================================\n", + "1. What is 2 + 2?...\n", + " Routed to: qwen2.5-7b-instruct\n", + "\n", + "2. Explain the theory of general relativity....\n", + " Routed to: qwen2.5-7b-instruct\n", + "\n", + "3. Prove P != NP....\n", + " Routed to: qwen2.5-7b-instruct\n", + "\n" + ] + } + ], + "source": [ + "EXAMPLE_QUERIES = [\n", + " {\"query\": \"What is 2 + 2?\"}, # Simple\n", + " {\"query\": \"Explain the theory of general relativity.\"}, # Medium\n", + " {\"query\": \"Prove P != NP.\"}, # Complex\n", + "]\n", + "\n", + "print(\"Routing Results:\")\n", + "print(\"=\" * 60)\n", + "\n", + "for i, query in enumerate(EXAMPLE_QUERIES, 1):\n", + " result = router.route_single(query)\n", + " print(f\"{i}. {query['query'][:50]}...\")\n", + " print(f\" Routed to: {result['model_name']}\")\n", + " print()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Batch Routing" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Routing 10 test queries...\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "\n", + "\u001b[1;31mGive Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new\u001b[0m\n", + "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.\n", + "\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "\n", + "Routing Distribution:\n", + " qwen2.5-7b-instruct: 10 (100.0%)\n" + ] + } + ], + "source": [ + "# Route test data\n", + "test_queries = router.routing_data_test[:10]\n", + "\n", + "print(f\"Routing {len(test_queries)} test queries...\")\n", + "results = router.route_batch(test_queries.to_dict('records'))\n", + "\n", + "print(f\"\\nRouting Distribution:\")\n", + "from collections import Counter\n", + "model_counts = Counter([r['model_name'] for r in results])\n", + "for model, count in model_counts.most_common():\n", + " print(f\" {model}: {count} ({100*count/len(results):.1f}%)\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. Evaluation" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Evaluation Metrics:\n", + "--------------------------------------------------\n", + "Total queries: 10\n", + "Evaluated queries: 10\n", + "Average score (em_mc): 0.0000\n", + "\n", + "Detailed Scores:\n", + "--------------------------------------------------\n", + " 1. Score: 0.0000 | pred: Let's use the given clues to d | ground truth: C\n", + " 2. Score: 0.0000 | pred: Based on the given clues, let' | ground truth: A\n", + " 3. Score: 0.0000 | pred: Based on the given clues, let' | ground truth: A\n", + " 4. Score: 0.0000 | pred: API Error: litellm.Timeout: AP | ground truth: D\n", + " 5. Score: 0.0000 | pred: To solve this logic puzzle, we | ground truth: D\n" + ] + } + ], + "source": [ + "from llmrouter.evaluation import evaluate_batch\n", + "from collections import Counter\n", + "\n", + "eval_data = []\n", + "for r in results:\n", + " if r.get('ground_truth'):\n", + " eval_data.append({\n", + " 'prediction': r.get('response', ''),\n", + " 'ground_truth': r.get('ground_truth'),\n", + " 'metric': r.get('metric', 'em')\n", + " })\n", + "\n", + "if eval_data:\n", + " eval_results = evaluate_batch(eval_data)\n", + " scores = [r['score'] for r in eval_results]\n", + " avg_score = sum(scores) / len(scores) if scores else 0\n", + "else:\n", + " eval_results = []\n", + " avg_score = 0\n", + "\n", + "\n", + "routing_dist = Counter(r['model_name'] for r in results)\n", + "\n", + "print(\"\\nEvaluation Metrics:\")\n", + "print(\"-\" * 50)\n", + "print(f\"Total queries: {len(results)}\")\n", + "print(f\"Evaluated queries: {len(eval_data)}\")\n", + "print(f\"Average score ({eval_data[0]['metric'] if eval_data else 'N/A'}): {avg_score:.4f}\")\n", + "\n", + "if eval_results:\n", + " print(\"\\nDetailed Scores:\")\n", + " print(\"-\" * 50)\n", + " for i, r in enumerate(eval_results[:5], 1):\n", + " pred = r.get('prediction', '')[:30]\n", + " gt = r.get('ground_truth', '')[:30]\n", + " print(f\" {i}. Score: {r['score']:.4f} | pred: {pred} | ground truth: {gt}\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7. File-Based Inference\n", + "\n", + "Load queries from a custom file and save results." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loaded 706 queries from: data/example_data/query_data/default_query_test.jsonl\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "\n", + "\u001b[1;31mGive Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new\u001b[0m\n", + "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.\n", + "\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "\n", + "\u001b[1;31mGive Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new\u001b[0m\n", + "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.\n", + "\n", + "Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n", + "Routed 10 queries\n", + "Results saved to: outputs/smallest_llm_results.jsonl\n", + "\n", + "Sample results:\n", + " 1. Q: There are 4 houses in a row, numbered... -> qwen2.5-7b-instruct\n", + " 2. Q: There are 3 houses in a row, numbered... -> qwen2.5-7b-instruct\n", + " 3. Q: There are 3 houses in a row, numbered... -> qwen2.5-7b-instruct\n" + ] + } + ], + "source": [ + "import json\n", + "\n", + "# Load queries from a JSONL file\n", + "def load_queries_from_file(file_path):\n", + " \"\"\"Load queries from a JSONL file.\"\"\"\n", + " queries = []\n", + " with open(file_path, 'r', encoding='utf-8') as f:\n", + " for line in f:\n", + " if line.strip():\n", + " queries.append(json.loads(line))\n", + " return queries\n", + "\n", + "# Save results to a JSONL file\n", + "def save_results_to_file(results, output_path):\n", + " \"\"\"Save routing results to a JSONL file.\"\"\"\n", + " os.makedirs(os.path.dirname(output_path), exist_ok=True)\n", + " with open(output_path, 'w', encoding='utf-8') as f:\n", + " for result in results:\n", + " f.write(json.dumps(result, ensure_ascii=False) + '\\n')\n", + " print(f\"Results saved to: {output_path}\")\n", + "\n", + "# Example: Load from your own query file\n", + "QUERY_FILE = \"data/example_data/query_data/default_query_test.jsonl\"\n", + "OUTPUT_FILE = \"outputs/smallest_llm_results.jsonl\"\n", + "\n", + "if os.path.exists(QUERY_FILE):\n", + " # Load queries from file\n", + " file_queries = load_queries_from_file(QUERY_FILE)\n", + " print(f\"Loaded {len(file_queries)} queries from: {QUERY_FILE}\")\n", + " \n", + " # Route queries using route_batch\n", + " file_results = router.route_batch(batch=file_queries[:10])\n", + " print(f\"Routed {len(file_results)} queries\")\n", + " \n", + " # Save results to file\n", + " save_results_to_file(file_results, OUTPUT_FILE)\n", + " \n", + " # Show sample results\n", + " print(f\"\\nSample results:\")\n", + " for i, result in enumerate(file_results[:3], 1):\n", + " print(f\" {i}. {result.get('query', '')[:40]}... -> {result['model_name']}\")\n", + "else:\n", + " print(f\"Query file not found: {QUERY_FILE}\")\n", + " print(\"Create a JSONL file with format: {\\\"query\\\": \\\"Your question\\\"}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "**SmallestLLM Router**:\n", + "- Always routes to the smallest model\n", + "- No training required (deterministic baseline)\n", + "- Provides lower bound for performance, upper bound for cost efficiency\n", + "- Useful for comparing against learned routing methods\n", + "\n", + "**Use Cases**:\n", + "- Baseline comparison for routing research\n", + "- Cost-critical applications with simple queries\n", + "- Latency-sensitive scenarios" + ] + } + ], + "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.13.11" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/colab_notebooks/svmrouter/01_svmrouter_training_and_inference.ipynb b/colab_notebooks/svmrouter/01_svmrouter_training_and_inference.ipynb new file mode 100644 index 0000000..47c816b --- /dev/null +++ b/colab_notebooks/svmrouter/01_svmrouter_training_and_inference.ipynb @@ -0,0 +1 @@ +{"cells":[{"cell_type":"markdown","metadata":{"id":"VPyWw60-Itiq"},"source":["# SVMRouter - Training\n","\n","This notebook demonstrates how to train the **SVMRouter** (Support Vector Machine Router).\n","\n","## Overview\n","\n","SVMRouter uses a Support Vector Machine classifier to route queries to the most suitable LLM based on:\n","- Query embeddings (using Longformer)\n","- Historical performance data\n","\n","**Key Features**:\n","- Effective in high-dimensional spaces\n","- Works well with clear margin of separation\n","- Supports probability estimation"]},{"cell_type":"markdown","metadata":{"id":"QzctB1gtItir"},"source":["## 1. Environment Setup"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"VPhE0-87Itis"},"outputs":[],"source":["# Install required packages (for Colab)\n","\n","!git clone https://github.com/ulab-uiuc/LLMRouter.git\n","%cd LLMRouter\n","!pip install -e .\n","!pip install scikit-learn transformers torch"]},{"cell_type":"code","source":["import os\n","os.environ['OPENAI_API_KEY'] = 'your-key'\n","os.environ['ANTHROPIC_API_KEY'] = 'your-key'\n","# Or for multiple keys:\n","os.environ['API_KEYS'] = '[\"key1\", \"key2\"]'"],"metadata":{"id":"F_uny0fGIvW9"},"execution_count":null,"outputs":[]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"pe68wx9LItit","executionInfo":{"status":"ok","timestamp":1767599749534,"user_tz":-480,"elapsed":11636,"user":{"displayName":"dai","userId":"16597391218515739453"}},"outputId":"997ce0fa-bbb6-42c9-fe2c-6e1ae8b7a995"},"outputs":[{"output_type":"stream","name":"stderr","text":["WARNING:torchao.kernel.intmm:Warning: Detected no triton, on systems without Triton certain kernels will not work\n"]},{"output_type":"stream","name":"stdout","text":["Environment setup complete!\n"]}],"source":["# Import required modules\n","from llmrouter.models.svmrouter import SVMRouter, SVMRouterTrainer\n","from llmrouter.utils import setup_environment\n","\n","setup_environment()\n","print(\"Environment setup complete!\")"]},{"cell_type":"markdown","metadata":{"id":"RLZl4UqrItit"},"source":["## 2. Configuration\n","\n","SVMRouter uses the following configuration parameters:\n","\n","| Parameter | Description | Default |\n","|-----------|-------------|--------|\n","| `kernel` | Kernel type: \"rbf\", \"linear\", \"poly\", \"sigmoid\" | \"rbf\" |\n","| `C` | Regularization parameter | 1.0 |\n","| `gamma` | Kernel coefficient | \"scale\" |\n","| `probability` | Enable probability estimates | true |"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"M52wUvkRItit","executionInfo":{"status":"ok","timestamp":1767599758684,"user_tz":-480,"elapsed":9,"user":{"displayName":"dai","userId":"16597391218515739453"}},"outputId":"a8949d3c-6f98-4b84-da23-e7645432996e"},"outputs":[{"output_type":"stream","name":"stdout","text":["Current Configuration:\n","==================================================\n","data_path:\n"," llm_data: data/example_data/llm_candidates/default_llm.json\n"," llm_embedding_data: data/example_data/llm_candidates/default_llm_embeddings.json\n"," query_data_test: data/example_data/query_data/default_query_test.jsonl\n"," query_data_train: data/example_data/query_data/default_query_train.jsonl\n"," query_embedding_data: data/example_data/routing_data/query_embeddings_longformer.pt\n"," routing_data_test: data/example_data/routing_data/default_routing_test_data.jsonl\n"," routing_data_train: data/example_data/routing_data/default_routing_train_data.jsonl\n","hparam:\n"," C: 1.0\n"," gamma: scale\n"," kernel: rbf\n"," probability: true\n","metric:\n"," weights:\n"," cost: 0\n"," llm_judge: 0\n"," performance: 1\n","model_path:\n"," ini_model_path: ''\n"," save_model_path: saved_models/svmrouter/svmrouter.pkl\n","\n"]}],"source":["import yaml\n","\n","# Configuration file path\n","CONFIG_PATH = \"configs/model_config_train/svmrouter.yaml\"\n","\n","# Load and display configuration\n","with open(CONFIG_PATH, 'r') as f:\n"," config = yaml.safe_load(f)\n","\n","print(\"Current Configuration:\")\n","print(\"=\" * 50)\n","print(yaml.dump(config, default_flow_style=False))"]},{"cell_type":"markdown","metadata":{"id":"M8B1fwt_Itiu"},"source":["## 3. Initialize Router"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"HR58OOHlItiu","executionInfo":{"status":"ok","timestamp":1767599763926,"user_tz":-480,"elapsed":2591,"user":{"displayName":"dai","userId":"16597391218515739453"}},"outputId":"7a5d5ec2-bad1-4005-c0b8-deffe6bc9d03"},"outputs":[{"output_type":"stream","name":"stdout","text":["✅ MetaRouter initialized successfully (YAML + data loaded).\n","Router initialized successfully!\n","Number of training samples: 50544\n","Number of LLM candidates: 14\n","LLM candidates: ['qwen2.5-7b-instruct', 'codegemma-7b', 'gemma-2-9b-it', 'llama-3.1-8b-instruct', 'llama3-chatqa-1.5-8b', 'mistral-7b-instruct-v0.3', 'llama-3.3-nemotron-super-49b-v1', 'llama-3.1-nemotron-51b-instruct', 'llama3-chatqa-1.5-70b', 'llama3-70b-instruct', 'mixtral-8x7b-instruct-v0.1', 'mixtral-8x22b-instruct-v0.1', 'palmyra-creative-122b', 'mistral-nemo-12b-instruct']\n"]}],"source":["# Initialize SVMRouter with configuration\n","router = SVMRouter(yaml_path=CONFIG_PATH)\n","\n","print(\"Router initialized successfully!\")\n","print(f\"Number of training samples: {len(router.routing_data_train)}\")\n","print(f\"Number of LLM candidates: {len(router.llm_data)}\")\n","print(f\"LLM candidates: {list(router.llm_data.keys())}\")"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"JxHJb7b2Itiu","executionInfo":{"status":"ok","timestamp":1767599765863,"user_tz":-480,"elapsed":13,"user":{"displayName":"dai","userId":"16597391218515739453"}},"outputId":"1268a7ef-0a55-4adb-fb83-de03e8845519"},"outputs":[{"output_type":"stream","name":"stdout","text":["SVM Model Parameters:\n","{'C': 1.0, 'break_ties': False, 'cache_size': 200, 'class_weight': None, 'coef0': 0.0, 'decision_function_shape': 'ovr', 'degree': 3, 'gamma': 'scale', 'kernel': 'rbf', 'max_iter': -1, 'probability': True, 'random_state': None, 'shrinking': True, 'tol': 0.001, 'verbose': False}\n"]}],"source":["# Inspect the SVM model configuration\n","print(\"SVM Model Parameters:\")\n","print(router.svm_model.get_params())"]},{"cell_type":"markdown","metadata":{"id":"Le-mo7D8Itiu"},"source":["## 4. Training"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"nRk-fdovItiv","executionInfo":{"status":"ok","timestamp":1767599773294,"user_tz":-480,"elapsed":18,"user":{"displayName":"dai","userId":"16597391218515739453"}},"outputId":"913ea5b1-96ce-4091-a543-8ed7797fae04"},"outputs":[{"output_type":"stream","name":"stdout","text":["[SVMRouterTrainer] Initialized with router.\n","Trainer initialized!\n","Training samples: 5608\n","Save path: /content/LLMRouter/llmrouter/saved_models/svmrouter/svmrouter.pkl\n"]}],"source":["# Initialize trainer\n","trainer = SVMRouterTrainer(router=router, device='cpu')\n","\n","print(\"Trainer initialized!\")\n","print(f\"Training samples: {len(trainer.query_embedding_list)}\")\n","print(f\"Save path: {trainer.save_model_path}\")"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"AK4LdS8nItiv","executionInfo":{"status":"ok","timestamp":1767599835501,"user_tz":-480,"elapsed":55061,"user":{"displayName":"dai","userId":"16597391218515739453"}},"outputId":"7c3d871a-44e0-491e-9bd7-12c4ae6438fd"},"outputs":[{"output_type":"stream","name":"stdout","text":["Starting training...\n","==================================================\n","Successfully saved pickle model: /content/LLMRouter/llmrouter/saved_models/svmrouter/svmrouter.pkl\n","==================================================\n","Training completed!\n"]}],"source":["# Train the model\n","print(\"Starting training...\")\n","print(\"=\" * 50)\n","\n","trainer.train()\n","\n","print(\"=\" * 50)\n","print(\"Training completed!\")"]},{"cell_type":"markdown","metadata":{"id":"sca-aXmbItiv"},"source":["## 5. Model Verification"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"jeQN5vu-Itiv","executionInfo":{"status":"ok","timestamp":1767599858332,"user_tz":-480,"elapsed":35,"user":{"displayName":"dai","userId":"16597391218515739453"}},"outputId":"86bcd405-11c3-4e82-caf4-e640db4836f7"},"outputs":[{"output_type":"stream","name":"stdout","text":["Successfully loaded pickle model: /content/LLMRouter/llmrouter/saved_models/svmrouter/svmrouter.pkl\n","Model loaded successfully!\n","Model type: SVC\n","Number of support vectors: [ 368 1075 794 254 171 60 165 158 1594]\n","Classes: ['codegemma-7b' 'gemma-2-9b-it' 'llama-3.1-8b-instruct'\n"," 'llama-3.1-nemotron-51b-instruct' 'llama-3.3-nemotron-super-49b-v1'\n"," 'llama3-chatqa-1.5-70b' 'llama3-chatqa-1.5-8b' 'mistral-7b-instruct-v0.3'\n"," 'qwen2.5-7b-instruct']\n"]}],"source":["# Verify the trained model\n","from llmrouter.utils import load_model\n","import numpy as np\n","\n","# Load the saved model\n","saved_model = load_model(trainer.save_model_path)\n","\n","print(\"Model loaded successfully!\")\n","print(f\"Model type: {type(saved_model).__name__}\")\n","print(f\"Number of support vectors: {saved_model.n_support_}\")\n","print(f\"Classes: {saved_model.classes_}\")"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"mBtpoeiKItiv","executionInfo":{"status":"ok","timestamp":1767599864530,"user_tz":-480,"elapsed":23,"user":{"displayName":"dai","userId":"16597391218515739453"}},"outputId":"1f37360d-52ae-4755-f31c-113f8750a174"},"outputs":[{"output_type":"stream","name":"stdout","text":["Test prediction: qwen2.5-7b-instruct\n","\n","Prediction probabilities:\n"," codegemma-7b: 0.0199\n"," gemma-2-9b-it: 0.1210\n"," llama-3.1-8b-instruct: 0.1903\n"," llama-3.1-nemotron-51b-instruct: 0.0542\n"," llama-3.3-nemotron-super-49b-v1: 0.0306\n"," llama3-chatqa-1.5-70b: 0.0131\n"," llama3-chatqa-1.5-8b: 0.0301\n"," mistral-7b-instruct-v0.3: 0.0272\n"," qwen2.5-7b-instruct: 0.5136\n"]}],"source":["# Quick prediction test\n","test_embedding = trainer.query_embedding_list[0].reshape(1, -1)\n","prediction = saved_model.predict(test_embedding)\n","\n","print(f\"Test prediction: {prediction[0]}\")\n","\n","# Get prediction probabilities\n","proba = saved_model.predict_proba(test_embedding)\n","print(f\"\\nPrediction probabilities:\")\n","for model, prob in zip(saved_model.classes_, proba[0]):\n"," print(f\" {model}: {prob:.4f}\")"]},{"cell_type":"markdown","metadata":{"id":"_nt70M30Itiv"},"source":["## 6. Hyperparameter Tuning"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"_et3TxImItiw","executionInfo":{"status":"ok","timestamp":1767600711703,"user_tz":-480,"elapsed":843048,"user":{"displayName":"dai","userId":"16597391218515739453"}},"outputId":"9d98f73a-90f2-4090-a025-a0cae1f3d71e"},"outputs":[{"output_type":"stream","name":"stdout","text":["Running grid search (this may take a while)...\n","Fitting 3 folds for each of 12 candidates, totalling 36 fits\n","\n","Best parameters: {'C': 1, 'gamma': 'scale', 'kernel': 'rbf'}\n","Best score: 0.5141\n"]}],"source":["from sklearn.model_selection import GridSearchCV\n","from sklearn.svm import SVC\n","import numpy as np\n","\n","# Prepare data\n","X = np.array(trainer.query_embedding_list)\n","y = np.array(trainer.model_name_list)\n","\n","# Define parameter grid\n","param_grid = {\n"," 'C': [0.1, 1, 10],\n"," 'kernel': ['rbf', 'linear'],\n"," 'gamma': ['scale', 'auto']\n","}\n","\n","print(\"Running grid search (this may take a while)...\")\n","\n","# Grid search\n","svm = SVC(probability=True)\n","grid_search = GridSearchCV(svm, param_grid, cv=3, scoring='accuracy', verbose=1, n_jobs=-1)\n","grid_search.fit(X, y)\n","\n","print(f\"\\nBest parameters: {grid_search.best_params_}\")\n","print(f\"Best score: {grid_search.best_score_:.4f}\")"]},{"cell_type":"markdown","metadata":{"id":"SvywNxyUItiw"},"source":["## Summary\n","\n","In this notebook, we:\n","\n","1. **Loaded Configuration**: Set up SVMRouter with YAML configuration\n","2. **Trained Model**: Used SVMRouterTrainer to fit the SVM classifier\n","3. **Verified Model**: Loaded and tested the saved model\n","4. **Tuned Hyperparameters**: Found optimal parameters using grid search\n","\n","**Next Steps**:\n","- Use `02_svmrouter_inference.ipynb` for inference\n","- Compare with other routers (KNN, MLP)"]},{"cell_type":"markdown","source":["# SVMRouter - Inference\n","\n","This notebook demonstrates how to use a trained **SVMRouter** for inference."],"metadata":{"id":"95CDrzubKbyq"}},{"cell_type":"markdown","source":["## 1. Environment Setup"],"metadata":{"id":"1ddN0Y9DKeYn"}},{"cell_type":"code","source":["from llmrouter.models.svmrouter import SVMRouter\n","from llmrouter.utils import setup_environment, load_model, get_longformer_embedding\n","import yaml\n","\n","setup_environment()"],"metadata":{"id":"c-GHL-SaKf9a"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":["## 2. Load Trained Router"],"metadata":{"id":"-JvVa1uzKg3b"}},{"cell_type":"code","source":["# Configuration\n","CONFIG_PATH = \"configs/model_config_train/svmrouter.yaml\"\n","\n","with open(CONFIG_PATH, 'r') as f:\n"," config = yaml.safe_load(f)\n","\n","config['model_path']['load_model_path'] = config['model_path']['save_model_path']\n","\n","# Create inference config\n","INFERENCE_CONFIG_PATH = \"configs/model_config_test/svmrouter_inference.yaml\"\n","os.makedirs(os.path.dirname(INFERENCE_CONFIG_PATH), exist_ok=True)\n","\n","with open(INFERENCE_CONFIG_PATH, 'w') as f:\n"," yaml.dump(config, f)\n","\n","# Initialize router\n","router = SVMRouter(yaml_path=INFERENCE_CONFIG_PATH)\n","print(f\"Router loaded with {len(router.llm_data)} LLM candidates\")"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"KuqtZR_PKip_","executionInfo":{"status":"ok","timestamp":1767600854157,"user_tz":-480,"elapsed":2723,"user":{"displayName":"dai","userId":"16597391218515739453"}},"outputId":"c3b9d2d0-96c4-4866-a682-462eb213a741"},"execution_count":null,"outputs":[{"output_type":"stream","name":"stdout","text":["✅ MetaRouter initialized successfully (YAML + data loaded).\n","Router loaded with 14 LLM candidates\n"]}]},{"cell_type":"markdown","source":["## 3. Single Query Routing"],"metadata":{"id":"ZDpFtj3SKjxu"}},{"cell_type":"code","source":["# Example queries\n","EXAMPLE_QUERIES = [\n"," {\"query\": \"What is the capital of France?\"},\n"," {\"query\": \"Solve the equation: 2x + 5 = 15\"},\n"," {\"query\": \"Write a Python function to check if a number is prime.\"},\n"," {\"query\": \"Explain quantum computing in simple terms.\"},\n","]\n","\n","print(\"Routing Results:\")\n","print(\"=\" * 60)\n","\n","for i, query in enumerate(EXAMPLE_QUERIES, 1):\n"," result = router.route_single(query)\n"," print(f\"{i}. {query['query'][:50]}...\")\n"," print(f\" Routed to: {result['model_name']}\")"],"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":607,"referenced_widgets":["da404c4ac9a84df3ab2a7034ac6d020c","93208c9976d74f1188c07366c77f1dcf","c362e25195454138a895ed1094f6a73f","2deb10f48fc0485db2504c855d0ccdde","3c9fb7381f1b4af5b81f4b06b30a4d9e","5919478a94df4b31a0c0fc4ba3d003e6","90ad95c6d79549f7ac19a70b11867dbf","69bdd2a99fc845a1bc1719184e884de9","89bca093ec5c4985bab7707c32413b3c","75d361e9ddbf40d6bf259ed47b47aef6","0ae3636c4167451c8f4bb3c94309f447","bdbd946cf4b64a64be5b78245481c6cb","95ff9a66527b4d1cad3f1d8b7ab9e15b","7e1fd7f45f304eae81775164a575fa1e","df91ed87541f4a0488ed5ecfcc85b0c1","82facacd6d194bc4b461709bff3eaf03","dda999afbad74235b6bd053dc7247171","872dc0a75d1a4743ad3b1ac30375482f","7987ef2a0955487381c30e90401f5e34","60dd33908255484b8d0d4a822bb54001","2e838cfa18694f62ae4851057b04c806","e9b27ea6331b479ea8c875816838c5f9","0844fe9066ae4f54bcd0812668041f63","0f751e627f614d57b32ed78fc249a645","719c269d2585472e9e40131999f331f8","5d53177942e748338cab4fc5e7a08328","6c18b164a7e04045a732f9ab21d2aacc","74ad759512c449c281f3545bca774628","eff9aae276854383b304b349b424480d","25a75f3251674b5a9f90e46adab2313a","94550edd51764620a90ff632c56b2f6f","413d9b5caeb6486f9cb96fc0a7790365","244a80df857b4b9da80dca996fde039b","7aee6632e8ad465d9cab9fd1473d888d","d1afa21f3529436e96d18ad132e6dd6d","4f3382442979487093bf88a7e62fe13a","bff76e5b3b9b4f9cb2ef26e1ee32cb08","7be2ed7953034a8fb9c1085f32976ab0","af9522f82d2b4a288b9ff5ba84ca4cfc","558c47054eda4942949f4ea4249078df","e6f52045f99645b2914e7b33219ae704","a2523255cf1d457b874f9e5f656527f6","f87f7a4a497049989652e197913b4b6f","7a6f1c6a8f7f4672a02020ed26e8577d","9d82e991fd894a4c95b1d8c7f1bc4a89","8248110b8b1f4eed9dd8232f60974f71","adccb019b8c44d4688e665d46b5d6bec","50de4684f97e45f0b930d122976899a4","3e2c5b913f9149f6af7ae165db75d9c8","43845319e7ea43a581e2d0d8cb1015a1","9ac96aa4bcfa467ea2026e2b75582bd0","e430ac8dd4e646bab05938f6e4a9112e","383be83a57d74188a6fa3a3010b8f7c2","9c11c57c6d61439bb2d780e61dca6263","0da5671d241741f1a54012d7bc60af77","559224923db24606be570cc6f884b2c8","1d337a87d7324a3cace6a65267cf4946","af70e95c3133440ea79988dae54d6ef1","6f35ad1b2c0f40b68edce1eed921b235","0e3cbb53e5694c33877c16b6a0ef9a57","0a095b25a25c4adfb7eb60ce95b32f13","1a58c08e494a4d2aa87cddf104533d24","7cc465645ec848878b711770f153b38c","a55bdca19a5c4c5cad53ef9565f40717","b13a95c186c444f5b2c131f8a1d0ebc6","4d0acc4bf9e24eaca863e39dc230e029"]},"id":"uXcS5X69Kk_S","executionInfo":{"status":"ok","timestamp":1767600880742,"user_tz":-480,"elapsed":24040,"user":{"displayName":"dai","userId":"16597391218515739453"}},"outputId":"b44c737b-1b89-4d83-ecba-5d7053285c78"},"execution_count":null,"outputs":[{"output_type":"stream","name":"stdout","text":["Routing Results:\n","============================================================\n","Successfully loaded pickle model: /content/LLMRouter/llmrouter/saved_models/svmrouter/svmrouter.pkl\n"]},{"output_type":"stream","name":"stderr","text":["/usr/local/lib/python3.12/dist-packages/huggingface_hub/utils/_auth.py:94: UserWarning: \n","The secret `HF_TOKEN` does not exist in your Colab secrets.\n","To authenticate with the Hugging Face Hub, create a token in your settings tab (https://huggingface.co/settings/tokens), set it as secret in your Google Colab and restart your session.\n","You will be able to reuse this secret in all of your notebooks.\n","Please note that authentication is recommended but still optional to access public models or datasets.\n"," warnings.warn(\n"]},{"output_type":"display_data","data":{"text/plain":["config.json: 0%| | 0.00/694 [00:00"],"image/png":"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YO3atUqj3UD+NPzbt29j6tSpuHbtGpYvX45NmzZhwoQJYp1ly5YpTXuPjo7Gpk2bcPXqVdy+fRubNm2Cv78/+vTpU2S2BRERERERfTyYeH8k8vLyMGbMGDg4OKB9+/aoV6+euMCXhYUFAgMDMX36dJiamsLPz6/EfpydnbF48WIsWLAADRs2RHR0NObPn/9eMSUlJeHEiROlTjOfNGkS4uLi4OLigqCgICxevLjE+1C/D3V1daSlpcHb2xv16tVD79690aFDB3FE2t3dHb6+vujTpw9MTEyKLDRXmKamJn799Vdcu3YNTk5OWLBgAYKCgpTq1KtXD/v370diYiKaN28ONzc37Ny5Exoa+ZNDJk+eDHV1dTg6OsLExAQpKSkl7u/TTz+FsbExkpKS0L9/f6Vttra22LNnDw4cOABnZ2eEhIRg9erVSufuyZMnSteza2hoYMGCBWjevDmcnJwQGBgIPz8/cXo7ERERERF9nCRC4QteiehfLSMjAwYGBrAcvwlqUt0P6is5uFMFRUVERERE9M9T8N06PT39nWspccSbiIiIiIiISIWYeBMRERERERGpEBNvIiIiIiIiIhVi4k1ERERERESkQky8iYiIiIiIiFSIiTcRERERERGRCjHxJiIiIiIiIlIhJt5EREREREREKsTEm4iIiIiIiEiFmHgTERERERERqZBGVQdARJXvUqAX9PX1qzoMIiIiIqL/BI54ExEREREREakQE28iIiIiIiIiFWLiTURERERERKRCTLyJiIiIiIiIVIiJNxEREREREZEKMfEmIiIiIiIiUiEm3kREREREREQqxPt4E/0HNZy1D2pSXZX1nxzcSWV9ExERERH903DEm4iIiIiIiEiFmHgTERERERERqRATbyIiIiIiIiIVYuJNREREREREpEJMvImIiIiIiIhUiIk3ERERERERkQox8SYiIiIiIiJSISbeRERERERERCrExJuIiIiIiIhIhZh4ExEREREREalQhSXebdq0wfjx4wEANjY2WLp0aUV1TVSqgIAANG7cuNztCr9niYiIiIiIVOU/N+KdlJSEtm3bwtTUFNra2qhduza+//575Obmltpu3LhxaNq0KaRS6XsleR+bo0ePQiKR4Pnz51Uah42NDSQSidIjODhY3P769Wv4+PigUaNG0NDQQPfu3Sts39u2bcOcOXMqrD+J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3gwzMzPxGnkbGxscOnQIHh4ekEqlSsfwtk8//RQRERHo0qULDA0NMXPmTKUffTw9PdG6dWt89dVXWLx4Mezs7HDt2jVIJBK0b98eNjY2yMzMxKFDh+Ds7AxdXV1x9ggREREREX1cVJ54Ozs7Y/HixViwYAH8/f3RunVrzJ8/H97e3h/Ur4mJCSIiIvDtt98iNDQUTZo0waJFi9C1a9dS22loaGDBggW4fv06BEGAtbU1/Pz8MGHCBLFOenq6uJJ1gWHDhimtNO7i4gIAuHPnjriy9fuYM2cOTExMMH/+fNy+fRuGhoZo0qQJvv322/fuE8hfKVyhUGDgwIF48eIFXF1dsW/fPjEZtLCwQGBgIKZPn47BgwfD29sbERERxfZlYWGBmJgYTJkyBc7OzjA2NsbQoUPx/ffff1CMUqkUGzduREBAALKzs2Fra4sJEyYoTY0G8hc8u3v3rvi84NwXnqptZ2eHL7/8Eh07dsTTp0/RuXNnpcX7KoJcLscPP/yAGzduQF1dHc2aNUNMTIz440NISAgmTpyIVatWwcLCAsnJySX25e/vjzt37qBz584wMDDAnDlzlEa8AWDr1q2YPHky+vXrh5cvX8LOzk5cEd/d3R2+vr7o06cP0tLSMGvWLN5SjIiIiIjoIyUR3r7QlIj+tTIyMvIXWRu/CWpS1Y2QJwd3UlnfREREREQfg4Lv1unp6UrrfBWnUlY1JyIiIiIiIvqvYuJNREREREREpEJMvImIiIiIiIhUiIk3ERERERERkQox8SYiIiIiIiJSISbeRERERERERCrExJuIiIiIiIhIhZh4ExEREREREakQE28iIiIiIiIiFWLiTURERERERKRCTLyJiIiIiIiIVEijqgMgosp3KdAL+vr6VR0GEREREdF/Ake8iYiIiIiIiFSIiTcRERERERGRCjHxJiIiIiIiIlIhJt5EREREREREKsTEm4iIiIiIiEiFmHgTERERERERqRATbyIiIiIiIiIV4n28if6DGs7aBzWpbqXsKzm4U6Xsh4iIiIjoY8URbyIiIiIiIiIVYuJNREREREREpEJMvImIiIiIiIhUiIk3ERERERERkQox8SYiIiIiIiJSISbeRERERERERCrExJuIiIiIiIhIhZh4ExEREREREakQE28iIiIiIiIiFWLiTURERERERKRC5Uq827Rpg/HjxwMA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kUsnVqxYARcXFwwfPhwA0Lp1a7i4uGDXrl0VdXhERERERKQCEqHwBbZEH5mIiAiMHz9e6V7f9P4yMjLyF1kbvwlq0uJnP1S05OD3m9VBRERERPQxK/hunZ6eDn19/VLrVtiq5kRERERERERUFBNvIiIiIiIiIhVi4k0fNR8fH04zJyIiIiKifzQm3kREREREREQqxMSbiIiIiIiISIWYeBMRERERERGpEBNvIiIiIiIiIhVi4k1ERERERESkQky8iYiIiIiIiFSIiTcRERERERGRCmlUdQBEVPkuBXpBX1+/qsMgIiIiIvpP4Ig3ERERERERkQox8SYiIiIiIiJSISbeRERERERERCrExJuIiIiIiIhIhZh4ExEREREREakQE28iIiIiIiIiFWLiTURERERERKRCTLyJiIiIiIiIVEijqgMgosrXcNY+qEl1qzqMd0oO7lTVIRARERERfTCOeBMRERERERGpEBNvIiIiIiIiIhVi4k1ERERERESkQky8iYiIiIiIiFSIiTcRERERERGRCjHxJiIiIiIiIlIhJt5EREREREREKsTEm4iIiIiIiEiFmHgTERERERERqRATbyIiIiIiIiIVYuJdhdq0aYPx48cDAGxsbLB06dIqjUeVJBIJduzYUdVh/KPxHBIRERER/TMx8SYlaWlpaN++PWrWrAmpVApLS0v4+fkhIyOjqkODj48PunfvXmn7++OPP9ClSxfUrFmzzEnv0aNHIZFIijwePHhQarvMzEz4+fmhVq1a0NHRgaOjI1asWFFBR0JERERERFVJo6oDoI+LmpoaunXrhqCgIJiYmODmzZsYM2YMnj59ig0bNlR1eJXq5cuXcHZ2xpAhQ/Dll1+Wq21SUhL09fXF5zVq1Ci1/sSJE3H48GH88ssvsLGxwf79+zF69GjUrFkTXbt2fa/4iYiIiIjo48AR74/U4sWL0ahRI+jp6cHS0hKjR49GZmamuD0iIgKGhobYvXs37O3toauri549eyIrKwuRkZGwsbGBkZERxo0bh7y8PLFdVFQUXF1dIZfLYWZmhv79++PRo0fidiMjI4waNQqurq6wtrZGu3btMHr0aBw/fvydMa9duxYNGjSAVCqFubk5/Pz8lLY/efIEPXr0gK6uLurWrYtdu3aJ2/Ly8jB06FDY2tpCR0cH9vb2+PHHH8XtAQEBiIyMxM6d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UvseNGgQli5diuXLl6NBgwbo3Lkzbty4UebYRo4ciS+//BJ9+vRBixYtkJaWpjRCDQDDhw+Hvb09XF1dYWJigtjYWMhkMvzvf//DxYsX4eLigu+++w4LFiwod9/FiYuLg4uLiziiPHHiRLi4uGDmzJlindTUVKXr0HNycjBp0iQ0atQInp6eSExMFK/XL83GjRvRrFkzDBgwQHxt5s6dC19fX6V6gYGB2LhxI5ycnLB+/Xr8+uuvcHR0fOexEBERERFR1ZEIgiBUdRBEFSk5ORm2tra4cOECGjduXNXhfFQyMjLyF1kbvwlqUt2qDuedkoM7VXUIRERERETFKvhunZ6eDn19/VLrcsSbiIiIiIiISIWYeBMRERERERGpEBdXo38dGxsb8AoKIiIiIiL6WHDEm4iIiIiIiEiFmHgTERERERERqRATbyIiIiIiIiIVYuJNREREREREpEJMvImIiIiIiIhUiIk3ERERERERkQox8SYiIiIiIiJSId7Hm+g/6FKgF/T19as6DCIiIiKi/wSOeBMRERERERGpEBNvIiIiIiIiIhVi4k1ERERERESkQky8iYiIiIiIiFSIiTcRERERERGRCjHxJiIiIiIiIlIhJt5EREREREREKsTEm4iIiIiIiEiFNKo6ACKqfA1n7YOaVLeqw6gUycGdqjoEIiIiIvqP44g3ERERERERkQox8SYiIiIiIiJSISbeRERERERERCrExJuIiIiIiIhIhZh4ExEREREREakQE28iIiIiIiIiFWLiTURERERERKRCTLyJiIiIiIiIVIiJNxEREREREZEKMfEmIiIiIiIiUiEm3h+RNm3aYPz48QAAGxsbLF26tErj+afw8fFB9+7dy92O55iIiIiIiCoDE28qt6SkJLRt2xampqbQ1tZG7dq18f333yM3N7fUduPGjUPTpk0hlUrRuHHjMu8vOjoazs7O0NXVhbm5OYYMGYK0tLQPPArg3LlzGDFixAf3AwDJycmQSCRISEiokP7eJSAgoFznkIiIiIiIqg4Tbyo3TU1NeHt7Y//+/UhKSsLSpUuxatUqzJo1651thwwZgj59+pR5X7GxsfD29sbQoUNx+fJlbN68GWfPnsXw4cM/5BAAACYmJtDV1f3gfsojJyenUvdHRERERERVj4n3P8TixYvRqFEj6OnpwdLSEqNHj0ZmZqa4PSIi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T5QWUCMiIiIioo8LE+9/AGdnZyxevBgLFixAw4YNER0djfnz539wvyYmJoiIiMDmzZvh6OiI4ODgMl2nraGhgQULFqB58+ZwcnJCYGAg/Pz8sHr1arFOeno6kpKSlNoNGzYMLi4uWLlyJa5fvw4XFxe4uLjg/v37Je7Lx8cHixcvxrJly9CwYUP06tUL9vb22LZtm1K9du3aoW7dumjdujX69OmDrl27IiAgoHwn5B20tLTg7+8PJycntG7dGurq6ti4cSOA/HMSGhqKlStXombNmujWrVupfYWEhMDS0hKtWrVC//79MXnyZKVr1LW0tLB//37UqFEDHTt2RKNGjRAcHCyO9n/11Vdo37492rZtCxMTE/z6668VeqxERERERFRxJMLbF5oS0b9WRkYGDAwMYDl+E9Skuu9u8C+QHNypqkMgIiIion+hgu/W6enp4hpVJeGINxEREREREZEKMfEmIiIiIiIiUiEm3kREREREREQqxMSbiIiIiIiISIWYeBMRERERERGpEBNvIiIiIiIiIhVi4k1ERERERESkQky8iYiIiIiIiFSIiTcRERERERGRCjHxJiIiIiIiIlIhjaoOgIgq36VAL+jr61d1GERERERE/wkc8SYiIiIiIiJSISbeRERERERERCrExJuIiIiIiIhIhZh4ExEREREREakQE28iIiIiIiIiFWLiTURERERERKRCTLyJiIiIiIiIVIj38Sb6D2o4ax/UpLpVHQYVIzm4U1WHQEREREQVjCPeRERERERERCrExJuIiIiIiIhIhZh4ExEREREREakQE28iIiIiIiIiFWLiTURERERERKRCTLyJiIiIiIiIVIiJNxEREREREZEKMfEmIiIiIiIiUiEm3kREREREREQqxMSbiIiIiIiISIWYeBMRERERERGpEBNvqlQ+Pj7o3r17VYfxj8RzR0RERET0z8TEm+gjEBAQAIlEUuShp6dX1aEREREREdEH0qjqAIgImDx5Mnx9fZXK2rVrh2bNmlVRREREREREVFE44k2lUigU+OGHH2BnZwepVAorKyvMnTsXAHDx4kV8+umn0NHRQbVq1TBixAhkZmaKbfPy8jBx4kQYGhqiWrVqmDp1KgRBKNL//PnzYWtrCx0dHTg7O2PLli1KdXbt2oW6detCW1sbbdu2RWRkJCQSCZ4/fy7WOXHiBFq1agUd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gUdAREREREQVjaua0z/O3LlzsWLFCi4U9h4KVl7kquYfL65qTkRERPTPUJ5VzXmNN330li9fjmbNmqFatWqIjY3FwoULq2QaORERERER0ftg4k0fvRs3biAoKAhPnz6FlZUVJk2aBH9//6oOi4iIiIiIqEyYeNNHb8mSJViyZElVh0FERERERPReuLgaERERERERkQox8SYiIiIiIiJSISbeRERERERERCrExJuIiIiIiIhIhZh4ExEREREREakQE28iIiIiIiIiFWLiTURERERERKRCvI830X/QpUAv6OvrV3UYRERERET/CRzxJiIiIiIiIlIhJt5EREREREREKsTEm4iIiIiIiEiFmHgTERERERERqRATbyIiIiIiIiIVYuJNREREREREpEJMvImIiIiIiIhUiPfxJvoPajhrH9SkulUdBv3DJQd3quoQiIiIiP4ROOJNREREREREpEJMvImIiIiIiIhUiIk3ERERERERkQox8SYiIiIiIiJSISbeRERERERERCrExJuIiIiIiIhIhZh4ExEREREREakQE28iIiIiIiIiFWLiTURERERERKRCTLyJiIiIiIiIVIiJN1Eli4iIgKGhYbnb+fj4oHv37hUeDxERERERqRYTb6Ji5ObmYtq0aWjUqBH09PRQs2ZNeHt74/79++9se+jQIbi7u0Mul8PMzAzTpk3DmzdvPjimH3/8EREREeLzNm3aYPz48R/cLxERERERqRYTb6JiZGVl4fz585gxYwbOnz+Pbdu2ISkpCV27di21XWJiIjp27Ij27dvjwoUL+O2337Br1y5Mnz79g2MyMDB4r5FyIiIiIiKqWky86b28ePECAwYMgJ6eHszNzbFkyRKlEdjs7GxMnjwZFhYW0NPTQ4sWLXD06FGxfcF06927d8Pe3h66urro2bMnsrKyEBkZCRsbGxgZGWHcuHHIy8sT29nY2CAoKAje3t6QyWSwtrbGrl278PjxY3Tr1g0ymQxOTk6Ii4sT26SlpaFfv36wsLCArq4uGjVqhF9//bXU4zMwMMCBAwfQu3dv2Nvb45NPPsGyZcsQHx+PlJSUEtv99ttvcHJywsyZM2FnZwdPT0/88MMP+Omnn/DixQulujt27EDdunWhra0NLy8v3Lt3r9SYCk819/HxwbFjx/Djjz9CIpFAIpEgOTm51PZERERERFQ1mHjTe5k4cSJiY2Oxa9cuHDhwAMePH8f58+fF7X5+fjh16hQ2btyIP//8E7169UL79u1x48YNsU5WVhZCQ0OxceNG7N27F0ePHkWPHj0QExODmJgYREVFYeXKldiyZYvSvpcsWQIPDw9cuHABnTp1wsCBA+Ht7Y2vv/4a58+fR506deDt7Q1BEAAAr1+/RtOmTbFnzx5cunQJI0aMwMCBA3H27NlyHXN6ejokEkmpo87Z2dnQ1tZWKtPR0cHr168RHx+vdOxz587F+vXrERsbi+fPn6Nv375ljuXHH3+Em5sbhg8fjtTUVKSmpsLS0rJcx0NERERERJVDo6oDoH+eFy9eIDIyEhs2bEC7du0AAOvWrUPNmjUBACkpKVi3bh1SUlLEssmTJ2Pv3r1Yt24d5s2bByD/Ourw8HDUqVMHANCzZ09ERUXh4cOHkMlkcHR0RNu2bXHkyBH06dNH3H/Hjh0xcuRIAMDMmTMRHh6OZs2aoVevXgCAadOmwc3NDQ8fPoSZmRksLCwwefJksf3YsWOxb98+bNq0Cc2bNy/TMb9+/RrTpk1Dv379oK+vX2I9Ly8vLF26FL/++it69+6NBw8eYPbs2QCA1NRUsV5ubi6WLVuGFi1aAAAiIyPh4OCAs2fPlikmAwMDaGlpQVdXF2ZmZiXWy87ORnZ2tvg8IyPjnX0TEREREVHF4og3ldvt27eRm5urlCAaGBjA3t4eAHDx4kXk5eWhXr16kMlk4uPYsWO4deuW2EZXV1dMugHA1NQUNjY2kMlkSmWPHj1S2r+Tk5PSdgBo1KhRkbKCdnl5eZgzZw4aNWoEY2NjyGQy7Nu3T5wyHh0drRTn8ePHlfaXm5uL3r17QxAEhIeHi+UdOnQQ2zRo0AAA8MUXX2DhwoXw9fWFVCpFvXr10LFjRwCAmtr/fdw0NDTQrFkz8Xn9+vVhaGiIq1evIiUlRSmegh8q3sf8+fNhYGAgPjgqTkRERERU+TjiTRUuMzMT6urqiI+Ph7q6utK2wkm1pqam0jaJRFJsmUKhUCorXEcikZRYVtBu4cKF+PHHH7F06VJxlfLx48cjJycHANC1a1dx5BkALCwsxH8XJN13797F4cOHlUa7V69ejVevXhXZ/8SJEzFhwgSkpqbCyMgIycnJ8Pf3R+3atYs/YW+pWbMmEhISxOfGxsZlalccf39/TJw4UXyekZHB5JuIiIiIqJIx8aZyq127NjQ1NXHu3DlYWVkByL/++fr162jdujVcXFyQl5eHR48eoVWrVlUcLRAbG4tu3brh66+/BpCfkF+/fh2Ojo4AALlcDrlcXqRdQdJ948YNHDlyBNWqVVPaXjhBf5tEIhGn2f/666+wtLREkyZNxO1v3rxBXFycOGsgKSkJz58/h4ODAzQ0NGBnZ/fO49LS0lJaeK44UqkUUqn0nX0REREREZHqMPGmcpPL5Rg0aBCmTJkCY2Nj1KhRA7NmzYKamhokEgnq1auHAQMGwNvbGyEhIXBxccHjx49x6NAhODk5oVOnTpUab926dbFlyxacPHkSRkZGWLx4MR4+fCgm3sXJzc1Fz549cf78eezevRt5eXl48OABgPwRaC0trRLbLly4EO3bt4eamhq2bduG4OBgbNq0SWn0X1NTE2PHjkVoaCg0NDTg5+eHTz75pMzXnAP5K7yfOXMGycnJkMlkMDY2VprOTkREREREHwd+S6f3snjxYri5uaFz58747LPP4OHhAQcHB3FF73Xr1sHb2xuTJk2Cvb09unfvrjRCXpm+//57NGnSBF5eXmjTpg3MzMzE23KV5O+//8auXbvw119/oXHjxjA3NxcfJ0+eLLXt77//jlatWsHV1RV79uzBzp07i+xPV1cX06ZNQ//+/eHh4QGZTIbffvutXMc1efJkqKurw9HRESYmJqXe5oyIiIiIiKqORCi45xLRB3j58iUsLCwQEhKCoUOHVnU4VIKMjIz8RdbGb4KaVLeqw6F/uOTgyp29QkRERPQxKfhunZ6eXuqdjwBONaf3dOHCBVy7dg3NmzdHenq6eMusbt26VXFkREREREREHxcm3vTeFi1ahKSkJGhpaaFp06Y4fvw4qlevXtVhERERERERfVSYeNN7cXFxQXx8fFWHQURERERE9NHj4mpEREREREREKsTEm4iIiIiIiEiFmHgTERERERERqRATbyIiIiIiIiIVYuJNREREREREpEJMvImIiIiIiIhUiIk3ERERERERkQox8SYiIiIiIiJSIY2qDoCIKt+lQC/o6+tXdRhERERERP8JHPEmIiIiIiIiUiEm3kREREREREQqxMSbiIiIiIiISIWYeBMRERERERGpEBNvIiIiIiIiIhVi4k1ERERERESkQky8iYiIiIiIiFSI9/Em+g9qOGsf1KS6VR0GEREREVG5JQd3quoQyo0j3kREREREREQqxMSbiIiIiIiISIWYeBMRERERERGpEBNvIiIiIiIiIhVi4k1ERERERESkQky8iYiIiIiIiFSIiTcRERERERGRCjHx/n/t3XtUVXX+xvHnIB5AEARvgMPgIhUZUFExA1NRKW3Msmw0VpmYl3GCUommmizETJnUzC5jiZqWY/TLHHWZkyYGU0hjqSgKndQiyqDMvKENCOzfH457PHlB0AOC79daZy323t+z9+fLZ6E8Z18AAAAAAMCBCN4AAAAAADgQwRsAAAAAAAcieKNRiI6O1pQpU2r8PovFojVr1lz1egAAAADgLII3amXXrl2KjY1VQECA3NzcFBISogULFlT7vvbt28tisdi9UlNTL/meuLi4895jsVgUGhp6xfMoLi7WbbfddsX7kaTMzExZLBYdPXr0quyvOnFxcRo+fHidHAsAAABA7TnXdwFomLZv3642bdpoxYoVCggI0NatWzVx4kQ1adJECQkJl3zvjBkzNGHCBHO5efPmlxy/YMECu3BeUVGhbt266Q9/+MOVTUKSr6/vFe+jpsrLy2W1Wuv8uAAAAADqB2e8G4iTJ0/qgQcekIeHh/z8/DRv3jzz8upXXnlFYWFh5tg1a9bIYrHotddeM9fFxMRo2rRp5vLatWvVo0cPubq6KigoSCkpKaqoqDC3WywWLV68WHfddZeaNWumjh07at26deb2Bx98UAsWLFD//v0VFBSk+++/X2PHjtXq1aurnUvz5s3l6+trvtzd3S853svLy278559/riNHjmjs2LF24yoqKpSQkCAvLy+1atVKTz/9tAzDuOS+z73UvLCwUBaLRatXr9aAAQPUrFkzdevWTTk5Oeb4b775RsOGDZO3t7fc3d0VGhqqDRs2qLCwUAMGDJAkeXt7y2KxKC4uTtKZy+ATEhI0ZcoUtWrVSoMHDzaPlZuba+776NGjslgsyszMNNft3btXt99+uzw9PdW8eXP17dtXBw4c0PTp07V8+XKtXbvWvALg3PcBAAAAuHYQvBuIxx57TFlZWVq7dq02bdqkzMxM7dixQ5LUv39/5efn69ChQ5KkrKwstWrVygxip0+fVk5OjqKjoyVJH3/8sR544AFNnjxZ+fn5ev3117Vs2TI999xzdsdMSUnRyJEjtXv3bv3+97/Xfffdp59//vmiNR47dkw+Pj7VziU1NVUtW7ZU9+7dNWfOHLvAfzmWLFmimJgYBQYG2q1fvny5nJ2dtW3bNi1YsEAvvPCCFi9eXKN9S9JTTz2lpKQk5ebmqlOnToqNjTVrjI+PV1lZmf71r38pLy9Pf/3rX+Xh4aGAgAC99957kiSbzabi4mK7S++XL18uq9Wq7Oxsuw9ELuXgwYPq16+fXFxctGXLFm3fvl0PPvigKioqlJSUpJEjR2rIkCEqLi5WcXGxoqKiajxXAAAAAI7HpeYNQGlpqZYsWaIVK1Zo0KBBks4Eud/85jeSpLCwMPn4+CgrK0v33HOPMjMz9eijj5rBb9u2bTp9+rQZzFJSUvTEE09ozJgxkqSgoCA9++yz+vOf/6zk5GTzuHFxcYqNjZUkzZo1Sy+99JK2bdumIUOGnFfj1q1b9c477+j999+/5FweeeQR9ejRQz4+Ptq6dauefPJJFRcX64UXXris78X333+vf/7zn1q5cuV52wICAjR//nxZLBYFBwcrLy9P8+fPt7us/XIkJSVp6NChks58r0JDQ7V//3517txZRUVFGjFihLp06SLpzPfurLMfOrRp00YtWrSw22fHjh31/PPPm8uFhYXV1vHqq6/Ky8tL6enpatq0qSSpU6dO5nY3NzeVlZVd8nL5srIylZWVmcvHjx+v9rgAAAAAri7OeDcABw4cUHl5uXr37m2u8/HxUXBwsKQzl0v369dPmZmZOnr0qPLz8/XQQw+prKxMX3zxhbKystSrVy81a9ZM0pkHo82YMUMeHh7ma8KECSouLtapU6fMY3Tt2tX82t3dXZ6envrxxx/Pq2/Pnj268847lZycrFtvvfWSc0lMTFR0dLS6du2qSZMmad68eXr55ZfNcHhuTZMmTTrv/cuXL1eLFi0u+FCxm266SRaLxVyOjIzUvn37VFlZqVmzZtntu6io6KI1njtvPz8/STLn/cgjj2jmzJnq06ePkpOTtXv37kvO96yePXte1rhz5ebmqm/fvmboro3Zs2fLy8vLfAUEBNR6XwAAAABqhzPejUR0dLQWLVqkjz/+WN27d5enp6cZxrOystS/f39zbGlpqVJSUnT33Xeftx9XV1fz618HPovFoqqqKrt1+fn5GjRokCZOnGh3D/nl6t27tyoqKlRYWKjg4GC7e549PT3txhqGoaVLl2r06NE1fjjZpEmTNHLkSHPZ39//omPPnffZIH923uPHj9fgwYP1/vvva9OmTZo9e7bmzZunhx9++JLH//V97E5OTuaczjp9+rTdGDc3t0vu83I8+eSTSkxMNJePHz9O+AYAAADqGGe8G4AbbrhBTZs21b///W9z3ZEjR/Tll1+ay2fv83733XfNe7mjo6O1efNmZWdnm+skqUePHrLZbOrQocN5r7OB8HLs3btXAwYM0JgxY867P/xy5ebmysnJSW3atJEku1rOrjsrKytL+/fv17hx4y64r3O/P5L06aefqmPHjmrSpIl8fHzs9u3sXPvPnAICAjRp0iStXr1ajz76qNLS0iTJ/DCgsrKy2n20bt1a0pk/Z3bWuR86SGfOvH/88cfnBfKzrFZrtcdycXGRp6en3QsAAABA3SJ4NwAeHh4aN26cHnvsMW3ZskV79uxRXFycXUju2rWrvL29tXLlSrvgvWbNGpWVlalPnz7m2GeeeUZvvvmmUlJStHfvXhUUFCg9Pb1GZ6z37NmjAQMG6NZbb1ViYqJKSkpUUlJiPuBNOnNveefOnXXw4EFJUk5Ojl588UXt2rVLX331lf7+979r6tSpuv/+++Xt7V3tMZcsWaLevXvbPcH9XEVFRUpMTJTNZtPbb7+tl19+WZMnT77sOV2OKVOmaOPGjfr666+1Y8cOffTRRwoJCZEkBQYGymKxaP369Tp06JBKS0svuh83NzfddNNNSk1NVUFBgbKyss77/ickJOj48eO699579fnnn2vfvn166623ZLPZJJ35m+i7d++WzWbTTz/9dNGADgAAAKB+EbwbiDlz5qhv374aNmyYYmJidPPNN9vdN2yxWNS3b19ZLBbdfPPNks6EcU9PT0VERNhd6jx48GCtX79emzZtUq9evXTTTTdp/vz55z0l/FJWrVqlQ4cOacWKFfLz8zNfvXr1MsecOnVKNpvNDIQuLi5KT09X//79FRoaqueee05Tp07VokWLqj3esWPH9N577130bLckPfDAA/rll1904403Kj4+XpMnT9bEiRMve06Xo7KyUvHx8QoJCdGQIUPUqVMn/e1vf5MktWvXznxwXdu2bav9e+ZLly5VRUWFevbsqSlTpmjmzJl221u2bKktW7aotLRU/fv3V8+ePZWWlmZeCj9hwgQFBwcrIiJCrVu3VnZ29lWdKwAAAICrw2JU94eOcc2Kjo5WeHi4XnzxxfouBQ3E8ePHzzxkbcr/ycmlWX2XAwAAANRYYerQ+i5B0v9+tz527Fi1t3RyxhsAAAAAAAcieAMAAAAA4ED8ObEGLDMzs75LAAAAAABUgzPeAAAAAAA4EMEbAAAAAAAHIngDAAAAAOBABG8AAAAAAByI4A0AAAAAgAMRvAEAAAAAcCCCNwAAAAAADsTf8QauQ3tSBsvT07O+ywAAAACuC5zxBgAAAADAgQjeAAAAAAA4EMEbAAAAAAAHIngDAAAAAOBABG8AAAAAAByI4A0AAAAAgAMRvAEAAAAAcCCCNwAAAAAADkTwBgAAAADAgQjeAAAAAAA4EMEbAAAAAAAHIngDAAAAAOBABG8AAAAAAByI4A0AAAAAgAMRvAEAAAAAcCCCNwAAAAAADkTwBgAAAADAgZzruwAAdccwDEnS8ePH67kSAAAAoGE7+zv12d+xL4XgDVxHDh8+LEkKCAio50oAAACAxuHEiRPy8vK65BiCN3Ad8fHxkSQVFRVV+48Drk3Hjx9XQECAvv32W3l6etZ3OagFetiw0b+Gjx42bPSv4WtMPTQMQydOnJC/v3+1YwnewHXEyenMYx28vLwa/D901ztPT0962MDRw4aN/jV89LBho38NX2Pp4eWezOLhagAAAAAAOBDBGwAAAAAAByJ4A9cRFxcXJScny8XFpb5LQS3Rw4aPHjZs9K/ho4cNG/1r+K7XHlqMy3n2OQAAAAAAqBXOeAMAAAAA4EAEbwAAAAAAHIjgDQAAAACAAxG8gUbm1VdfVfv27eXq6qrevXtr27Ztlxz/7rvvqnPnznJ1dVWXLl20YcOGOqoUF1OTHu7du1cjRoxQ+/btZbFY9OKLL9ZdobiomvQwLS1Nffv2lbe3t7y9vRUTE1Ptzy0cqyb9W716tSIiItSiRQu5u7srPDxcb731Vh1Wiwup6f+FZ6Wnp8tisWj48OGOLRCXVJP+LVu2TBaLxe7l6upah9XiQmr6M3j06FHFx8fLz89PLi4u6tSpU6P7nZTgDTQi77zzjhITE5WcnKwdO3aoW7duGjx4sH788ccLjt+6datiY2M1btw47dy5U8OHD9fw4cO1Z8+eOq4cZ9W0h6dOnVJQUJBSU1Pl6+tbx9XiQmraw8zMTMXGxuqjjz5STk6OAgICdOutt+rgwYN1XDmkmvfPx8dHTz31lHJycrR7926NHTtWY8eO1caNG+u4cpxV0x6eVVhYqKSkJPXt27eOKsWF1KZ/np6eKi4uNl/ffPNNHVaMX6tpD8vLy3XLLbeosLBQq1atks1mU1pamtq1a1fHlTuYAaDRuPHGG434+HhzubKy0vD39zdmz559wfEjR440hg4dareud+/exh//+EeH1omLq2kPzxUYGGjMnz/fgdXhclxJDw3DMCoqKozmzZsby5cvd1SJuIQr7Z9hGEb37t2NadOmOaI8XIba9LCiosKIiooyFi9ebIwZM8a4884766BSXEhN+/fGG28YXl5edVQdLkdNe7hw4UIjKCjIKC8vr6sS6wVnvIFGory8XNu3b1dMTIy5zsnJSTExMcrJybnge3JycuzGS9LgwYMvOh6OVZse4tpyNXp46tQpnT59Wj4+Po4qExdxpf0zDEMZGRmy2Wzq16+fI0vFRdS2hzNmzFCbNm00bty4uigTF1Hb/pWWliowMFABAQG68847tXfv3rooFxdQmx6uW7dOkZGRio+PV9u2bRUWFqZZs2apsrKyrsquEwRvoJH46aefVFlZqbZt29qtb9u2rUpKSi74npKSkhqNh2PVpoe4tlyNHj7++OPy9/c/70MxOF5t+3fs2DF5eHjIarVq6NChevnll3XLLbc4ulxcQG16+Mknn2jJkiVKS0urixJxCbXpX3BwsJYuXaq1a9dqxYoVqqqqUlRUlL777ru6KBm/UpsefvXVV1q1apUqKyu1YcMGPf3005o3b55mzpxZFyXXGef6LgAAAJyRmpqq9PR0ZWZm8nCgBqR58+bKzc1VaWmpMjIylJiYqKCgIEVHR9d3aajGiRMnNHr0aKWlpalVq1b1XQ5qITIyUpGRkeZyVFSUQkJC9Prrr+vZZ5+tx8pwuaqqqtSmTRstWrRITZo0Uc+ePXXw4EHNmTNHycnJ9V3eVUPwBhqJVq1aqUmTJvrhhx/s1v/www8XfeiWr69vjcbDsWrTQ1xbrqSHc+fOVWpqqjZv3qyuXbs6skxcRG375+TkpA4dOkiSwsPDVVBQoNmzZxO860FNe3jgwAEVFhZq2LBh5rqqqipJkrOzs2w2m2644QbHFg3T1fh/sGnTpurevbv279/viBJRjdr00M/PT02bNlWTJk3MdSEhISopKVF5ebmsVqtDa64rXGoONBJWq1U9e/ZURkaGua6qqkoZGRl2nwSfKzIy0m68JH344YcXHQ/Hqk0PcW2pbQ+ff/55Pfvss/rggw8UERFRF6XiAq7Wz2BVVZXKysocUSKqUdMedu7cWXl5ecrNzTVfd9xxhwYMGKDc3FwFBATUZfnXvavxM1hZWam8vDz5+fk5qkxcQm162KdPH+3fv9/80EuSvvzyS/n5+TWa0C2Jp5oDjUl6errh4uJiLFu2zMjPzzcmTpxotGjRwigpKTEMwzBGjx5tPPHEE+b47Oxsw9nZ2Zg7d65RUFBgJCcnG02bNjXy8vLqawrXvZr2sKyszNi5c6exc+dOw8/Pz0hKSjJ27txp7Nu3r76mcN2raQ9TU1MNq9VqrFq1yiguLjZfJ06cqK8pXNdq2r9Zs2YZmzZtMg4cOGDk5+cbc+fONZydnY20tLT6msJ1r6Y9/DWeal6/atq/lJQUY+PGjcaBAweM7du3G/fee6/h6upq7N27t76mcN2raQ+LioqM5s2bGwkJCYbNZjPWr19vtGnTxpg5c2Z9TcEhuNQcaERGjRqlQ4cO6ZlnnlFJSYnCw8P1wQcfmA+4KCoqkpPT/y50iYqK0sqVKzVt2jT95S9/UceOHbVmzRqFhYXV1xSuezXt4ffff6/u3buby3PnztXcuXPVv39/ZWZm1nX5UM17uHDhQpWXl+uee+6x209ycrKmT59el6VDNe/fyZMn9dBDD+m7776Tm5ubOnfurBUrVmjUqFH1NYXrXk17iGtLTft35MgRTZgwQSUlJfL29lbPnj21detW/e53v6uvKVz3atrDgIAAbdy4UVOnTlXXrl3Vrl07TZ48WY8//nh9TcEhLIZhGPVdBAAAAAAAjRUf9wEAAAAA4EAEbwAAAAAAHIjgDQAAAACAAxG8AQAAAABwIII3AAAAAAAORPAGAAAAAMCBCN4AAAAAADgQwRsAAAAAAAcieAMAADhIXFychg8ffkX7KCwslMViUW5u7kXHZGZmymKx6OjRo5KkZcuWqUWLFub26dOnKzw8/IrqAADUHsEbAABAZ0KyxWKRxWKR1WpVhw4dNGPGDFVUVNR3adWKiopScXGxvLy8Lrg9KSlJGRkZ5vLV+EAAAHD5nOu7AAAAgGvFkCFD9MYbb6isrEwbNmxQfHy8mjZtqieffNJuXHl5uaxWaz1VeT6r1SpfX9+Lbvfw8JCHh0cdVgQAOBdnvAEAAP7LxcVFvr6+CgwM1J/+9CfFxMRo3bp15hni5557Tv7+/goODpYk5eXlaeDAgXJzc1PLli01ceJElZaWnrfflJQUtW7dWp6enpo0aZLKy8vNbR988IFuvvlmtWjRQi1bttTtt9+uAwcOnLePL774QlFRUXJ1dVVYWJiysrLMbb++1PzXzr3UfPr06Vq+fLnWrl1rnuHPzMzUwIEDlZCQYPe+Q4cOyWq12p0tBwDUHMEbAADgItzc3MyQnJGRIZvNpg8//FDr16/XyZMnNXjwYHl7e+uzzz7Tu+++q82bN58XXjMyMlRQUKDMzEy9/fbbWr16tVJSUsztJ0+eVGJioj7//HNlZGTIyclJd911l6qqquz289hjj+nRRx/Vzp07FRkZqWHDhunw4cM1nlNSUpJGjhypIUOGqLi4WMXFxYqKitL48eO1cuVKlZWVmWNXrFihdu3aaeDAgTU+DgDgfwjeAAAAv2IYhjZv3qyNGzeaodPd3V2LFy9WaGioQkNDtXLlSv3nP//Rm2++qbCwMA0cOFCvvPKK3nrrLf3www/mvqxWq5YuXarQ0FANHTpUM2bM0EsvvWQG6xEjRujuu+9Whw4dFB4erqVLlyovL0/5+fl2NSUkJGjEiBEKCQnRwoUL5eXlpSVLltR4bh4eHnJzczPP7vv6+spqteruu++WJK1du9Ycu2zZMvPedwBA7RG8AQAA/mv9+vXy8PCQq6urbrvtNo0aNUrTp0+XJHXp0sXuvu6CggJ169ZN7u7u5ro+ffqoqqpKNpvNXNetWzc1a9bMXI6MjFRpaam+/fZbSdK+ffsUGxuroKAgeXp6qn379pKkoqIiu9oiIyPNr52dnRUREaGCgoKrNndXV1eNHj1aS5culSTt2LFDe/bsUVxc3FU7BgBcr3i4GgAAwH8NGDBACxculNVqlb+/v5yd//er0rkB+2oaNmyYAgMDlZaWJn9/f1VVVSksLMzuPvC6Mn78eIWHh+u7777TG2+8oYEDByowMLDO6wCAxoYz3gAAAP/l7u6uDh066Le//a1d6L6QkJAQ7dq1SydPnjTXZWdny8nJyXz4miTt2rVLv/zyi7n86aefysPDQwEBATp8+LBsNpumTZumQYMGKSQkREeOHLng8T799FPz64qKCm3fvl0hISG1mqfValVlZeV567t06aKIiAilpaVp5cqVevDBB2u1fwCAPYI3AABALdx3331ydXXVmDFjtGfPHn300Ud6+OGHNXr0aLVt29YcV15ernHjxik/P18bNmxQcnKyEhIS5OTkJG9vb7Vs2VKLFi3S/v37tWXLFiUmJl7weK+++qr+8Y9/6IsvvlB8fLyOHDlS62Dcvn177d69WzabTT/99JNOnz5tbhs/frxSU1NlGIbuuuuuWu0fAGCP4A0AAFALzZo108aNG/Xzzz+rV69euueeezRo0CC98sorduMGDRqkjh07ql+/fho1apTuuOMO875xJycnpaena/v27QoLC9PUqVM1Z86cCx4vNTVVqamp6tatmz755BOtW7dOrVq1qlXtEyZMUHBwsCIiItS6dWtlZ2eb22JjY+Xs7KzY2Fi5urrWav8AAHsWwzCM+i4CAAAA14bCwkLdcMMN+uyzz9SjR4/6LgcAGgWCNwAAAHT69GkdPnxYSUlJ+vrrr+3OggMArgyXmgMAAEDZ2dny8/PTZ599ptdee62+ywGARoUz3gAAAAAAOBBnvAEAAAAAcCCCNwAAAAAADkTwBgAAAADAgQjeAAAAAAA4EMEbAAAAAAAHIngDAAAAAOBABG8AAAAAAByI4A0AAAAAgAMRvAEAAAAAcKD/B4NeqjZrRPCRAAAAAElFTkSuQmCC\n"},"metadata":{}}]},{"cell_type":"markdown","source":["## 5. Batch Routing"],"metadata":{"id":"LUP2UDaGKolO"}},{"cell_type":"markdown","source":["## 6. File-Based Inference\n","\n","Load queries from a file and save results."],"metadata":{"id":"_j95Q5wqKpKy"}},{"cell_type":"code","source":["import json\n","\n","# Load queries from a JSONL file\n","def load_queries_from_file(file_path):\n"," \"\"\"Load queries from a JSONL file.\"\"\"\n"," queries = []\n"," with open(file_path, 'r', encoding='utf-8') as f:\n"," for line in f:\n"," if line.strip():\n"," queries.append(json.loads(line))\n"," return queries\n","\n","# Save results to a JSONL file\n","def save_results_to_file(results, output_path):\n"," \"\"\"Save routing results to a JSONL file.\"\"\"\n"," os.makedirs(os.path.dirname(output_path), exist_ok=True)\n"," with open(output_path, 'w', encoding='utf-8') as f:\n"," for result in results:\n"," f.write(json.dumps(result, ensure_ascii=False) + '\\n')\n"," print(f\"Results saved to: {output_path}\")\n","\n","# Example: Load from default query file\n","QUERY_FILE = \"data/example_data/query_data/default_query_test.jsonl\"\n","OUTPUT_FILE = \"outputs/svmrouter_results.jsonl\"\n","\n","if os.path.exists(QUERY_FILE):\n"," # Load queries\n"," file_queries = load_queries_from_file(QUERY_FILE)\n"," print(f\"Loaded {len(file_queries)} queries from: {QUERY_FILE}\")\n","\n"," # Route queries (using route_batch for efficiency)\n"," file_results = router.route_batch(batch=file_queries[:10])\n"," print(f\"Routed {len(file_results)} queries\")\n","\n"," # Save results\n"," save_results_to_file(file_results, OUTPUT_FILE)\n","\n"," # Show sample results\n"," print(f\"\\nSample results:\")\n"," for i, result in enumerate(file_results[:3], 1):\n"," print(f\" {i}. {result.get('query', '')[:40]}... -> {result['model_name']}\")\n","else:\n"," print(f\"Query file not found: {QUERY_FILE}\")\n"," print(\"Create a JSONL file with format: {\\\"query\\\": \\\"Your question\\\"}\")"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"XhZWIIngKqil","executionInfo":{"status":"ok","timestamp":1767602340102,"user_tz":-480,"elapsed":17689,"user":{"displayName":"dai","userId":"16597391218515739453"}},"outputId":"85d192fd-649e-4d22-a03c-bbe16aff8389"},"execution_count":null,"outputs":[{"output_type":"stream","name":"stdout","text":["Loaded 706 queries from: data/example_data/query_data/default_query_test.jsonl\n","Successfully loaded pickle model: /content/LLMRouter/llmrouter/saved_models/svmrouter/svmrouter.pkl\n","Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n","Error calling API for query: API_KEYS environment variable is not set\n","Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n","Error calling API for query: API_KEYS environment variable is not set\n","Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n","Error calling API for query: API_KEYS environment variable is not set\n","Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n","Error calling API for query: API_KEYS environment variable is not set\n","Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n","Error calling API for query: API_KEYS environment variable is not set\n","Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n","Error calling API for query: API_KEYS environment variable is not set\n","Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n","Error calling API for query: API_KEYS environment variable is not set\n","Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n","Error calling API for query: API_KEYS environment variable is not set\n","Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n","Error calling API for query: API_KEYS environment variable is not set\n","Warning: Failed to format query with task 'agentverse-logicgrid': Unknown task name: agentverse-logicgrid. Using original query.\n","Error calling API for query: API_KEYS environment variable is not set\n","Routed 10 queries\n","Results saved to: outputs/svmrouter_results.jsonl\n","\n","Sample results:\n"," 1. Q: There are 4 houses in a row, numbered... -> qwen2.5-7b-instruct\n"," 2. Q: There are 3 houses in a row, numbered... -> qwen2.5-7b-instruct\n"," 3. Q: There are 3 houses in a row, numbered... -> qwen2.5-7b-instruct\n"]}]},{"cell_type":"code","source":["from collections import Counter\n","\n","# Route multiple queries\n","results = [router.route_single(q) for q in EXAMPLE_QUERIES]\n","\n","# Show distribution\n","model_counts = Counter(r['model_name'] for r in results)\n","print(\"Routing Distribution:\")\n","for model, count in model_counts.most_common():\n"," print(f\" {model}: {count}\")"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"WBrDZXbkKs5C","outputId":"83747d8b-f399-48d1-ce70-87354ed6089f"},"execution_count":null,"outputs":[{"output_type":"stream","name":"stdout","text":["Successfully loaded pickle model: /content/LLMRouter/llmrouter/saved_models/svmrouter/svmrouter.pkl\n","Successfully loaded pickle model: /content/LLMRouter/llmrouter/saved_models/svmrouter/svmrouter.pkl\n"]}]},{"cell_type":"markdown","source":["## Summary\n","\n","This notebook demonstrated:\n","1. Loading a trained SVMRouter\n","2. Single and batch query routing\n","3. Visualizing routing probabilities\n","\n","SVMRouter is effective for:\n","- High-dimensional embedding spaces\n","- Clear separation between LLM performance profiles"],"metadata":{"id":"8AQVaR_0Kr_-"}}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.10.0"},"colab":{"provenance":[]},"widgets":{"application/vnd.jupyter.widget-state+json":{"da404c4ac9a84df3ab2a7034ac6d020c":{"model_module":"@jupyter-widgets/controls","model_name":"HBoxModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HBoxModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HBoxView","box_style":"","children":["IPY_MODEL_93208c9976d74f1188c07366c77f1dcf","IPY_MODEL_c362e25195454138a895ed1094f6a73f","IPY_MODEL_2deb10f48fc0485db2504c855d0ccdde"],"layout":"IPY_MODEL_3c9fb7381f1b4af5b81f4b06b30a4d9e"}},"93208c9976d74f1188c07366c77f1dcf":{"model_module":"@jupyter-widgets/controls","model_name":"HTMLModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_5919478a94df4b31a0c0fc4ba3d003e6","placeholder":"​","style":"IPY_MODEL_90ad95c6d79549f7ac19a70b11867dbf","value":"config.json: 100%"}},"c362e25195454138a895ed1094f6a73f":{"model_module":"@jupyter-widgets/controls","model_name":"FloatProgressModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"FloatProgressModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"ProgressView","bar_style":"success","description":"","description_tooltip":null,"layout":"IPY_MODEL_69bdd2a99fc845a1bc1719184e884de9","max":694,"min":0,"orientation":"horizontal","style":"IPY_MODEL_89bca093ec5c4985bab7707c32413b3c","value":694}},"2deb10f48fc0485db2504c855d0ccdde":{"model_module":"@jupyter-widgets/controls","model_name":"HTMLModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_75d361e9ddbf40d6bf259ed47b47aef6","placeholder":"​","style":"IPY_MODEL_0ae3636c4167451c8f4bb3c94309f447","value":" 694/694 [00:00<00:00, 69.5kB/s]"}},"3c9fb7381f1b4af5b81f4b06b30a4d9e":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"5919478a94df4b31a0c0fc4ba3d003e6":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"90ad95c6d79549f7ac19a70b11867dbf":{"model_module":"@jupyter-widgets/controls","model_name":"DescriptionStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}},"69bdd2a99fc845a1bc1719184e884de9":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"89bca093ec5c4985bab7707c32413b3c":{"model_module":"@jupyter-widgets/controls","model_name":"ProgressStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"ProgressStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","bar_color":null,"description_width":""}},"75d361e9ddbf40d6bf259ed47b47aef6":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"0ae3636c4167451c8f4bb3c94309f447":{"model_module":"@jupyter-widgets/controls","model_name":"DescriptionStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}},"bdbd946cf4b64a64be5b78245481c6cb":{"model_module":"@jupyter-widgets/controls","model_name":"HBoxModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HBoxModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HBoxView","box_style":"","children":["IPY_MODEL_95ff9a66527b4d1cad3f1d8b7ab9e15b","IPY_MODEL_7e1fd7f45f304eae81775164a575fa1e","IPY_MODEL_df91ed87541f4a0488ed5ecfcc85b0c1"],"layout":"IPY_MODEL_82facacd6d194bc4b461709bff3eaf03"}},"95ff9a66527b4d1cad3f1d8b7ab9e15b":{"model_module":"@jupyter-widgets/controls","model_name":"HTMLModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_dda999afbad74235b6bd053dc7247171","placeholder":"​","style":"IPY_MODEL_872dc0a75d1a4743ad3b1ac30375482f","value":"vocab.json: "}},"7e1fd7f45f304eae81775164a575fa1e":{"model_module":"@jupyter-widgets/controls","model_name":"FloatProgressModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"FloatProgressModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"ProgressView","bar_style":"success","description":"","description_tooltip":null,"layout":"IPY_MODEL_7987ef2a0955487381c30e90401f5e34","max":1,"min":0,"orientation":"horizontal","style":"IPY_MODEL_60dd33908255484b8d0d4a822bb54001","value":1}},"df91ed87541f4a0488ed5ecfcc85b0c1":{"model_module":"@jupyter-widgets/controls","model_name":"HTMLModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_2e838cfa18694f62ae4851057b04c806","placeholder":"​","style":"IPY_MODEL_e9b27ea6331b479ea8c875816838c5f9","value":" 899k/? [00:00<00:00, 19.5MB/s]"}},"82facacd6d194bc4b461709bff3eaf03":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"dda999afbad74235b6bd053dc7247171":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"872dc0a75d1a4743ad3b1ac30375482f":{"model_module":"@jupyter-widgets/controls","model_name":"DescriptionStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}},"7987ef2a0955487381c30e90401f5e34":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":"20px"}},"60dd33908255484b8d0d4a822bb54001":{"model_module":"@jupyter-widgets/controls","model_name":"ProgressStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"ProgressStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","bar_color":null,"description_width":""}},"2e838cfa18694f62ae4851057b04c806":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"e9b27ea6331b479ea8c875816838c5f9":{"model_module":"@jupyter-widgets/controls","model_name":"DescriptionStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}},"0844fe9066ae4f54bcd0812668041f63":{"model_module":"@jupyter-widgets/controls","model_name":"HBoxModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HBoxModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HBoxView","box_style":"","children":["IPY_MODEL_0f751e627f614d57b32ed78fc249a645","IPY_MODEL_719c269d2585472e9e40131999f331f8","IPY_MODEL_5d53177942e748338cab4fc5e7a08328"],"layout":"IPY_MODEL_6c18b164a7e04045a732f9ab21d2aacc"}},"0f751e627f614d57b32ed78fc249a645":{"model_module":"@jupyter-widgets/controls","model_name":"HTMLModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_74ad759512c449c281f3545bca774628","placeholder":"​","style":"IPY_MODEL_eff9aae276854383b304b349b424480d","value":"merges.txt: "}},"719c269d2585472e9e40131999f331f8":{"model_module":"@jupyter-widgets/controls","model_name":"FloatProgressModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"FloatProgressModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"ProgressView","bar_style":"success","description":"","description_tooltip":null,"layout":"IPY_MODEL_25a75f3251674b5a9f90e46adab2313a","max":1,"min":0,"orientation":"horizontal","style":"IPY_MODEL_94550edd51764620a90ff632c56b2f6f","value":1}},"5d53177942e748338cab4fc5e7a08328":{"model_module":"@jupyter-widgets/controls","model_name":"HTMLModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_413d9b5caeb6486f9cb96fc0a7790365","placeholder":"​","style":"IPY_MODEL_244a80df857b4b9da80dca996fde039b","value":" 456k/? [00:00<00:00, 28.5MB/s]"}},"6c18b164a7e04045a732f9ab21d2aacc":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"74ad759512c449c281f3545bca774628":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"eff9aae276854383b304b349b424480d":{"model_module":"@jupyter-widgets/controls","model_name":"DescriptionStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}},"25a75f3251674b5a9f90e46adab2313a":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":"20px"}},"94550edd51764620a90ff632c56b2f6f":{"model_module":"@jupyter-widgets/controls","model_name":"ProgressStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"ProgressStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","bar_color":null,"description_width":""}},"413d9b5caeb6486f9cb96fc0a7790365":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"244a80df857b4b9da80dca996fde039b":{"model_module":"@jupyter-widgets/controls","model_name":"DescriptionStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}},"7aee6632e8ad465d9cab9fd1473d888d":{"model_module":"@jupyter-widgets/controls","model_name":"HBoxModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HBoxModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HBoxView","box_style":"","children":["IPY_MODEL_d1afa21f3529436e96d18ad132e6dd6d","IPY_MODEL_4f3382442979487093bf88a7e62fe13a","IPY_MODEL_bff76e5b3b9b4f9cb2ef26e1ee32cb08"],"layout":"IPY_MODEL_7be2ed7953034a8fb9c1085f32976ab0"}},"d1afa21f3529436e96d18ad132e6dd6d":{"model_module":"@jupyter-widgets/controls","model_name":"HTMLModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_af9522f82d2b4a288b9ff5ba84ca4cfc","placeholder":"​","style":"IPY_MODEL_558c47054eda4942949f4ea4249078df","value":"tokenizer.json: "}},"4f3382442979487093bf88a7e62fe13a":{"model_module":"@jupyter-widgets/controls","model_name":"FloatProgressModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"FloatProgressModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"ProgressView","bar_style":"success","description":"","description_tooltip":null,"layout":"IPY_MODEL_e6f52045f99645b2914e7b33219ae704","max":1,"min":0,"orientation":"horizontal","style":"IPY_MODEL_a2523255cf1d457b874f9e5f656527f6","value":1}},"bff76e5b3b9b4f9cb2ef26e1ee32cb08":{"model_module":"@jupyter-widgets/controls","model_name":"HTMLModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_f87f7a4a497049989652e197913b4b6f","placeholder":"​","style":"IPY_MODEL_7a6f1c6a8f7f4672a02020ed26e8577d","value":" 1.36M/? [00:00<00:00, 48.5MB/s]"}},"7be2ed7953034a8fb9c1085f32976ab0":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"af9522f82d2b4a288b9ff5ba84ca4cfc":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"558c47054eda4942949f4ea4249078df":{"model_module":"@jupyter-widgets/controls","model_name":"DescriptionStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}},"e6f52045f99645b2914e7b33219ae704":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":"20px"}},"a2523255cf1d457b874f9e5f656527f6":{"model_module":"@jupyter-widgets/controls","model_name":"ProgressStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"ProgressStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","bar_color":null,"description_width":""}},"f87f7a4a497049989652e197913b4b6f":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"7a6f1c6a8f7f4672a02020ed26e8577d":{"model_module":"@jupyter-widgets/controls","model_name":"DescriptionStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}},"9d82e991fd894a4c95b1d8c7f1bc4a89":{"model_module":"@jupyter-widgets/controls","model_name":"HBoxModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HBoxModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HBoxView","box_style":"","children":["IPY_MODEL_8248110b8b1f4eed9dd8232f60974f71","IPY_MODEL_adccb019b8c44d4688e665d46b5d6bec","IPY_MODEL_50de4684f97e45f0b930d122976899a4"],"layout":"IPY_MODEL_3e2c5b913f9149f6af7ae165db75d9c8"}},"8248110b8b1f4eed9dd8232f60974f71":{"model_module":"@jupyter-widgets/controls","model_name":"HTMLModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_43845319e7ea43a581e2d0d8cb1015a1","placeholder":"​","style":"IPY_MODEL_9ac96aa4bcfa467ea2026e2b75582bd0","value":"pytorch_model.bin: 100%"}},"adccb019b8c44d4688e665d46b5d6bec":{"model_module":"@jupyter-widgets/controls","model_name":"FloatProgressModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"FloatProgressModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"ProgressView","bar_style":"success","description":"","description_tooltip":null,"layout":"IPY_MODEL_e430ac8dd4e646bab05938f6e4a9112e","max":597257159,"min":0,"orientation":"horizontal","style":"IPY_MODEL_383be83a57d74188a6fa3a3010b8f7c2","value":597257159}},"50de4684f97e45f0b930d122976899a4":{"model_module":"@jupyter-widgets/controls","model_name":"HTMLModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_9c11c57c6d61439bb2d780e61dca6263","placeholder":"​","style":"IPY_MODEL_0da5671d241741f1a54012d7bc60af77","value":" 597M/597M [00:09<00:00, 69.8MB/s]"}},"3e2c5b913f9149f6af7ae165db75d9c8":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"43845319e7ea43a581e2d0d8cb1015a1":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"9ac96aa4bcfa467ea2026e2b75582bd0":{"model_module":"@jupyter-widgets/controls","model_name":"DescriptionStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}},"e430ac8dd4e646bab05938f6e4a9112e":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"383be83a57d74188a6fa3a3010b8f7c2":{"model_module":"@jupyter-widgets/controls","model_name":"ProgressStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"ProgressStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","bar_color":null,"description_width":""}},"9c11c57c6d61439bb2d780e61dca6263":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"0da5671d241741f1a54012d7bc60af77":{"model_module":"@jupyter-widgets/controls","model_name":"DescriptionStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}},"559224923db24606be570cc6f884b2c8":{"model_module":"@jupyter-widgets/controls","model_name":"HBoxModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HBoxModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HBoxView","box_style":"","children":["IPY_MODEL_1d337a87d7324a3cace6a65267cf4946","IPY_MODEL_af70e95c3133440ea79988dae54d6ef1","IPY_MODEL_6f35ad1b2c0f40b68edce1eed921b235"],"layout":"IPY_MODEL_0e3cbb53e5694c33877c16b6a0ef9a57"}},"1d337a87d7324a3cace6a65267cf4946":{"model_module":"@jupyter-widgets/controls","model_name":"HTMLModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_0a095b25a25c4adfb7eb60ce95b32f13","placeholder":"​","style":"IPY_MODEL_1a58c08e494a4d2aa87cddf104533d24","value":"model.safetensors: 100%"}},"af70e95c3133440ea79988dae54d6ef1":{"model_module":"@jupyter-widgets/controls","model_name":"FloatProgressModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"FloatProgressModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"ProgressView","bar_style":"success","description":"","description_tooltip":null,"layout":"IPY_MODEL_7cc465645ec848878b711770f153b38c","max":597241956,"min":0,"orientation":"horizontal","style":"IPY_MODEL_a55bdca19a5c4c5cad53ef9565f40717","value":597241956}},"6f35ad1b2c0f40b68edce1eed921b235":{"model_module":"@jupyter-widgets/controls","model_name":"HTMLModel","model_module_version":"1.5.0","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_b13a95c186c444f5b2c131f8a1d0ebc6","placeholder":"​","style":"IPY_MODEL_4d0acc4bf9e24eaca863e39dc230e029","value":" 597M/597M [00:14<00:00, 37.1MB/s]"}},"0e3cbb53e5694c33877c16b6a0ef9a57":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"0a095b25a25c4adfb7eb60ce95b32f13":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"1a58c08e494a4d2aa87cddf104533d24":{"model_module":"@jupyter-widgets/controls","model_name":"DescriptionStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}},"7cc465645ec848878b711770f153b38c":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"a55bdca19a5c4c5cad53ef9565f40717":{"model_module":"@jupyter-widgets/controls","model_name":"ProgressStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"ProgressStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","bar_color":null,"description_width":""}},"b13a95c186c444f5b2c131f8a1d0ebc6":{"model_module":"@jupyter-widgets/base","model_name":"LayoutModel","model_module_version":"1.2.0","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"4d0acc4bf9e24eaca863e39dc230e029":{"model_module":"@jupyter-widgets/controls","model_name":"DescriptionStyleModel","model_module_version":"1.5.0","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}}}}},"nbformat":4,"nbformat_minor":0} \ No newline at end of file diff --git a/configs/model_config_train/knnmultiroundrouter.yaml b/configs/model_config_train/knnmultiroundrouter.yaml index 780fe23..857e46a 100644 --- a/configs/model_config_train/knnmultiroundrouter.yaml +++ b/configs/model_config_train/knnmultiroundrouter.yaml @@ -15,7 +15,7 @@ model_path: save_model_path: 'saved_models/knnmultiroundrouter/knnmultiroundrouter.pkl' # Configuration for full pipeline processing -base_model: 'Qwen/Qwen2.5-3B-Instruct' # Model for decomposition and aggregation +base_model: 'qwen/qwen2.5-7b-instruct' # Model for decomposition and aggregation use_local_llm: false # Set to true to use vLLM for local inference (requires vLLM installed) api_endpoint: 'https://integrate.api.nvidia.com/v1' # API endpoint for execution api_key: null # API key (can also be set via environment variable API_KEYS) diff --git a/configs/model_config_train/mlprouter.yaml b/configs/model_config_train/mlprouter.yaml index 9c975d1..616736a 100644 --- a/configs/model_config_train/mlprouter.yaml +++ b/configs/model_config_train/mlprouter.yaml @@ -31,3 +31,5 @@ hparam: epochs: 100 # Number of training epochs batch_size: 32 # Batch size for training alpha: 0.0001 # L2 regularization (weight decay) + +api_endpoint: '' \ No newline at end of file diff --git a/llmrouter/models/elorouter/router.py b/llmrouter/models/elorouter/router.py index 25fa56d..878c707 100644 --- a/llmrouter/models/elorouter/router.py +++ b/llmrouter/models/elorouter/router.py @@ -46,7 +46,7 @@ def _load_elo_if_needed(self): return project_root = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) - load_path = os.path.join(project_root, self.cfg["model_path"]["load_model_path"]) + load_path = os.path.join(project_root, self.cfg["model_path"]["save_model_path"]) elo_obj = load_model(load_path) diff --git a/llmrouter/models/gmtrouter/router.py b/llmrouter/models/gmtrouter/router.py index c3fd5a1..39ff7c7 100644 --- a/llmrouter/models/gmtrouter/router.py +++ b/llmrouter/models/gmtrouter/router.py @@ -15,6 +15,7 @@ import os import json import torch +import torch.nn as nn import numpy as np from llmrouter.models.meta_router import MetaRouter @@ -58,7 +59,8 @@ def __init__(self, yaml_path: str): Args: yaml_path: Path to YAML configuration file """ - super().__init__(yaml_path=yaml_path) + dummy_model = nn.Identity() + super(GMTRouter, self).__init__(model=dummy_model, yaml_path=yaml_path) # GMTRouter-specific configuration self.gmt_config = self.cfg.get("gmt_config", {}) diff --git a/llmrouter/models/hybrid_llm/router.py b/llmrouter/models/hybrid_llm/router.py index 9302e55..7eda31e 100644 --- a/llmrouter/models/hybrid_llm/router.py +++ b/llmrouter/models/hybrid_llm/router.py @@ -50,7 +50,7 @@ def __init__(self, yaml_path: str): # ------------------------------- # Determine smallest / largest LLM # ------------------------------- - self.small_model_name, self.large_model_name = self._resolve_small_large() + self.small_model_name, self.large_model_name = self._resolve_small_large(self.routing_data_train) print( f"[HybridLLMRouter] Mode={self.router_mode}, " f"Small='{self.small_model_name}', Large='{self.large_model_name}'" @@ -67,7 +67,7 @@ def __init__(self, yaml_path: str): # ============================================================== # Compute smallest and largest models from llm_data # ============================================================== - def _resolve_small_large(self): + def _resolve_small_large(self, routing_data=None): if not hasattr(self, "llm_data") or not self.llm_data: raise ValueError("[HybridLLMRouter] llm_data missing.") @@ -82,6 +82,17 @@ def _resolve_small_large(self): if len(available) < 2: raise ValueError("[HybridLLMRouter] Need at least 2 models (size ends with B).") + # Filter models to only those that exist in routing_data (if provided) + if routing_data is not None and not routing_data.empty: + available_in_data = routing_data["model_name"].unique().tolist() + available = [m for m in available if m in available_in_data] + if len(available) < 2: + raise ValueError( + f"[HybridLLMRouter] Only found {len(available)} models with routing data. " + f"Need at least 2 models that exist in routing_data." + ) + print(f"[HybridLLMRouter] Filtered to {len(available)} models with routing data.") + sorted_models = sorted( available, key=lambda m: parse_size(self.llm_data[m].get("size", "0B")) diff --git a/llmrouter/models/knnmultiroundrouter/router.py b/llmrouter/models/knnmultiroundrouter/router.py index e6e3c77..a3e2fce 100644 --- a/llmrouter/models/knnmultiroundrouter/router.py +++ b/llmrouter/models/knnmultiroundrouter/router.py @@ -108,7 +108,7 @@ def _load_knn_model_if_needed(self): # Construct model path dynamically (only when needed, not during __init__) # This allows training to work when load_model_path doesn't exist in config project_root = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) - load_model_path = self.cfg.get("model_path", {}).get("load_model_path") + load_model_path = self.cfg.get("model_path", {}).get("save_model_path") if not load_model_path: raise RuntimeError( diff --git a/llmrouter/models/mfrouter/router.py b/llmrouter/models/mfrouter/router.py index 9f41e75..570951c 100644 --- a/llmrouter/models/mfrouter/router.py +++ b/llmrouter/models/mfrouter/router.py @@ -124,7 +124,7 @@ def _load_mf_model(self): # --------------------------------------------------------- def route_single(self, query: Dict[str, Any]) -> Dict[str, Any]: project_root = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) - self.load_model_path = os.path.join(project_root, self.cfg["model_path"]["load_model_path"]) + self.load_model_path = os.path.join(project_root, self.cfg["model_path"]["save_model_path"]) model = self._load_mf_model() q_emb = self.embed_query(query["query"]).to(model.device) @@ -162,7 +162,7 @@ def route_batch(self, batch: Optional[Any] = None, task_name: Optional[str] = No """ # Load model once project_root = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) - self.load_model_path = os.path.join(project_root, self.cfg["model_path"]["load_model_path"]) + self.load_model_path = os.path.join(project_root, self.cfg["model_path"]["save_model_path"]) model = self._load_mf_model() # Determine which data to use diff --git a/llmrouter/models/mlprouter/trainer.py b/llmrouter/models/mlprouter/trainer.py index 4d81ccc..f6d961d 100644 --- a/llmrouter/models/mlprouter/trainer.py +++ b/llmrouter/models/mlprouter/trainer.py @@ -3,6 +3,7 @@ import torch import torch.nn as nn from torch.utils.data import DataLoader, TensorDataset +import json from llmrouter.models.base_trainer import BaseTrainer from llmrouter.utils import save_model, load_model @@ -76,7 +77,7 @@ def train(self): print(f"[MLPTrainer] Loaded initial model from {self.ini_model_path}") except Exception as e: print(f"[MLPTrainer] Could not load initial model: {e}") - + self.loss_history = [] # Prepare data X = self.query_embeddings.float().to(self.device) y = self.label_indices.to(self.device) @@ -106,6 +107,7 @@ def train(self): epoch_losses.append(loss.item()) avg_loss = np.mean(epoch_losses) + self.loss_history.append(avg_loss) # Print progress every 10 epochs or at the end if (epoch + 1) % 10 == 0 or epoch == self.epochs - 1: @@ -122,6 +124,10 @@ def train(self): os.makedirs(os.path.dirname(self.save_model_path), exist_ok=True) save_model(self.model.state_dict(), self.save_model_path) print(f"[MLPTrainer] Model saved to {self.save_model_path}") + loss_path = self.save_model_path.replace('.pth', '_loss.json').replace('.pkl', '_loss.json') + with open(loss_path, 'w') as f: + json.dump(self.loss_history, f) + print(f"[MLPTrainer] Loss history saved to {loss_path}") def evaluate(self): """ diff --git a/llmrouter/models/routerdc/trainer.py b/llmrouter/models/routerdc/trainer.py index 11c93ad..3ee26b0 100644 --- a/llmrouter/models/routerdc/trainer.py +++ b/llmrouter/models/routerdc/trainer.py @@ -152,6 +152,7 @@ def train(self): # Ensure model is on the correct device self.router = self.router.to(self.device) self.router.train() + self.loss_history = [] # Create dataloader train_dataloader = DataLoader( @@ -202,6 +203,7 @@ def train(self): # Update statistics losses.update(loss.item(), scores.size(0)) + self.loss_history.append(loss.item()) pbar.set_postfix({"step": f"{step}", "loss": f"{loss.item():.4f}"}) pbar.update(1)