Files
fastembed/docs/examples/FastEmbed_vs_HF_Comparison.ipynb

327 lines
43 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 🤗 Huggingface vs ⚡ FastEmbed\n",
"\n",
"Comparing the performance of Huggingface's 🤗 Transformers and ⚡ FastEmbed on a simple task on the following machine: Apple M2 Max, 32 GB RAM\n",
"\n",
"## 📦 Imports\n",
"\n",
"Importing the necessary libraries for this comparison."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"import time\n",
"from pathlib import Path\n",
"from typing import Any, Callable, List, Tuple\n",
"\n",
"import numpy as np\n",
"import torch.nn.functional as F\n",
"from fastembed.embedding import DefaultEmbedding\n",
"import matplotlib.pyplot as plt\n",
"from torch import Tensor\n",
"from transformers import AutoModel, AutoTokenizer"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 📖 Data\n",
"\n",
"data is a list of strings, each string is a document."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"12"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"documents: List[str] = [\n",
" \"Chandrayaan-3 is India's third lunar mission\",\n",
" \"It aimed to land a rover on the Moon's surface - joining the US, China and Russia\",\n",
" \"The mission is a follow-up to Chandrayaan-2, which had partial success\",\n",
" \"Chandrayaan-3 will be launched by the Indian Space Research Organisation (ISRO)\",\n",
" \"The estimated cost of the mission is around $35 million\",\n",
" \"It will carry instruments to study the lunar surface and atmosphere\",\n",
" \"Chandrayaan-3 landed on the Moon's surface on 23rd August 2023\",\n",
" \"It consists of a lander named Vikram and a rover named Pragyan similar to Chandrayaan-2. Its propulsion module would act like an orbiter.\",\n",
" \"The propulsion module carries the lander and rover configuration until the spacecraft is in a 100-kilometre (62 mi) lunar orbit\",\n",
" \"The mission used GSLV Mk III rocket for its launch\",\n",
" \"Chandrayaan-3 was launched from the Satish Dhawan Space Centre in Sriharikota\",\n",
" \"Chandrayaan-3 was launched earlier in the year 2023\",\n",
"]\n",
"len(documents)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Setting up 🤗 Huggingface\n",
"\n",
"We'll be using the [Huggingface Transformers](https://huggingface.co/transformers/) with PyTorch library to generate embeddings. We'll be using the same model across both libraries for a fair(er?) comparison."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"torch.Size([12, 384])"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"class HF:\n",
" \"\"\"\n",
" HuggingFace Transformer implementation of FlagEmbedding\n",
" Based on https://huggingface.co/BAAI/bge-base-en\n",
" \"\"\"\n",
"\n",
" def __init__(self, model_id: str):\n",
" self.model = AutoModel.from_pretrained(model_id)\n",
" self.tokenizer = AutoTokenizer.from_pretrained(model_id)\n",
"\n",
" def embed(self, texts: List[str]):\n",
" encoded_input = self.tokenizer(texts, max_length=512, padding=True, truncation=True, return_tensors=\"pt\")\n",
" model_output = self.model(**encoded_input)\n",
" sentence_embeddings = model_output[0][:, 0]\n",
" sentence_embeddings = F.normalize(sentence_embeddings)\n",
" return sentence_embeddings\n",
"\n",
"hf = HF(model_id=\"BAAI/bge-small-en\")\n",
"hf.embed(documents).shape"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Setting up ⚡FastEmbed\n",
"\n",
"Sorry, don't have a lot to set up here. We'll be using the default model, which is Flag Embedding, same as the Huggingface model."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"embedding_model = DefaultEmbedding()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 📊 Comparison\n",
"\n",
"We'll be comparing the following metrics: Minimum, Maximum, Mean, across k runs. Let's write a function to do that:\n",
"\n",
"### 🚀 Calculating Stats"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Huggingface Transformers (Average, Max, Min): (0.06628990173339844, 0.06881093978881836, 0.06376886367797852)\n",
"FastEmbed (Average, Max, Min): (0.037211060523986816, 0.03802299499511719, 0.036399126052856445)\n"
]
}
],
"source": [
"import types\n",
"def calculate_time_stats(embed_func: Callable, documents: list, k: int) -> Tuple[float, float, float]:\n",
" times = []\n",
" for _ in range(k):\n",
" # Timing the embed_func call\n",
" start_time = time.time()\n",
" embeddings = embed_func(documents)\n",
" # Force computation if embed_func returns a generator\n",
" if isinstance(embeddings, types.GeneratorType):\n",
" embeddings = list(embeddings)\n",
"\n",
" end_time = time.time()\n",
" times.append(end_time - start_time)\n",
"\n",
" # Returning mean, max, and min time for the call\n",
" return (sum(times) / k, max(times), min(times))\n",
"\n",
"hf_stats = calculate_time_stats(hf.embed, documents, k=2)\n",
"print(f\"Huggingface Transformers (Average, Max, Min): {hf_stats}\")\n",
"fst_stats = calculate_time_stats(embedding_model.embed, documents, k=2)\n",
"print(f\"FastEmbed (Average, Max, Min): {fst_stats}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 📈 Results\n",
"\n",
"Let's run the comparison and see the results."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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+vfR5e83YzARERGRDAxNRERERDJwThMRkYG44tmxtEsgMmiVrm0q1e3zSBMRERGRDAxNRERERDIwNBERERHJwNBEREREJANDExEREZEMDE1EREREMjA0EREREcnA0EREREQkA0MTERERkQwMTUREREQyMDQRERERycDQRERERCQDQxMRERGRDAxNRERERDIwNBERERHJwNBEREREJANDExEREZEMDE1EREREMjA0EREREcnA0EREREQkA0MTERERkQwMTUREREQyMDQRERERycDQRERERCQDQxMRERGRDAxNRERERDIwNBERERHJwNBEREREJANDExEREZEMDE1EREREMjA0EREREcnA0EREREQkA0MTERERkQwMTUREREQyMDQRERERycDQRERERCQDQxMRERGRDAxNRERERDIwNBERERHJwNBEREREJANDExEREZEMDE1EREREMjA0EREREcnA0EREREQkA0MTERERkQwMTUREREQyMDQRERERycDQRERERCQDQxMRERGRDAxNRERERDIwNBERERHJwNBEREREJANDExEREZEMDE1EREREMjA0EREREcnA0EREREQkQ6mGppiYGDRo0AA2NjZwcnJCly5dcOHCBY0+jx8/RmRkJBwcHGBtbY3u3bsjNTVVo09ycjJCQ0NhaWkJJycnfPTRR8jPz9fos2vXLtSrVw9mZmaoXLkyli5dqlXP/Pnz4enpCXNzczRq1Ah//fWX3veZiIiI/p1KNTTt3r0bkZGROHToELZv344nT54gKCgIOTk5Up/Ro0dj48aNWL16NXbv3o3bt2+jW7du0vKCggKEhoYiLy8PBw4cwLJly7B06VJMmDBB6nP16lWEhoaidevWOH78OEaNGoVBgwZh69atUp/ffvsN0dHRmDhxIo4ePYratWsjODgYd+/e/WeeDCIiIjJoCiGEKO0iCqWlpcHJyQm7d+9GixYtkJWVBUdHR6xYsQJvv/02AOD8+fPw8fHBwYMH0bhxY2zZsgUdO3bE7du34ezsDACIjY3F2LFjkZaWBlNTU4wdOxabN2/G6dOnpW317t0bmZmZiIuLAwA0atQIDRo0wLfffgsAUKvVcHd3x/Dhw/Hxxx+/tHaVSgVbW1tkZWVBqVTq+6khov+AK54dS7sEIoNW6domvY+py99vg5rTlJWVBQCwt7cHACQmJuLJ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",
"text/plain": [
"<Figure size 640x480 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"def plot_character_per_second_comparison(\n",
" hf_stats: Tuple[float, float, float], fst_stats: Tuple[float, float, float], documents: list\n",
"):\n",
" # Calculating total characters in documents\n",
" total_characters = sum(len(doc) for doc in documents)\n",
"\n",
" # Calculating characters per second for each model\n",
" hf_chars_per_sec = total_characters / hf_stats[0] # Mean time is at index 0\n",
" fst_chars_per_sec = total_characters / fst_stats[0]\n",
"\n",
" # Plotting the bar chart\n",
" models = [\"HF Embed (Torch)\", \"FastEmbed\"]\n",
" chars_per_sec = [hf_chars_per_sec, fst_chars_per_sec]\n",
"\n",
" bars = plt.bar(models, chars_per_sec, color=[\"#1f356c\", \"#dd1f4b\"])\n",
" plt.ylabel(\"Characters per Second\")\n",
" plt.title(\"Characters Processed per Second Comparison\")\n",
"\n",
" # Adding the number at the top of each bar\n",
" for bar, chars in zip(bars, chars_per_sec):\n",
" plt.text(\n",
" bar.get_x() + bar.get_width() / 2,\n",
" bar.get_height(),\n",
" f\"{chars:.1f}\",\n",
" ha=\"center\",\n",
" va=\"bottom\",\n",
" color=\"#1f356c\",\n",
" fontsize=12,\n",
" )\n",
"\n",
" plt.show()\n",
"\n",
"\n",
"plot_character_per_second_comparison(hf_stats, fst_stats, documents)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Are the Embeddings the same?\n",
"\n",
"This is a very important question. Let's see if the embeddings are the same."
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/var/folders/b4/grpbcmrd36gc7q5_11whbn540000gn/T/ipykernel_34880/1522845950.py:7: UserWarning: Creating a tensor from a list of numpy.ndarrays is extremely slow. Please consider converting the list to a single numpy.ndarray with numpy.array() before converting to a tensor. (Triggered internally at /Users/runner/work/pytorch/pytorch/pytorch/torch/csrc/utils/tensor_new.cpp:248.)\n",
" calculate_cosine_similarity(hf.embed(documents), Tensor(list(embedding_model.embed(documents))))\n"
]
},
{
"data": {
"text/plain": [
"0.9999997019767761"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def calculate_cosine_similarity(embeddings1: Tensor, embeddings2: Tensor) -> float:\n",
" \"\"\"\n",
" Calculate cosine similarity between two sets of embeddings\n",
" \"\"\"\n",
" return F.cosine_similarity(embeddings1, embeddings2).mean().item()\n",
"\n",
"calculate_cosine_similarity(hf.embed(documents), Tensor(list(embedding_model.embed(documents))))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This indicates the embeddings are quite close to each with a cosine similarity of 0.99. This gives us confidence that the embeddings are the same and we are not sacrificing accuracy for speed."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "fst",
"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.9.17"
},
"orig_nbformat": 4
},
"nbformat": 4,
"nbformat_minor": 2
}