Compare commits

..
Author SHA1 Message Date
Nirant 4db4839995 [PyPi Publish] Bump version to 0.2.5 in pyproject.toml (#156)
* Bump version to 0.2.5 in pyproject.toml

* chore: case insensitive check (#157)
2024-03-20 19:11:44 +05:30
NirantandAnush 96a2a9097a Fix model name typo + Add SPLADE notebook (#155)
* Rename model + Add SPLADE notebook

* Update docs/examples/SPLADE_with_FastEmbed.ipynb

Co-authored-by: Anush  <anushshetty90@gmail.com>

* Update docs/examples/SPLADE_with_FastEmbed.ipynb

Co-authored-by: Anush  <anushshetty90@gmail.com>

* Update docs/examples/SPLADE_with_FastEmbed.ipynb

Co-authored-by: Anush  <anushshetty90@gmail.com>

* Update CANONICAL_COLUMN_VALUES in test_sparse_embeddings.py

---------

Co-authored-by: Anush <anushshetty90@gmail.com>
2024-03-20 18:15:53 +05:30
NirantandKumar Shivendu 256b2265d5 Move CONTRIBUTING.md + Add Test for Adding New Models (#154)
* Move CONTRIBUTING.md + Ad Test for Adding New Models

* Update CONTRIBUTING.md

Co-authored-by: Kumar Shivendu <kshivendu1@gmail.com>

---------

Co-authored-by: Kumar Shivendu <kshivendu1@gmail.com>
2024-03-18 19:58:02 +05:30
NirantandKumar Shivendu 1e91c8d165 Fix Issue Template forms (#152)
* Add CONTRIBUTING.md file with guidelines for contributing to FastEmbed

* Add code linting and pre-commit info to CONTRIBUTING

* Update CONTRIBUTING.md

Co-authored-by: Kumar Shivendu <kshivendu1@gmail.com>

* Add bug/new model issue template and move CONTRIBUTING.md

* Re-organize issue templates

---------

Co-authored-by: Kumar Shivendu <kshivendu1@gmail.com>
2024-03-14 15:11:23 +05:30
Nirant a761f456b2 Add import statement for version debugging (#151) 2024-03-14 15:02:15 +05:30
NirantandKumar Shivendu 287e19c494 Add CONTRIBUTING.md file with guidelines for contributing to FastEmbed (#150)
* Add CONTRIBUTING.md file with guidelines for contributing to FastEmbed

* Add code linting and pre-commit info to CONTRIBUTING

* Update CONTRIBUTING.md

Co-authored-by: Kumar Shivendu <kshivendu1@gmail.com>

* Add bug/new model issue template and move CONTRIBUTING.md

---------

Co-authored-by: Kumar Shivendu <kshivendu1@gmail.com>
2024-03-14 14:50:48 +05:30
Anush 6a94994038 Release v0.2.4 (#149) 2024-03-13 23:56:01 +05:30
Andrey Vasnetsov 361f674e47 avoid changing output dimentionality for a single input (#148) 2024-03-13 23:38:51 +05:30
Nirant 041a606285 Merge pull request #146 from qdrant:v0.2.3
Publish to PyPi with SPLADE models
2024-03-13 18:17:41 +05:30
Nirant Kasliwal 5b937c29f6 Update poetry install command to exclude docs 2024-03-13 18:15:51 +05:30
Nirant Kasliwal 8d368889c0 Update version and add pre-commit dependency 2024-03-13 18:07:31 +05:30
d817da2e01 Add Splade v1 (#144)
* Add SPLADE v1

* WIP SPLADE Export errors

* add ONNX model to HF hub and use that

* Update sentences in Converting_SPLADE_to_ONNX.ipynb

* Remove unnecessary files and directories

* Rename var in TextEmbedding class to use EMBEDDING_MODEL_TYPE

* Add SPLADE to list of text embeddings

* Add SPLADE model support for text embedding

* Fix deprecation warning in embedding.py

* Add test for batch embedding with sparse embeddings

* Refactor import statement in test_sparse_embeddings.py

* Rename nbs

* Update vocab size in SPLADE model

* Fix canonical vector lookup in test_text_onnx_embeddings.py

* review refactoring

* restore list_supported_models in OnnxTextEmbedding

* Remove unused method _preprocess_onnx_input() in SpladePP class

* Update SPLADE_PP_en_v1 source in splade_pp.py

* Refactor onnx_model.py to change base model behavior

* extend tests to sparse values as well as indicies

* chore: pre-commit hooks

---------

Co-authored-by: generall <andrey@vasnetsov.com>
Co-authored-by: Anush008 <anushshetty90@gmail.com>
2024-03-13 18:04:44 +05:30
Artem Daineko 68635efa3f Fix link to Optimus in docs (#143) 2024-03-10 11:45:44 +05:30
Nirant 9ffde58df6 Getting Started Improvements (#138)
* Update FastEmbed README.md

* Rewrite GettingStarted to use TextEmbedding instead of DefaultEmbedding

* Improve grammar

* Update Getting Started.ipynb with model information and document format
2024-03-07 17:01:11 +05:30
Nirant Kasliwal 5603fbe1fb Fix naming typo 2024-03-06 14:30:54 +05:30
Nirant Kasliwal 9ed4486d9c Rename typo in notebook 2024-03-06 14:24:48 +05:30
Nirant Kasliwal e36a39c388 Update author information in notebook 2024-03-06 14:18:48 +05:30
Nirant Kasliwal 97c359c1b9 Add Navrasa LLM download and explanation sections 2024-03-06 14:17:41 +05:30
Nirant Kasliwal 9fd51425fe Add author information and Colab link to notebook 2024-03-06 14:12:04 +05:30
Nirant Kasliwal 0dec22d02d Remove inline outputs 2024-03-06 14:06:46 +05:30
Nirant Kasliwal 8a3b746b71 Rename notebook 2024-03-06 14:05:13 +05:30
Nirant 337ad9c93f Hindi RAG with Qdrant and FastEmbed (#135)
* Add workingnb

* Add A100 Colab

* Remove old checkpoint

* Refactor code to separate HF Token
2024-03-06 14:02:32 +05:30
NirantandAnush 74062e8607 Add attention export functionality to experiments (#134)
* Add attention export functionality

* Update experiments/attention_export.py

Co-authored-by: Anush <anushshetty90@gmail.com>

---------

Co-authored-by: Anush <anushshetty90@gmail.com>
2024-03-04 16:30:17 +05:30
31 changed files with 2921 additions and 982 deletions
View File
+46
View File
@@ -0,0 +1,46 @@
name: Bug/New Model Request
description: File a bug report/Request a new Model
title: "[Bug/Model Request]: "
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: textarea
id: what-happened
attributes:
label: What happened?
description: Also tell us, what did you expect to happen?
placeholder: Tell us what you see!
value: "A bug happened!"
validations:
required: true
- type: dropdown
id: version
attributes:
label: Version
description: What version of FastEmbed are you running? python -c "import fastembed; print(fastembed.__version__)". If you're not on the latest, please upgrade and see if the problem persists.
options:
- 0.2.4 (Latest)
- 0.2.3
- 0.2.2
- 0.2.1
- 0.1.x
default: 0
validations:
required: true
- type: dropdown
id: os
attributes:
label: What os are you seeing the problem on?
multiple: true
options:
- Linux
- MacOS
- Windows
- type: textarea
id: logs
attributes:
label: Relevant stack traces and/or logs
description: Please copy and paste any relevant raised exceptions. This will be automatically formatted into code, so no need for backticks.
render: shell
+5
View File
@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: GitHub Community Support
url: https://github.com/qdrant/fastembed/discussions
about: Please ask and answer questions here.
+1 -1
View File
@@ -38,7 +38,7 @@ jobs:
run: |
python -m pip install poetry
poetry config virtualenvs.create false
poetry install --no-interaction --no-ansi
poetry install --no-interaction --no-ansi --without docs
- name: Run tests
run: |
export IS_UBUNTU_CI=$(test "${{ matrix.os }}" = "ubuntu-latest" && echo "true" || echo "false")
+2 -8
View File
@@ -168,11 +168,5 @@ local_cache/*/*
docs/experimental/*.parquet
docs/experimental/*.bin
qdrant_storage/*
fooling_around/fast-multilingual-e5-large/config.json
fooling_around/fast-multilingual-e5-large/model_optimized.onnx
fooling_around/fast-multilingual-e5-large/model_optimized.onnx.data
fooling_around/fast-multilingual-e5-large/ort_config.json
fooling_around/fast-multilingual-e5-large/sentencepiece.bpe.model
fooling_around/fast-multilingual-e5-large/special_tokens_map.json
fooling_around/fast-multilingual-e5-large/tokenizer_config.json
fooling_around/fast-multilingual-e5-large/tokenizer.json
fooling_around/*
experiments/models/*
+78
View File
@@ -0,0 +1,78 @@
# Contributing to FastEmbed!
:+1::tada: First off, thanks for taking the time to contribute! :tada::+1:
The following is a set of guidelines for contributing to FastEmbed. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
## Table Of Contents
[I don't want to read this whole thing, I just have a question!!!](#i-dont-want-to-read-this-whole-thing-i-just-have-a-question)
[How Can I Contribute?](#how-can-i-contribute)
* [Your First Code Contribution](#your-first-code-contribution)
* [Adding New Models](#adding-new-models)
[Styleguides](#styleguides)
* [Code Lint](#code-lint)
* [Pre-Commit Hooks](#pre-commit-hooks)
## I don't want to read this whole thing I just have a question!!!
> **Note:** Please don't file an issue to ask a question. You'll get faster results by using the resources below:
* [FastEmbed Docs](https://qdrant.github.io/fastembed/)
* [Qdrant Discord](https://discord.gg/Qy6HCJK9Dc)
## How Can I Contribute?
## How Do I Submit A (Good) Bug Report?
Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/).
Explain the problem and include additional details to help maintainers reproduce the problem:
* **Use a clear and descriptive title** for the issue to identify the problem.
* **Describe the exact steps which reproduce the problem** in as many details as possible. For example, start by explaining how you are using FastEmbed, e.g. with Langchain, Qdrant Client, Llama Index and which command exactly you used. When listing steps, **don't just say what you did, but explain how you did it**.
* **Provide specific examples to demonstrate the steps**. Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. If you're providing snippets in the issue, use [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines).
* **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior.
* **Explain which behavior you expected to see instead and why.**
* **If the problem is related to performance or memory**, include a [call stack profile capture](https://github.com/joerick/pyinstrument) and your observations.
Include details about your configuration and environment:
* **Which version of FastEmbed are you using?** You can get the exact version by running `python -c "import fastembed; print(fastembed.__version__)"`.
* **What's the name and version of the OS you're using**?
* **Which packages do you have installed?** You can get that list by running `pip freeze`
### Your First Code Contribution
Unsure where to begin contributing to FastEmbed? You can start by looking through these `good-first-issue`issues:
* [Good First Issue](https://github.com/qdrant/fastembed/labels/good%20first%20issue) - issues which should only require a few lines of code, and a test or two. These are a great way to get started with FastEmbed. This includes adding new models which are already tested and ready on Huggingface Hub.
## Pull Requests
The best way to learn about the mechanics of FastEmbed is to start working on it.
### Your First Code Contribution
Your first code contribution can be small bug fixes:
1. This PR adds a small bug fix for a single input: https://github.com/qdrant/fastembed/pull/148
2. This PR adds a check for the right file location and extension, specific to an OS: https://github.com/qdrant/fastembed/pull/128
Even documentation improvements and tests are most welcome:
1. This PR fixes a README link: https://github.com/qdrant/fastembed/pull/143
### Adding New Models
1. Open Requests for New Models are [here](https://github.com/qdrant/fastembed/labels/model%20request).
2. There are quite a few pull requests that were merged for this purpose and you can use them as a reference. Here is an example: https://github.com/qdrant/fastembed/pull/129
3. Make sure to add tests for the new model
- The CANONICAL_VECTOR values must come from a reference implementation usually from Huggingface Transformers or Sentence Transformers
- Here is a reference [Colab Notebook](https://colab.research.google.com/drive/1tNdV3DsiwsJzu2AXnUnoeF5av1Hp8HF1?usp=sharing) for how we will evaluate whether your VECTOR values in the test are correct or not.
## Styleguides
### Code Lint
We use ruff for code linting. It should be installed with poetry since it's a dev dependency.
### Pre-Commit Hooks
We use pre-commit hooks to ensure that the code is linted before it's committed. You can install pre-commit hooks by running `pre-commit install` in the root directory of the project.
+19 -16
View File
@@ -4,16 +4,13 @@ FastEmbed is a lightweight, fast, Python library built for embedding generation.
The default text embedding (`TextEmbedding`) model is Flag Embedding, the top model in the [MTEB](https://huggingface.co/spaces/mteb/leaderboard) leaderboard. It supports "query" and "passage" prefixes for the input text. Here is an example for [Retrieval Embedding Generation](https://qdrant.github.io/fastembed/examples/Retrieval_with_FastEmbed/) and how to use [FastEmbed with Qdrant](https://qdrant.github.io/fastembed/examples/Usage_With_Qdrant/).
1. Light & Fast
- Quantized model weights
- ONNX Runtime, no PyTorch dependency
- CPU-first design
- Data-parallelism for encoding of large datasets
## 📈 Why FastEmbed?
2. Accuracy/Recall
- Better than OpenAI Ada-002
- Default is Flag Embedding, which is top of the [MTEB](https://huggingface.co/spaces/mteb/leaderboard) leaderboard
- List of [supported models](https://qdrant.github.io/fastembed/examples/Supported_Models/) - including multilingual models
1. Light: FastEmbed is a lightweight library with few external dependencies. We don't require a GPU and don't download GBs of PyTorch dependencies, and instead use the ONNX Runtime. This makes it a great candidate for serverless runtimes like AWS Lambda.
2. Fast: FastEmbed is designed for speed. We use the ONNX Runtime, which is faster than PyTorch. We also use data-parallelism for encoding large datasets.
3. Accurate: FastEmbed is better than OpenAI Ada-002. We also [supported](https://qdrant.github.io/fastembed/examples/Supported_Models/) an ever expanding set of models, including a few multilingual models.
## 🚀 Installation
@@ -26,18 +23,24 @@ pip install fastembed
## 📖 Quickstart
```python
import numpy as np
from fastembed import TextEmbedding
from typing import List
import numpy as np
# Example list of documents
documents: List[str] = [
"passage: Hello, World!",
"query: Hello, World!", # these are two different embedding
"passage: This is an example passage.",
"fastembed is supported by and maintained by Qdrant." # You can leave out the prefix but it's recommended
"This is built to be faster and lighter than other embedding libraries e.g. Transformers, Sentence-Transformers, etc.",
"fastembed is supported by and maintained by Qdrant.",
]
embedding_model = TextEmbedding(model_name="BAAI/bge-base-en")
embeddings: List[np.ndarray] = list(embedding_model.embed(documents)) # Note the list() call - this is a generator
# This will trigger the model download and initialization
embedding_model = TextEmbedding()
print("The model BAAI/bge-small-en-v1.5 is ready to use.")
embeddings_generator = embedding_model.embed(documents) # reminder this is a generator
embeddings_list = list(embedding_model.embed(documents))
# you can also convert the generator to a list, and that to a numpy array
len(embeddings_list[0]) # Vector of 384 dimensions
```
## Usage with Qdrant
+139 -141
View File
@@ -11,7 +11,9 @@
"\n",
"## Quick Start\n",
"\n",
"The fastembed package is designed to be easy to use. The main class is the `Embedding` class. It takes a list of strings as input and returns a list of vectors as output. The `Embedding` class is initialized with a model file."
"The fastembed package is designed to be easy to use. We'll be using `TextEmbedding` class. It takes a list of strings as input and returns an generator of vectors. If you're seeing generators for the first time, don't worry, you can convert it to a list using `list()`.\n",
"\n",
"> 💡 You can learn more about generators from [Python Wiki](https://wiki.python.org/moin/Generators)"
]
},
{
@@ -21,15 +23,7 @@
"metadata": {},
"outputs": [],
"source": [
"!pip install fastembed --upgrade --quiet # Install fastembed "
]
},
{
"cell_type": "markdown",
"id": "ed81d725",
"metadata": {},
"source": [
"Make the necessary imports, initialize the `Embedding` class, and embed your data into vectors:"
"!pip install -Uqq fastembed # Install fastembed"
]
},
{
@@ -39,43 +33,115 @@
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"100%|██████████| 76.7M/76.7M [00:05<00:00, 15.0MiB/s]\n",
"100%|██████████| 3/3 [00:00<00:00, 455.37it/s]"
]
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "890cc3b969354eec8d149d143e301a7a",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Fetching 9 files: 0%| | 0/9 [00:00<?, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"(384,)\n"
"The model BAAI/bge-small-en-v1.5 is ready to use.\n"
]
},
{
"name": "stderr",
"data": {
"text/plain": [
"384"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import numpy as np\n",
"from fastembed import TextEmbedding\n",
"from typing import List\n",
"\n",
"# Example list of documents\n",
"documents: List[str] = [\n",
" \"This is built to be faster and lighter than other embedding libraries e.g. Transformers, Sentence-Transformers, etc.\",\n",
" \"fastembed is supported by and maintained by Qdrant.\",\n",
"]\n",
"\n",
"# This will trigger the model download and initialization\n",
"embedding_model = TextEmbedding()\n",
"print(\"The model BAAI/bge-small-en-v1.5 is ready to use.\")\n",
"\n",
"embeddings_generator = embedding_model.embed(documents) # reminder this is a generator\n",
"embeddings_list = list(embedding_model.embed(documents))\n",
"# you can also convert the generator to a list, and that to a numpy array\n",
"len(embeddings_list[0]) # Vector of 384 dimensions"
]
},
{
"cell_type": "markdown",
"id": "d772190b",
"metadata": {},
"source": [
"> 💡 **Why do we use generators?**\n",
"> \n",
"> We use them to save memory mostly. Instead of loading all the vectors into memory, we can load them one by one. This is useful when you have a large dataset and you don't want to load all the vectors at once."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "8a225cb8",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n"
"Document: This is built to be faster and lighter than other embedding libraries e.g. Transformers, Sentence-Transformers, etc.\n",
"Vector of type: <class 'numpy.ndarray'> with shape: (384,)\n",
"Document: fastembed is supported by and maintained by Qdrant.\n",
"Vector of type: <class 'numpy.ndarray'> with shape: (384,)\n"
]
}
],
"source": [
"from typing import List\n",
"import numpy as np\n",
"from fastembed.embedding import DefaultEmbedding\n",
"embeddings_generator = embedding_model.embed(documents) # reminder this is a generator\n",
"\n",
"# Example list of documents\n",
"documents: List[str] = [\n",
" \"Hello, World!\",\n",
" \"This is an example document.\",\n",
" \"fastembed is supported by and maintained by Qdrant.\",\n",
"]\n",
"# Initialize the DefaultEmbedding class\n",
"embedding_model = DefaultEmbedding()\n",
"embeddings: List[np.ndarray] = list(embedding_model.embed(documents))\n",
"print(embeddings[0].shape)"
"for doc, vector in zip(documents, embeddings_generator):\n",
" print(\"Document:\", doc)\n",
" print(f\"Vector of type: {type(vector)} with shape: {vector.shape}\")"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "769a1be9",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(2, 384)"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"embeddings_list = np.array(\n",
" list(embedding_model.embed(documents))\n",
") # you can also convert the generator to a list, and that to a numpy array\n",
"embeddings_list.shape"
]
},
{
@@ -83,142 +149,74 @@
"id": "8c49ae50",
"metadata": {},
"source": [
"## Let's think step by step"
]
},
{
"cell_type": "markdown",
"id": "92cf4b76",
"metadata": {},
"source": [
"### Setup\n",
"\n",
"Importing the required classes and modules:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "c0a6f634",
"metadata": {},
"outputs": [],
"source": [
"from typing import List\n",
"import numpy as np\n",
"from fastembed.embedding import DefaultEmbedding"
]
},
{
"cell_type": "markdown",
"id": "3fd03a71",
"metadata": {},
"source": [
"Notice that we are using the DefaultEmbedding -- which is a quantized, state of the Art Flag Embedding model which beats OpenAI's Embedding by a large margin. \n",
"\n",
"### Prepare your Documents\n",
"You can define a list of documents that you'd like to embed. These can be sentences, paragraphs, or even entire documents. \n",
"We're using [BAAI/bge-small-en-v1.5](https://huggingface.co/BAAI/bge-small-en-v1.5) a state of the art Flag Embedding model. The model does better than OpenAI text-embedding-ada-002. We've made it even faster by converting it to ONNX format and quantizing the model for you.\n",
"\n",
"#### Format of the Document List\n",
"\n",
"1. List of Strings: Your documents must be in a list, and each document must be a string\n",
"2. For Retrieval Tasks: If you're working with queries and passages, you can add special labels to them:\n",
"2. For Retrieval Tasks with our default: If you're working with queries and passages, you can add special labels to them:\n",
"- **Queries**: Add \"query:\" at the beginning of each query string\n",
"- **Passages**: Add \"passage:\" at the beginning of each passage string"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "145a56ce",
"metadata": {},
"outputs": [],
"source": [
"# Example list of documents\n",
"documents: List[str] = [\n",
" \"passage: Hello, World!\",\n",
" \"query: Hello, World!\", # these are two different embedding\n",
" \"passage: This is an example passage.\",\n",
" # You can leave out the prefix but it's recommended\n",
" \"fastembed is supported by and maintained by Qdrant.\",\n",
"]"
]
},
{
"cell_type": "markdown",
"id": "1cb3cc87",
"metadata": {},
"source": [
"### Load the Embedding Model Weights\n",
"Next, initialize the Embedding class with the desired parameters. Here, \"BAAI/bge-small-en\" is the pre-trained model name, and max_length=512 is the maximum token length for each document.\n",
"- **Passages**: Add \"passage:\" at the beginning of each passage string\n",
"\n",
"This will download the model weights, decompress to directory `local_cache` and load them into the Embedding class.\n",
"## Beyond the default model\n",
"\n",
"#### Initialize DefaultEmbedding\n",
"\n",
"We will initialize Flag Embeddings with the model name and the maximum token length. That is the DefaultEmbedding class with the model name \"BAAI/bge-small-en\" and max_length=512."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "272c8915",
"metadata": {},
"outputs": [],
"source": [
"embedding_model = DefaultEmbedding()"
]
},
{
"cell_type": "markdown",
"id": "5549d501",
"metadata": {},
"source": [
"### Embed your Documents\n",
"\n",
"Use the embed method of the embedding model to transform the documents into a List of np.array. The method returns a generator, so we cast it to a list to get the embeddings."
"The default model is built for speed and efficiency. If you need a more accurate model, you can use the `TextEmbedding` class to load any model from our list of available models. You can find the list of available models using `TextEmbedding.list_supported_models()`."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "8013eee9",
"id": "2e9c8766",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"100%|██████████| 4/4 [00:00<00:00, 361.82it/s]\n"
]
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "9470ec542f3c4400a42452c2489a1abc",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Fetching 8 files: 0%| | 0/8 [00:00<?, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"embeddings: List[np.ndarray] = list(embedding_model.embed(documents))"
]
},
{
"cell_type": "markdown",
"id": "e5b5a6ad",
"metadata": {},
"source": [
"You can print the shape of the embeddings to understand their dimensions. Typically, the shape will indicate the number of dimensions in the vector."
"multilingual_large_model = TextEmbedding(\"intfloat/multilingual-e5-large\") # This can take a few minutes to download"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "0d8c8e08",
"id": "a9e70f0e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"(384,)\n"
]
"data": {
"text/plain": [
"(4, 1024)"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"print(embeddings[0].shape) # (384,) or similar output"
"np.array(\n",
" list(multilingual_large_model.embed([\"Hello, world!\", \"你好世界\", \"¡Hola Mundo!\", \"नमस्ते!\"]))\n",
").shape # Vector of 1024 dimensions"
]
},
{
"cell_type": "markdown",
"id": "64fe20ed",
"metadata": {},
"source": [
"Next: Checkout how to use FastEmbed with Qdrant for similarity search: [FastEmbed with Qdrant](https://qdrant.github.io/fastembed/examples/Usage_With_Qdrant/)"
]
}
],
@@ -238,7 +236,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.17"
"version": "3.10.13"
}
},
"nbformat": 4,
File diff suppressed because it is too large Load Diff
+392
View File
@@ -0,0 +1,392 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Introduction to SPLADE with FastEmbed\n",
"\n",
"In this notebook, we will explore how to generate Sparse Vectors -- in particular a variant of the [SPLADE](https://arxiv.org/abs/2107.05720).\n",
"\n",
"> 💡 The original [naver/SPLADE](https://github.com/naver/splade) models were licensed CC BY-NC-SA 4.0 -- Not for Commercial Use. This [SPLADE++](https://huggingface.co/prithivida/Splade_PP_en_v1) model is Apache License and hence, licensed for commercial use. \n",
"\n",
"## Outline:\n",
"1. [What is SPLADE?](#What-is-SPLADE?)\n",
"2. [Setting up the environment](#Setting-up-the-environment)\n",
"3. Generating SPLADE vectors with FastEmbed\n",
"4. Understanding SPLADE vectors\n",
"5. Applications of SPLADE vectors\n",
"\n",
"## What is SPLADE?\n",
"\n",
"SPLADE was a novel method for _learning_ sparse vectors for text representation. This model beats BM25 -- the underlying approach for the Elastic/Lucene family of implementations. Thus making it highly effective for tasks such as information retrieval, document classification, and more. \n",
"\n",
"The key advantage of SPLADE is its ability to generate sparse vectors, which are more efficient and interpretable than dense vectors. This makes SPLADE a powerful tool for handling large-scale text data.\n",
"\n",
"## Setting up the environment\n",
"\n",
"This notebook uses few dependencies, which are installed below: "
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"# !pip install -q fastembed"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's get started! 🚀"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"from fastembed.sparse.sparse_text_embedding import SparseTextEmbedding, SparseEmbedding\n",
"from typing import List"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> You can find the list of all supported Sparse Embedding models by calling this API: `SparseTextEmbedding.list_supported_models()`"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[{'model': 'prithvida/Splade_PP_en_v1',\n",
" 'vocab_size': 30522,\n",
" 'description': 'Independent Implementation of SPLADE++ Model for English',\n",
" 'size_in_GB': 0.532,\n",
" 'sources': {'hf': 'Qdrant/SPLADE_PP_en_v1'}}]"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"SparseTextEmbedding.list_supported_models()"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "b4308dd64349499b9107d723a035ce73",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Fetching 9 files: 0%| | 0/9 [00:00<?, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"model_name = \"prithvida/Splade_PP_en_v1\"\n",
"# This triggers the model download\n",
"model = SparseTextEmbedding(model_name=model_name)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"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",
"sparse_embeddings_list: List[SparseEmbedding] = list(\n",
" model.embed(documents, batch_size=6)\n",
") # batch_size is optional, notice the generator"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"SparseEmbedding(values=array([0.05297208, 0.01963477, 0.36459631, 1.38508618, 0.71776593,\n",
" 0.12667948, 0.46230844, 0.446771 , 0.26897505, 1.01519883,\n",
" 1.5655334 , 0.29412213, 1.53102326, 0.59785569, 1.1001817 ,\n",
" 0.02079751, 0.09955651, 0.44249091, 0.09747757, 1.53519952,\n",
" 1.36765671, 0.15740395, 0.49882549, 0.38629025, 0.76612782,\n",
" 1.25805044, 0.39058095, 0.27236196, 0.45152301, 0.48262018,\n",
" 0.26085234, 1.35912788, 0.70710695, 1.71639752]), indices=array([ 1010, 1011, 1016, 1017, 2001, 2018, 2034, 2093, 2117,\n",
" 2319, 2353, 2509, 2634, 2686, 2796, 2817, 2922, 2959,\n",
" 3003, 3148, 3260, 3390, 3462, 3523, 3822, 4231, 4316,\n",
" 4774, 5590, 5871, 6416, 11926, 12076, 16469]))"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"index = 0\n",
"sparse_embeddings_list[index]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The previous output is a SparseEmbedding object for the first document in our list.\n",
"\n",
"It contains two arrays: values and indices. \n",
"- The 'values' array represents the weights of the features (tokens) in the document.\n",
"- The 'indices' array represents the indices of these features in the model's vocabulary.\n",
"\n",
"Each pair of corresponding values and indices represents a token and its weight in the document."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Token at index 1010 has weight 0.05297207832336426\n",
"Token at index 1011 has weight 0.01963476650416851\n",
"Token at index 1016 has weight 0.36459630727767944\n",
"Token at index 1017 has weight 1.385086178779602\n",
"Token at index 2001 has weight 0.7177659273147583\n"
]
}
],
"source": [
"# Let's print the first 5 features and their weights for better understanding.\n",
"for i in range(5):\n",
" print(f\"Token at index {sparse_embeddings_list[0].indices[i]} has weight {sparse_embeddings_list[0].values[i]}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This is still a little abstract, so let's use the tokenizer vocab to make sense of these indices."
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "3e13d98f38244741aa8d5473991a30aa",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"tokenizer_config.json: 0%| | 0.00/1.38k [00:00<?, ?B/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "67128c35e1504a99b8ec61b0f572a90c",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"vocab.txt: 0%| | 0.00/232k [00:00<?, ?B/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "85522823dd444b218c00e4e77c9d5d51",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"tokenizer.json: 0%| | 0.00/712k [00:00<?, ?B/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "4f890e772918465dba9177f0e74478f6",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"special_tokens_map.json: 0%| | 0.00/695 [00:00<?, ?B/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"{',': 0.05297207832336426, '-': 0.01963476650416851, '2': 0.36459630727767944, '3': 1.385086178779602, 'was': 0.7177659273147583, 'had': 0.12667948007583618, 'first': 0.46230843663215637, 'three': 0.4467709958553314, 'second': 0.26897504925727844, '##an': 1.015198826789856, 'third': 1.5655333995819092, '##3': 0.2941221296787262, 'india': 1.5310232639312744, 'space': 0.5978556871414185, 'indian': 1.1001816987991333, 'study': 0.02079751156270504, 'largest': 0.09955651313066483, 'fourth': 0.44249090552330017, 'leader': 0.09747757017612457, '##ya': 1.535199522972107, 'mission': 1.3676567077636719, 'launched': 0.15740394592285156, 'flight': 0.4988254904747009, 'iii': 0.3862902522087097, '3rd': 0.7661278247833252, 'moon': 1.2580504417419434, 'vehicle': 0.390580952167511, 'planet': 0.27236196398735046, 'expedition': 0.4515230059623718, 'satellite': 0.4826201796531677, 'missions': 0.2608523368835449, 'lunar': 1.3591278791427612, 'spacecraft': 0.7071069478988647, 'chandra': 1.7163975238800049}\n"
]
}
],
"source": [
"from transformers import AutoTokenizer\n",
"\n",
"tokenizer = AutoTokenizer.from_pretrained(SparseTextEmbedding.list_supported_models()[0][\"sources\"][\"hf\"])"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{\n",
" \"chandra\": 1.7163975238800049,\n",
" \"third\": 1.5655333995819092,\n",
" \"##ya\": 1.535199522972107,\n",
" \"india\": 1.5310232639312744,\n",
" \"3\": 1.385086178779602,\n",
" \"mission\": 1.3676567077636719,\n",
" \"lunar\": 1.3591278791427612,\n",
" \"moon\": 1.2580504417419434,\n",
" \"indian\": 1.1001816987991333,\n",
" \"##an\": 1.015198826789856,\n",
" \"3rd\": 0.7661278247833252,\n",
" \"was\": 0.7177659273147583,\n",
" \"spacecraft\": 0.7071069478988647,\n",
" \"space\": 0.5978556871414185,\n",
" \"flight\": 0.4988254904747009,\n",
" \"satellite\": 0.4826201796531677,\n",
" \"first\": 0.46230843663215637,\n",
" \"expedition\": 0.4515230059623718,\n",
" \"three\": 0.4467709958553314,\n",
" \"fourth\": 0.44249090552330017,\n",
" \"vehicle\": 0.390580952167511,\n",
" \"iii\": 0.3862902522087097,\n",
" \"2\": 0.36459630727767944,\n",
" \"##3\": 0.2941221296787262,\n",
" \"planet\": 0.27236196398735046,\n",
" \"second\": 0.26897504925727844,\n",
" \"missions\": 0.2608523368835449,\n",
" \"launched\": 0.15740394592285156,\n",
" \"had\": 0.12667948007583618,\n",
" \"largest\": 0.09955651313066483,\n",
" \"leader\": 0.09747757017612457,\n",
" \",\": 0.05297207832336426,\n",
" \"study\": 0.02079751156270504,\n",
" \"-\": 0.01963476650416851\n",
"}\n"
]
}
],
"source": [
"import json\n",
"\n",
"\n",
"def get_tokens_and_weights(sparse_embedding, tokenizer):\n",
" token_weight_dict = {}\n",
" for i in range(len(sparse_embedding.indices)):\n",
" token = tokenizer.decode([sparse_embedding.indices[i]])\n",
" weight = sparse_embedding.values[i]\n",
" token_weight_dict[token] = weight\n",
"\n",
" # Sort the dictionary by weights\n",
" token_weight_dict = dict(sorted(token_weight_dict.items(), key=lambda item: item[1], reverse=True))\n",
" return token_weight_dict\n",
"\n",
"\n",
"# Test the function with the first SparseEmbedding\n",
"print(json.dumps(get_tokens_and_weights(sparse_embeddings_list[index], tokenizer), indent=4))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Observations\n",
"\n",
"1. The relative order of importance is quite useful. The most important tokens in the sentence have the highest weights.\n",
"1. **Term Expansion**: The model can expand the terms in the document. This means that the model can generate weights for tokens that are not present in the document but are related to the tokens in the document. This is a powerful feature that allows the model to capture the context of the document. Here, you'll see that the model has added the tokens '3' from 'third' and 'moon' from 'lunar' to the sparse vector.\n",
"\n",
"## Design Choices\n",
"\n",
"1. The weights are not normalized. This means that the sum of the weights is not 1 or 100. This is a common practice in sparse embeddings, as it allows the model to capture the importance of each token in the document.\n",
"1. Tokens are included in the sparse vector only if they are present in the model's vocabulary. This means that the model will not generate a weight for tokens that it has not seen during training."
]
}
],
"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.10.13"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
+1 -1
View File
@@ -6,7 +6,7 @@ The default embedding supports "query" and "passage" prefixes for the input text
1. Light & Fast
- Quantized model weights
- ONNX Runtime for inference via [Optimum](github.com/huggingface/optimum)
- ONNX Runtime for inference via [Optimum](https://github.com/huggingface/optimum)
2. Accuracy/Recall
- Better than OpenAI Ada-002
+371
View File
@@ -0,0 +1,371 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import torch\n",
"from transformers import AutoModelForMaskedLM, AutoTokenizer"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Running the model with Transformers and Torch"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"sentences = [\n",
" \"Hello World\",\n",
" \"Built by Nirant Kasliwal\",\n",
"]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## PyTorch Code from the [SPLADERunner](https://github.com/PrithivirajDamodaran/SPLADERunner) library"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"hf_token = \"<your_hf_token_here>\""
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Output Logits shape: torch.Size([2, 10, 30522])\n",
"Output Attention mask shape: torch.Size([2, 10])\n",
"Sparse Vector shape: torch.Size([2, 30522])\n",
"SPLADE BOW rep for sentence:\tBuilt by Nirant Kasliwal\n",
"[('##rant', 2.02), ('built', 1.94), ('##wal', 1.79), ('##sl', 1.69), ('build', 1.57), ('ka', 1.4), ('ni', 1.26), ('made', 0.93), ('architect', 0.76), ('was', 0.69), ('who', 0.61), ('his', 0.5), ('wrote', 0.47), ('india', 0.45), ('company', 0.41), ('##i', 0.41), ('he', 0.37), ('manufacturer', 0.36), ('by', 0.35), ('engineer', 0.33), ('architecture', 0.33), ('ko', 0.23), ('him', 0.22), ('invented', 0.19), ('said', 0.14), ('k', 0.11), ('man', 0.11), ('statue', 0.11), ('bomb', 0.1), ('##wa', 0.1), ('builder', 0.09), ('.', 0.07), ('started', 0.06), (',', 0.04), ('ku', 0.03)]\n"
]
}
],
"source": [
"# Download the model and tokenizer\n",
"device = \"cuda:0\" if torch.cuda.is_available() else \"cpu\"\n",
"tokenizer = AutoTokenizer.from_pretrained(\"prithivida/Splade_PP_en_v1\", token=hf_token)\n",
"reverse_voc = {v: k for k, v in tokenizer.vocab.items()}\n",
"model = AutoModelForMaskedLM.from_pretrained(\"prithivida/Splade_PP_en_v1\", token=hf_token)\n",
"model.to(device)\n",
"\n",
"# Tokenize the input\n",
"inputs = tokenizer(sentences, return_tensors=\"pt\", padding=True, truncation=True, max_length=512)\n",
"inputs = {key: val.to(device) for key, val in inputs.items()}\n",
"input_ids = inputs[\"input_ids\"]\n",
"attention_mask = inputs[\"attention_mask\"]\n",
"token_type_ids = inputs[\"token_type_ids\"]\n",
"\n",
"# Run model and prepare sparse vector\n",
"outputs = model(**inputs)\n",
"logits = outputs.logits\n",
"print(\"Output Logits shape: \", logits.shape)\n",
"print(\"Output Attention mask shape: \", attention_mask.shape)\n",
"relu_log = torch.log(1 + torch.relu(logits))\n",
"weighted_log = relu_log * attention_mask.unsqueeze(-1)\n",
"max_val, _ = torch.max(weighted_log, dim=1)\n",
"vector = max_val.squeeze()\n",
"print(\"Sparse Vector shape: \", vector.shape)\n",
"# print(\"Number of Actual Dimensions: \", len(cols))\n",
"cols = [vec.nonzero().squeeze().cpu().tolist() for vec in vector]\n",
"weights = [vec[col].cpu().tolist() for vec, col in zip(vector, cols)]\n",
"\n",
"idx = 1\n",
"cols, weights = cols[idx], weights[idx]\n",
"# Print the BOW representation\n",
"d = {k: v for k, v in zip(cols, weights)}\n",
"sorted_d = {k: v for k, v in sorted(d.items(), key=lambda item: item[1], reverse=True)}\n",
"bow_rep = []\n",
"for k, v in sorted_d.items():\n",
" bow_rep.append((reverse_voc[k], round(v, 2)))\n",
"print(f\"SPLADE BOW rep for sentence:\\t{sentences[idx]}\\n{bow_rep}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Export with output_attentions and logits"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Exporting model to models/nirantk_SPLADE_PP_en_v1\n"
]
},
{
"data": {
"text/plain": [
"('models/nirantk_SPLADE_PP_en_v1/tokenizer_config.json',\n",
" 'models/nirantk_SPLADE_PP_en_v1/special_tokens_map.json',\n",
" 'models/nirantk_SPLADE_PP_en_v1/vocab.txt',\n",
" 'models/nirantk_SPLADE_PP_en_v1/added_tokens.json',\n",
" 'models/nirantk_SPLADE_PP_en_v1/tokenizer.json')"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from transformers import AutoTokenizer\n",
"\n",
"model_id = \"nirantk/SPLADE_PP_en_v1\"\n",
"output_dir = f\"models/{model_id.replace('/', '_')}\"\n",
"model_kwargs = {\"output_attentions\": True, \"return_dict\": True}\n",
"\n",
"print(f\"Exporting model to {output_dir}\")\n",
"tokenizer.save_pretrained(output_dir)\n",
"# main_export(\n",
"# model_id,\n",
"# output=output_dir,\n",
"# no_post_process=True,\n",
"# model_kwargs=model_kwargs,\n",
"# token=hf_token,\n",
"# )"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Running the model with ONNX"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"from optimum.onnxruntime import ORTModelForMaskedLM\n",
"\n",
"model = ORTModelForMaskedLM.from_pretrained(\"nirantk/SPLADE_PP_en_v1\")\n",
"tokenizer = AutoTokenizer.from_pretrained(\"nirantk/SPLADE_PP_en_v1\")"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"inputs = tokenizer(sentences, return_tensors=\"pt\", padding=True, truncation=True, max_length=512)\n",
"inputs = {key: val.to(device) for key, val in inputs.items()}\n",
"input_ids = inputs[\"input_ids\"]\n",
"attention_mask = inputs[\"attention_mask\"]\n",
"token_type_ids = inputs[\"token_type_ids\"]\n",
"\n",
"onnx_input = {\n",
" \"input_ids\": input_ids.cpu().numpy(),\n",
" \"attention_mask\": attention_mask.cpu().numpy(),\n",
" \"token_type_ids\": token_type_ids.cpu().numpy(),\n",
"}\n",
"\n",
"logits = model(**onnx_input).logits"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(2, 10, 30522)"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"logits.shape"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Output Logits shape: (2, 10, 30522)\n",
"Sparse Vector shape: (2, 30522)\n",
"SPLADE BOW rep for sentence:\tBuilt by Nirant Kasliwal\n",
"[('##rant', 2.02), ('built', 1.94), ('##wal', 1.79), ('##sl', 1.69), ('build', 1.57), ('ka', 1.4), ('ni', 1.26), ('made', 0.93), ('architect', 0.76), ('was', 0.69), ('who', 0.61), ('his', 0.5), ('wrote', 0.47), ('india', 0.45), ('company', 0.41), ('##i', 0.41), ('he', 0.37), ('manufacturer', 0.36), ('by', 0.35), ('engineer', 0.33), ('architecture', 0.33), ('ko', 0.23), ('him', 0.22), ('invented', 0.19), ('said', 0.14), ('k', 0.11), ('man', 0.11), ('statue', 0.11), ('bomb', 0.1), ('##wa', 0.1), ('builder', 0.09), ('.', 0.07), ('started', 0.06), (',', 0.04), ('ku', 0.03)]\n"
]
}
],
"source": [
"print(\"Output Logits shape: \", logits.shape)\n",
"\n",
"relu_log = np.log(1 + np.maximum(logits, 0))\n",
"\n",
"# Equivalent to relu_log * attention_mask.unsqueeze(-1)\n",
"# For NumPy, you might need to explicitly expand dimensions if 'attention_mask' is not already 2D\n",
"weighted_log = relu_log * np.expand_dims(attention_mask, axis=-1)\n",
"\n",
"# Equivalent to torch.max(weighted_log, dim=1)\n",
"# NumPy's max function returns only the max values, not the indices, so we don't need to unpack two values\n",
"max_val = np.max(weighted_log, axis=1)\n",
"\n",
"# Equivalent to max_val.squeeze()\n",
"# This step may be unnecessary in NumPy if max_val doesn't have unnecessary dimensions\n",
"vector = np.squeeze(max_val)\n",
"print(\"Sparse Vector shape: \", vector.shape)\n",
"\n",
"# print(vector[0].nonzero())\n",
"\n",
"cols = [vec.nonzero()[0].squeeze().tolist() for vec in vector]\n",
"weights = [vec[col].tolist() for vec, col in zip(vector, cols)]\n",
"\n",
"idx = 1\n",
"cols, weights = cols[idx], weights[idx]\n",
"# Print the BOW representation\n",
"d = {k: v for k, v in zip(cols, weights)}\n",
"sorted_d = {k: v for k, v in sorted(d.items(), key=lambda item: item[1], reverse=True)}\n",
"bow_rep = []\n",
"for k, v in sorted_d.items():\n",
" bow_rep.append((reverse_voc[k], round(v, 2)))\n",
"print(f\"SPLADE BOW rep for sentence:\\t{sentences[idx]}\\n{bow_rep}\")"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"35"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"len(cols)"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[1010,\n",
" 1012,\n",
" 1047,\n",
" 2001,\n",
" 2002,\n",
" 2010,\n",
" 2011,\n",
" 2032,\n",
" 2040,\n",
" 2056,\n",
" 2072,\n",
" 2081,\n",
" 2158,\n",
" 2194,\n",
" 2318,\n",
" 2328,\n",
" 2626,\n",
" 2634,\n",
" 3857,\n",
" 3992,\n",
" 4213,\n",
" 4294,\n",
" 4944,\n",
" 5968,\n",
" 6231,\n",
" 7751,\n",
" 8826,\n",
" 9152,\n",
" 10556,\n",
" 12508,\n",
" 12849,\n",
" 13476,\n",
" 13970,\n",
" 14540,\n",
" 17884]"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"cols"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"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.10.13"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
+15
View File
@@ -0,0 +1,15 @@
from optimum.exporters.onnx import main_export
from transformers import AutoTokenizer
model_id = "sentence-transformers/paraphrase-MiniLM-L6-v2"
output_dir = f"models/{model_id.replace('/', '_')}"
model_kwargs = {"output_attentions": True, "return_dict": True}
tokenizer = AutoTokenizer.from_pretrained(model_id)
# export if the output model does not exist
# try:
# sess = onnxruntime.InferenceSession(f"{output_dir}/model.onnx")
# print("Model already exported")
# except FileNotFoundError:
print(f"Exporting model to {output_dir}")
main_export(model_id, output=output_dir, no_post_process=True, model_kwargs=model_kwargs)
-473
View File
@@ -1,473 +0,0 @@
"""
This script is used to convert HuggingFace models to ONNX format and optionally quantize the model using dynamic quantization.
This is courtesy of Joshua aka @Xenova
"""
import json
import os
import shutil
from dataclasses import dataclass, field
from typing import Optional, Set
import onnx
from onnxruntime.quantization import QuantType, quantize_dynamic
from optimum.exporters.onnx import export_models, main_export
from optimum.exporters.tasks import TasksManager
from tqdm import tqdm
from transformers import AutoConfig, AutoTokenizer, HfArgumentParser
DEFAULT_QUANTIZE_PARAMS = {
"per_channel": True,
"reduce_range": True,
}
MODEL_SPECIFIC_QUANTIZE_PARAMS = {
# Decoder-only models
"codegen": {
"per_channel": False,
"reduce_range": False,
},
"gpt2": {
"per_channel": False,
"reduce_range": False,
},
"gpt_bigcode": {
"per_channel": False,
"reduce_range": False,
},
"gptj": {
"per_channel": False,
"reduce_range": False,
},
"gpt-neo": {
"per_channel": False,
"reduce_range": False,
},
"gpt-neox": {
"per_channel": False,
"reduce_range": False,
},
"mpt": {
"per_channel": False,
"reduce_range": False,
},
"bloom": {
"per_channel": False,
"reduce_range": False,
},
"llama": {
"per_channel": False,
"reduce_range": False,
},
"opt": {
"per_channel": False,
"reduce_range": False,
},
"mistral": {
"per_channel": False,
"reduce_range": False,
},
"falcon": {
"per_channel": False,
"reduce_range": False,
},
"phi": {
"per_channel": False,
"reduce_range": False,
},
"qwen2": {
"per_channel": False,
"reduce_range": False,
},
# Encoder-decoder models
"whisper": {
"per_channel": False,
"reduce_range": False,
},
"vision-encoder-decoder": {
"per_channel": False,
"reduce_range": False,
},
# Encoder-only models
"owlv2": {
"per_channel": False,
"reduce_range": False,
},
}
MODELS_WITHOUT_TOKENIZERS = [
"wav2vec2",
"wav2vec2-bert",
"wavlm",
"hubert",
]
@dataclass
class ConversionArguments:
"""
Arguments used for converting HuggingFace models to onnx.
"""
model_id: str = field(metadata={"help": "Model identifier"})
tokenizer_id: str = field(default=None, metadata={"help": "Tokenizer identifier (if different to `model_id`)"})
quantize: bool = field(default=False, metadata={"help": "Whether to quantize the model."})
output_parent_dir: str = field(
default="./models/", metadata={"help": "Path where the converted model will be saved to."}
)
task: Optional[str] = field(
default="auto",
metadata={
"help": (
"The task to export the model for. If not specified, the task will be auto-inferred based on the model. Available tasks depend on the model, but are among:"
f" {str(TasksManager.get_all_tasks())}. For decoder models, use `xxx-with-past` to export the model using past key values in the decoder."
)
},
)
opset: int = field(
default=None,
metadata={
"help": (
"If specified, ONNX opset version to export the model with. Otherwise, the default opset will be used."
)
},
)
device: str = field(default="cpu", metadata={"help": "The device to use to do the export."})
skip_validation: bool = field(default=False, metadata={"help": "Whether to skip validation of the converted model"})
per_channel: bool = field(default=None, metadata={"help": "Whether to quantize weights per channel"})
reduce_range: bool = field(
default=None,
metadata={
"help": "Whether to quantize weights with 7-bits. It may improve the accuracy for some models running on non-VNNI machine, especially for per-channel mode"
},
)
output_attentions: bool = field(
default=False,
metadata={
"help": "Whether to output attentions from the model. NOTE: This is only supported for whisper models right now."
},
)
split_modalities: bool = field(
default=False,
metadata={
"help": "Whether to split multimodal models. NOTE: This is only supported for CLIP models right now."
},
)
trust_remote_code: bool = field(
default=False,
metadata={
"help": "Allows to use custom code for the modeling hosted in the model repository. This option should only be set for repositories"
"you trust and in which you have read the code, as it will execute on your local machine arbitrary code present in the model repository."
},
)
custom_onnx_configs: str = field(
default=None,
metadata={
"help": "Experimental usage: override the default ONNX config used for the given model. This argument may be useful for advanced users "
"that desire a finer-grained control on the export."
},
)
def get_operators(model: onnx.ModelProto) -> Set[str]:
operators = set()
def traverse_graph(graph):
for node in graph.node:
operators.add(node.op_type)
for attr in node.attribute:
if attr.type == onnx.AttributeProto.GRAPH:
subgraph = attr.g
traverse_graph(subgraph)
traverse_graph(model.graph)
return operators
def quantize(model_names_or_paths, **quantize_kwargs):
"""
Quantize the weights of the model from float32 to int8 to allow very efficient inference on modern CPU
Uses unsigned ints for activation values, signed ints for weights, per
https://onnxruntime.ai/docs/performance/quantization.html#data-type-selection
it is faster on most CPU architectures
Args:
onnx_model_path: Path to location the exported ONNX model is stored
Returns: The Path generated for the quantized
"""
quantize_config = dict(**quantize_kwargs, per_model_config={})
for model in tqdm(model_names_or_paths, desc="Quantizing"):
directory_path = os.path.dirname(model)
file_name_without_extension = os.path.splitext(os.path.basename(model))[0]
# NOTE:
# As of 2023/04/20, the current latest version of onnxruntime-web is 1.14.0, and does not support INT8 weights for Conv layers.
# For this reason, we choose model weight types to ensure compatibility with onnxruntime-web.
#
# As per docs, signed weight type (QInt8) is faster on most CPUs, so, we use that unless the model contains a Conv layer.
# For more information, see:
# - https://github.com/microsoft/onnxruntime/issues/3130#issuecomment-1105200621
# - https://github.com/microsoft/onnxruntime/issues/2339
loaded_model = onnx.load_model(model)
op_types = get_operators(loaded_model)
weight_type = QuantType.QUInt8 if "Conv" in op_types else QuantType.QInt8
quantize_dynamic(
model_input=model,
model_output=os.path.join(directory_path, f"{file_name_without_extension}_quantized.onnx"),
weight_type=weight_type,
# TODO allow user to specify these
# op_types_to_quantize=['MatMul', 'Add', 'Conv'],
extra_options=dict(EnableSubgraph=True),
**quantize_kwargs,
)
quantize_config["per_model_config"][file_name_without_extension] = dict(
op_types=list(op_types),
weight_type=str(weight_type),
)
# Save quantization config
with open(os.path.join(directory_path, "quantize_config.json"), "w") as fp:
json.dump(quantize_config, fp, indent=4)
def main():
parser = HfArgumentParser((ConversionArguments,))
(conv_args,) = parser.parse_args_into_dataclasses()
model_id = conv_args.model_id
tokenizer_id = conv_args.tokenizer_id or model_id
output_model_folder = os.path.join(conv_args.output_parent_dir, model_id)
# Create output folder
os.makedirs(output_model_folder, exist_ok=True)
from_pretrained_kwargs = dict(
trust_remote_code=conv_args.trust_remote_code,
)
# Saving the model config
config = AutoConfig.from_pretrained(model_id, **from_pretrained_kwargs)
custom_kwargs = {}
if conv_args.custom_onnx_configs is not None:
if conv_args.task == "auto":
raise Exception("`--task` must be set when exporting with `--custom_onnx_configs`")
custom_onnx_configs = json.loads(conv_args.custom_onnx_configs)
for key in custom_onnx_configs:
onnx_configs = TasksManager._SUPPORTED_MODEL_TYPE[custom_onnx_configs[key]]["onnx"]
mapping = onnx_configs[conv_args.task]
custom_onnx_configs[key] = mapping.func(config, **mapping.keywords)
custom_kwargs["custom_onnx_configs"] = custom_onnx_configs
tokenizer = None
try:
# Load tokenizer
tokenizer = AutoTokenizer.from_pretrained(tokenizer_id, **from_pretrained_kwargs)
# To avoid inserting all chat templates into tokenizers.js, we save the chat template
# to the tokenizer_config.json file, and load it when the tokenizer is loaded.
if getattr(tokenizer, "chat_template", None) is None and getattr(tokenizer, "use_default_system_prompt", False):
# No chat template specified, and we use the default
setattr(tokenizer, "chat_template", tokenizer.default_chat_template)
except KeyError:
pass # No Tokenizer
except Exception as e:
if config.model_type not in MODELS_WITHOUT_TOKENIZERS:
raise e
core_export_kwargs = dict(
opset=conv_args.opset,
device=conv_args.device,
trust_remote_code=conv_args.trust_remote_code,
**custom_kwargs,
)
export_kwargs = dict(
model_name_or_path=model_id,
output=output_model_folder,
task=conv_args.task,
do_validation=not conv_args.skip_validation,
library_name="transformers",
**core_export_kwargs,
)
# Handle special cases
if config.model_type == "marian":
from .extra.marian import generate_tokenizer_json
tokenizer_json = generate_tokenizer_json(model_id, tokenizer)
with open(os.path.join(output_model_folder, "tokenizer.json"), "w", encoding="utf-8") as fp:
json.dump(tokenizer_json, fp, indent=4)
elif config.model_type == "esm":
from .extra.esm import generate_fast_tokenizer
fast_tokenizer = generate_fast_tokenizer(tokenizer)
fast_tokenizer.save(os.path.join(output_model_folder, "tokenizer.json"))
elif config.model_type == "whisper":
if conv_args.output_attentions:
from .extra.whisper import get_main_export_kwargs
export_kwargs.update(**get_main_export_kwargs(config, "automatic-speech-recognition"))
elif config.model_type in ("wav2vec2", "wav2vec2-bert", "hubert"):
if tokenizer is not None:
from .extra.wav2vec2 import generate_tokenizer_json
tokenizer_json = generate_tokenizer_json(tokenizer)
with open(os.path.join(output_model_folder, "tokenizer.json"), "w", encoding="utf-8") as fp:
json.dump(tokenizer_json, fp, indent=4)
elif config.model_type == "vits":
if tokenizer is not None:
from .extra.vits import generate_tokenizer_json
tokenizer_json = generate_tokenizer_json(tokenizer)
with open(os.path.join(output_model_folder, "tokenizer.json"), "w", encoding="utf-8") as fp:
json.dump(tokenizer_json, fp, indent=4)
elif config.model_type == "speecht5":
# TODO allow user to specify vocoder path
export_kwargs["model_kwargs"] = {"vocoder": "microsoft/speecht5_hifigan"}
if tokenizer is not None:
from .extra.speecht5 import generate_tokenizer_json
tokenizer_json = generate_tokenizer_json(tokenizer)
with open(os.path.join(output_model_folder, "tokenizer.json"), "w", encoding="utf-8") as fp:
json.dump(tokenizer_json, fp, indent=4)
elif config.model_type in ("owlvit", "owlv2"):
# Override default batch size to 1, needed because non-maximum suppression is performed for exporting.
# For more information, see https://github.com/huggingface/optimum/blob/e3b7efb1257c011db907ef40ab340e795cc5684c/optimum/exporters/onnx/model_configs.py#L1028-L1032
export_kwargs["batch_size"] = 1
else:
pass # TODO
# Step 1. convert huggingface model to onnx
if not conv_args.split_modalities:
main_export(**export_kwargs)
else:
custom_export_kwargs = dict(
output_dir=output_model_folder,
**core_export_kwargs,
)
if config.model_type == "clip":
# Handle special case for exporting text and vision models separately
from transformers.models.clip import CLIPTextModelWithProjection, CLIPVisionModelWithProjection
from .extra.clip import CLIPTextModelWithProjectionOnnxConfig, CLIPVisionModelWithProjectionOnnxConfig
text_model = CLIPTextModelWithProjection.from_pretrained(model_id, **from_pretrained_kwargs)
vision_model = CLIPVisionModelWithProjection.from_pretrained(model_id, **from_pretrained_kwargs)
export_models(
models_and_onnx_configs={
"text_model": (text_model, CLIPTextModelWithProjectionOnnxConfig(text_model.config)),
"vision_model": (vision_model, CLIPVisionModelWithProjectionOnnxConfig(vision_model.config)),
},
**custom_export_kwargs,
)
elif config.model_type == "siglip":
# Handle special case for exporting text and vision models separately
from transformers.models.siglip import SiglipTextModel, SiglipVisionModel
from .extra.siglip import SiglipTextModelOnnxConfig, SiglipVisionModelOnnxConfig
text_model = SiglipTextModel.from_pretrained(model_id, **from_pretrained_kwargs)
vision_model = SiglipVisionModel.from_pretrained(model_id, **from_pretrained_kwargs)
export_models(
models_and_onnx_configs={
"text_model": (text_model, SiglipTextModelOnnxConfig(text_model.config)),
"vision_model": (vision_model, SiglipVisionModelOnnxConfig(vision_model.config)),
},
**custom_export_kwargs,
)
# TODO: Enable once https://github.com/huggingface/optimum/pull/1552 is merged
# elif config.model_type == 'clap':
# # Handle special case for exporting text and audio models separately
# from .extra.clap import ClapTextModelWithProjectionOnnxConfig, ClapAudioModelWithProjectionOnnxConfig
# from transformers.models.clap import ClapTextModelWithProjection, ClapAudioModelWithProjection
# text_model = ClapTextModelWithProjection.from_pretrained(model_id, **from_pretrained_kwargs)
# audio_model = ClapAudioModelWithProjection.from_pretrained(model_id, **from_pretrained_kwargs)
# export_models(
# models_and_onnx_configs={
# "text_model": (text_model, ClapTextModelWithProjectionOnnxConfig(text_model.config)),
# "audio_model": (audio_model, ClapAudioModelWithProjectionOnnxConfig(audio_model.config)),
# },
# **custom_export_kwargs,
# )
else:
raise Exception(f"Unable to export {config.model_type} model with `--split_modalities`.")
# Step 2. (optional, recommended) quantize the converted model for fast inference and to reduce model size.
if conv_args.quantize:
# Update quantize config with model specific defaults
quantize_config = MODEL_SPECIFIC_QUANTIZE_PARAMS.get(config.model_type, DEFAULT_QUANTIZE_PARAMS)
# Update if user specified values
if conv_args.per_channel is not None:
quantize_config["per_channel"] = conv_args.per_channel
if conv_args.reduce_range is not None:
quantize_config["reduce_range"] = conv_args.reduce_range
quantize(
[
os.path.join(output_model_folder, x)
for x in os.listdir(output_model_folder)
if x.endswith(".onnx") and not x.endswith("_quantized.onnx")
],
**quantize_config,
)
# Step 3. Move .onnx files to the 'onnx' subfolder
os.makedirs(os.path.join(output_model_folder, "onnx"), exist_ok=True)
for file in os.listdir(output_model_folder):
if file.endswith((".onnx", ".onnx_data")):
shutil.move(os.path.join(output_model_folder, file), os.path.join(output_model_folder, "onnx", file))
# Step 4. Update the generation config if necessary
if config.model_type == "whisper":
from transformers import GenerationConfig
from .extra.whisper import get_alignment_heads
generation_config = GenerationConfig.from_pretrained(model_id, **from_pretrained_kwargs)
generation_config.alignment_heads = get_alignment_heads(config)
generation_config.save_pretrained(output_model_folder)
if __name__ == "__main__":
main()
+29
View File
@@ -0,0 +1,29 @@
import numpy as np
import onnx
import onnxruntime
from transformers import AutoTokenizer
model_id = "sentence-transformers/paraphrase-MiniLM-L6-v2"
output_dir = f"models/{model_id.replace('/', '_')}"
model_kwargs = {"output_attentions": True, "return_dict": True}
tokenizer = AutoTokenizer.from_pretrained(model_id)
model_path = f"{output_dir}/model.onnx"
onnx_model = onnx.load(model_path)
ort_session = onnxruntime.InferenceSession(model_path)
text = "This is a test sentence"
tokenizer_output = tokenizer(text, return_tensors="np")
input_ids = tokenizer_output["input_ids"]
attention_mask = tokenizer_output["attention_mask"]
print(attention_mask)
# Prepare the input
input_ids = np.array(input_ids).astype(np.int64) # Replace your_input_ids with actual input data
# Run the ONNX model
outputs = ort_session.run(None, {"input_ids": input_ids, "attention_mask": attention_mask})
# Get the attention weights
attentions = outputs[-1]
# Print the attention weights for the first layer and first head
print(attentions[0][0])
-56
View File
@@ -1,56 +0,0 @@
from pathlib import Path
from typing import List
import click
import numpy as np
import torch
import torch.nn.functional as F
from optimum.onnxruntime import ORTModelForFeatureExtraction
from optimum.pipelines import pipeline
from torch import Tensor
from transformers import AutoModel, AutoTokenizer
def average_pool(last_hidden_states: Tensor, attention_mask: Tensor) -> Tensor:
last_hidden = last_hidden_states.masked_fill(~attention_mask[..., None].bool(), 0.0)
return last_hidden.sum(dim=1) / attention_mask.sum(dim=1)[..., None]
def hf_embed(model_id: str, texts: List[str], tokenizer):
# Tokenize the input texts
model = AutoModel.from_pretrained(model_id)
model.eval()
encoded_input = tokenizer(texts, padding=True, truncation=True, return_tensors="pt")
model_output = model(**encoded_input)
sentence_embeddings = model_output[0][:, 0]
sentence_embeddings = torch.nn.functional.normalize(sentence_embeddings, p=2, dim=1)
return sentence_embeddings
@click.command()
@click.option("--model_id", help="model id from huggingface.co/models")
@click.option("--model_dir", help="The person to greet.")
def setup(model_id, model_dir):
text = "This is a test sentence"
tokenizer = AutoTokenizer.from_pretrained(model_id)
output_dir = Path(model_dir)
model = ORTModelForFeatureExtraction.from_pretrained(output_dir)
onnx_quant_embed = pipeline(
"feature-extraction", model=model, accelerator="ort", tokenizer=tokenizer, return_tensors=True
)
quant_embeddings = onnx_quant_embed([text])
quant_embeddings = F.normalize(quant_embeddings[0][:,0], p=2, dim=1)
quant_embeddings = quant_embeddings.detach().numpy()
print(quant_embeddings.shape)
torch_embeddings = hf_embed(model_id, texts=[text], tokenizer=tokenizer)
torch_embeddings = F.normalize(torch_embeddings, p=2, dim=1)
torch_embeddings = torch_embeddings.detach().numpy()
print(torch_embeddings.shape)
assert quant_embeddings.shape == torch_embeddings.shape
print(np.allclose(quant_embeddings, torch_embeddings, atol=1e-5))
if __name__ == "__main__":
setup()
+3
View File
@@ -1,3 +1,6 @@
import importlib.metadata
from fastembed.text.text_embedding import TextEmbedding
__version__ = importlib.metadata.version("fastembed")
__all__ = ["TextEmbedding"]
+29
View File
@@ -29,6 +29,35 @@ def locate_model_file(model_dir: Path, file_names: List[str]) -> Path:
class ModelManagement:
@classmethod
def list_supported_models(cls) -> List[Dict[str, Any]]:
"""Lists the supported models.
Returns:
List[Dict[str, Any]]: A list of dictionaries containing the model information.
"""
raise NotImplementedError()
@classmethod
def _get_model_description(cls, model_name: str) -> Dict[str, Any]:
"""
Gets the model description from the model_name.
Args:
model_name (str): The name of the model.
raises:
ValueError: If the model_name is not supported.
Returns:
Dict[str, Any]: The model description.
"""
for model in cls.list_supported_models():
if model_name == model["model"]:
return model
raise ValueError(f"Model {model_name} is not supported in {cls.__name__}.")
@classmethod
def download_file_from_gcs(cls, url: str, output_path: str, show_progress: bool = True) -> str:
"""
+130
View File
@@ -0,0 +1,130 @@
import os
from multiprocessing import get_all_start_methods
from pathlib import Path
from typing import Any, Dict, Generic, Iterable, List, Optional, Tuple, Type, TypeVar, Union
import numpy as np
import onnxruntime as ort
from fastembed.common.model_management import locate_model_file
from fastembed.common.models import load_tokenizer
from fastembed.common.utils import iter_batch
from fastembed.parallel_processor import ParallelWorkerPool, Worker
# Holds type of the embedding result
T = TypeVar("T")
class OnnxModel(Generic[T]):
@classmethod
def _get_worker_class(cls) -> Type["EmbeddingWorker"]:
raise NotImplementedError("Subclasses must implement this method")
@classmethod
def _post_process_onnx_output(cls, output: Tuple[np.ndarray, np.ndarray]) -> Iterable[T]:
raise NotImplementedError("Subclasses must implement this method")
def __init__(self) -> None:
self.model = None
self.tokenizer = None
def _preprocess_onnx_input(self, onnx_input: Dict[str, np.ndarray]) -> Dict[str, np.ndarray]:
"""
Preprocess the onnx input.
"""
return onnx_input
def load_onnx_model(self, model_dir: Path, threads: Optional[int], max_length: int) -> None:
model_path = locate_model_file(model_dir, ["model.onnx", "model_optimized.onnx"])
# List of Execution Providers: https://onnxruntime.ai/docs/execution-providers
onnx_providers = ["CPUExecutionProvider"]
so = ort.SessionOptions()
so.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
if threads is not None:
so.intra_op_num_threads = threads
so.inter_op_num_threads = threads
self.tokenizer = load_tokenizer(model_dir=model_dir, max_length=max_length)
self.model = ort.InferenceSession(str(model_path), providers=onnx_providers, sess_options=so)
def onnx_embed(self, documents: List[str]) -> Tuple[np.ndarray, np.ndarray]:
encoded = self.tokenizer.encode_batch(documents)
input_ids = np.array([e.ids for e in encoded])
attention_mask = np.array([e.attention_mask for e in encoded])
onnx_input = {
"input_ids": np.array(input_ids, dtype=np.int64),
"attention_mask": np.array(attention_mask, dtype=np.int64),
"token_type_ids": np.array([np.zeros(len(e), dtype=np.int64) for e in input_ids], dtype=np.int64),
}
onnx_input = self._preprocess_onnx_input(onnx_input)
model_output = self.model.run(None, onnx_input)
embeddings = model_output[0]
return embeddings, attention_mask
def _embed_documents(
self,
model_name: str,
cache_dir: str,
documents: Union[str, Iterable[str]],
batch_size: int = 256,
parallel: Optional[int] = None,
) -> Iterable[T]:
is_small = False
if isinstance(documents, str):
documents = [documents]
is_small = True
if isinstance(documents, list):
if len(documents) < batch_size:
is_small = True
if parallel == 0:
parallel = os.cpu_count()
if parallel is None or is_small:
for batch in iter_batch(documents, batch_size):
yield from self._post_process_onnx_output(self.onnx_embed(batch))
else:
start_method = "forkserver" if "forkserver" in get_all_start_methods() else "spawn"
params = {
"model_name": model_name,
"cache_dir": cache_dir,
}
pool = ParallelWorkerPool(parallel, self._get_worker_class(), start_method=start_method)
for batch in pool.ordered_map(iter_batch(documents, batch_size), **params):
yield from self._post_process_onnx_output(batch)
class EmbeddingWorker(Worker):
def init_embedding(
self,
model_name: str,
cache_dir: str,
) -> OnnxModel:
raise NotImplementedError()
def __init__(
self,
model_name: str,
cache_dir: str,
):
self.model = self.init_embedding(model_name, cache_dir)
@classmethod
def start(cls, model_name: str, cache_dir: str, **kwargs: Any) -> "EmbeddingWorker":
return cls(
model_name=model_name,
cache_dir=cache_dir,
)
def process(self, items: Iterable[Tuple[int, Any]]) -> Iterable[Tuple[int, Any]]:
for idx, batch in items:
embeddings, attn_mask = self.model.onnx_embed(batch)
yield idx, (embeddings, attn_mask)
+3 -1
View File
@@ -4,7 +4,9 @@ from loguru import logger
from fastembed.text.text_embedding import TextEmbedding
logger.warning("DefaultEmbedding, FlagEmbedding, JinaEmbedding are deprecated." "Use from fastembed import TextEmbedding instead.")
logger.warning(
"DefaultEmbedding, FlagEmbedding, JinaEmbedding are deprecated." "Use from fastembed import TextEmbedding instead."
)
DefaultEmbedding = TextEmbedding
FlagEmbedding = TextEmbedding
+37
View File
@@ -0,0 +1,37 @@
from dataclasses import dataclass
from typing import Dict, Iterable, Optional, Union
import numpy as np
from fastembed.common.model_management import ModelManagement
@dataclass
class SparseEmbedding:
values: np.ndarray
indices: np.ndarray
def as_object(self) -> Dict[str, np.ndarray]:
return {
"values": self.values,
"indices": self.indices,
}
def as_dict(self) -> Dict[int, float]:
return {i: v for i, v in zip(self.indices, self.values)}
class SparseTextEmbeddingBase(ModelManagement):
def __init__(self, model_name: str, cache_dir: Optional[str] = None, threads: Optional[int] = None, **kwargs):
self.model_name = model_name
self.cache_dir = cache_dir
self.threads = threads
def embed(
self,
documents: Union[str, Iterable[str]],
batch_size: int = 256,
parallel: Optional[int] = None,
**kwargs,
) -> Iterable[SparseEmbedding]:
raise NotImplementedError()
+82
View File
@@ -0,0 +1,82 @@
from typing import List, Type, Dict, Any, Union, Iterable, Optional
from fastembed.sparse.sparse_embedding_base import SparseTextEmbeddingBase, SparseEmbedding
from fastembed.sparse.splade_pp import SpladePP
class SparseTextEmbedding(SparseTextEmbeddingBase):
EMBEDDINGS_REGISTRY: List[Type[SparseTextEmbeddingBase]] = [
SpladePP,
]
@classmethod
def list_supported_models(cls) -> List[Dict[str, Any]]:
"""
Lists the supported models.
Returns:
List[Dict[str, Any]]: A list of dictionaries containing the model information.
Example:
```
[
{
"model": "prithvida/SPLADE_PP_en_v1",
"vocab_size": 30522,
"description": "Independent Implementation of SPLADE++ Model for English",
"size_in_GB": 0.532,
"sources": {
"hf": "qdrant/SPLADE_PP_en_v1",
},
}
]
```
"""
result = []
for embedding in cls.EMBEDDINGS_REGISTRY:
result.extend(embedding.list_supported_models())
return result
def __init__(
self,
model_name: str,
cache_dir: Optional[str] = None,
threads: Optional[int] = None,
**kwargs,
):
super().__init__(model_name, cache_dir, threads, **kwargs)
for EMBEDDING_MODEL_TYPE in self.EMBEDDINGS_REGISTRY:
supported_models = EMBEDDING_MODEL_TYPE.list_supported_models()
if any(model_name.lower() == model["model"].lower() for model in supported_models):
self.model = EMBEDDING_MODEL_TYPE(model_name, cache_dir, threads, **kwargs)
return
raise ValueError(
f"Model {model_name} is not supported in SparseTextEmbedding."
"Please check the supported models using `SparseTextEmbedding.list_supported_models()`"
)
def embed(
self,
documents: Union[str, Iterable[str]],
batch_size: int = 256,
parallel: Optional[int] = None,
**kwargs,
) -> Iterable[SparseEmbedding]:
"""
Encode a list of documents into list of embeddings.
We use mean pooling with attention so that the model can handle variable-length inputs.
Args:
documents: Iterator of documents or single document to embed
batch_size: Batch size for encoding -- higher values will use more memory, but be faster
parallel:
If > 1, data-parallel encoding will be used, recommended for offline encoding of large datasets.
If 0, use all available cores.
If None, don't use data-parallel processing, use default onnxruntime threading instead.
Returns:
List of embeddings, one per document
"""
yield from self.model.embed(documents, batch_size, parallel, **kwargs)
+115
View File
@@ -0,0 +1,115 @@
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
import numpy as np
from fastembed.common.onnx_model import EmbeddingWorker, OnnxModel
from fastembed.common.utils import define_cache_dir
from fastembed.sparse.sparse_embedding_base import SparseEmbedding, SparseTextEmbeddingBase
supported_splade_models = [
{
"model": "prithvida/Splade_PP_en_v1",
"vocab_size": 30522,
"description": "Independent Implementation of SPLADE++ Model for English",
"size_in_GB": 0.532,
"sources": {
"hf": "Qdrant/SPLADE_PP_en_v1",
},
},
]
class SpladePP(SparseTextEmbeddingBase, OnnxModel[SparseEmbedding]):
@classmethod
def _post_process_onnx_output(cls, output: Tuple[np.ndarray, np.ndarray]) -> Iterable[SparseEmbedding]:
logits, attention_mask = output
relu_log = np.log(1 + np.maximum(logits, 0))
weighted_log = relu_log * np.expand_dims(attention_mask, axis=-1)
scores = np.max(weighted_log, axis=1)
# Score matrix of shape (batch_size, vocab_size)
# Most of the values are 0, only a few are non-zero
for row_scores in scores:
indices = row_scores.nonzero()[0]
scores = row_scores[indices]
yield SparseEmbedding(values=scores, indices=indices)
@classmethod
def list_supported_models(cls) -> List[Dict[str, Any]]:
"""Lists the supported models.
Returns:
List[Dict[str, Any]]: A list of dictionaries containing the model information.
"""
return supported_splade_models
def __init__(
self,
model_name: str,
cache_dir: Optional[str] = None,
threads: Optional[int] = None,
**kwargs,
):
"""
Args:
model_name (str): The name of the model to use.
cache_dir (str, optional): The path to the cache directory.
Can be set using the `FASTEMBED_CACHE_PATH` env variable.
Defaults to `fastembed_cache` in the system's temp directory.
threads (int, optional): The number of threads single onnxruntime session can use. Defaults to None.
Raises:
ValueError: If the model_name is not in the format <org>/<model> e.g. BAAI/bge-base-en.
"""
super().__init__(model_name, cache_dir, threads, **kwargs)
self.model_name = model_name
self._model_description = self._get_model_description(model_name)
self._cache_dir = define_cache_dir(cache_dir)
self._model_dir = self.download_model(self._model_description, self._cache_dir)
self._max_length = 512
self.load_onnx_model(self._model_dir, self.threads, self._max_length)
def embed(
self,
documents: Union[str, Iterable[str]],
batch_size: int = 256,
parallel: Optional[int] = None,
**kwargs,
) -> Iterable[SparseEmbedding]:
"""
Encode a list of documents into list of embeddings.
We use mean pooling with attention so that the model can handle variable-length inputs.
Args:
documents: Iterator of documents or single document to embed
batch_size: Batch size for encoding -- higher values will use more memory, but be faster
parallel:
If > 1, data-parallel encoding will be used, recommended for offline encoding of large datasets.
If 0, use all available cores.
If None, don't use data-parallel processing, use default onnxruntime threading instead.
Returns:
List of embeddings, one per document
"""
yield from self._embed_documents(
model_name=self.model_name,
cache_dir=str(self._cache_dir),
documents=documents,
batch_size=batch_size,
parallel=parallel,
)
class SpladePPEmbeddingWorker(EmbeddingWorker):
def init_embedding(
self,
model_name: str,
cache_dir: str,
) -> SpladePP:
return SpladePP(model_name=model_name, cache_dir=cache_dir, threads=1)
+2 -1
View File
@@ -2,7 +2,8 @@ from typing import Type, List, Dict, Any
import numpy as np
from fastembed.text.onnx_embedding import OnnxTextEmbedding, OnnxTextEmbeddingWorker, EmbeddingWorker
from fastembed.common.onnx_model import EmbeddingWorker
from fastembed.text.onnx_embedding import OnnxTextEmbedding, OnnxTextEmbeddingWorker
supported_multilingual_e5_models = [
{
+4 -3
View File
@@ -1,9 +1,10 @@
from typing import Type, List, Dict, Any, Tuple
from typing import Type, List, Dict, Any, Tuple, Iterable
import numpy as np
from fastembed.common.models import normalize
from fastembed.text.onnx_embedding import OnnxTextEmbedding, EmbeddingWorker, OnnxTextEmbeddingWorker
from fastembed.common.onnx_model import EmbeddingWorker
from fastembed.text.onnx_embedding import OnnxTextEmbedding, OnnxTextEmbeddingWorker
supported_jina_models = [
{
@@ -48,7 +49,7 @@ class JinaOnnxEmbedding(OnnxTextEmbedding):
return supported_jina_models
@classmethod
def _post_process_onnx_output(cls, output: Tuple[np.ndarray, np.ndarray]) -> np.ndarray:
def _post_process_onnx_output(cls, output: Tuple[np.ndarray, np.ndarray]) -> Iterable[np.ndarray]:
embeddings, attn_mask = output
return normalize(cls.mean_pooling(embeddings, attn_mask)).astype(np.float32)
+16 -115
View File
@@ -1,14 +1,10 @@
import os
from multiprocessing import get_all_start_methods
from typing import List, Dict, Any, Optional, Tuple, Union, Iterable, Type
from typing import Dict, Optional, Tuple, Union, Iterable, Type, List, Any
import numpy as np
import onnxruntime as ort
from fastembed.common.model_management import locate_model_file
from fastembed.common.models import load_tokenizer, normalize
from fastembed.common.utils import define_cache_dir, iter_batch
from fastembed.parallel_processor import ParallelWorkerPool, Worker
from fastembed.common.onnx_model import OnnxModel, EmbeddingWorker
from fastembed.common.models import normalize
from fastembed.common.utils import define_cache_dir
from fastembed.text.text_embedding_base import TextEmbeddingBase
supported_onnx_models = [
@@ -150,38 +146,19 @@ supported_onnx_models = [
]
class OnnxTextEmbedding(TextEmbeddingBase):
class OnnxTextEmbedding(TextEmbeddingBase, OnnxModel[np.ndarray]):
"""Implementation of the Flag Embedding model."""
@classmethod
def list_supported_models(cls) -> List[Dict[str, Any]]:
"""Lists the supported models.
"""
Lists the supported models.
Returns:
List[Dict[str, Any]]: A list of dictionaries containing the model information.
"""
return supported_onnx_models
@classmethod
def _get_model_description(cls, model_name: str) -> Dict[str, Any]:
"""
Gets the model description from the model_name.
Args:
model_name (str): The name of the model.
raises:
ValueError: If the model_name is not supported.
Returns:
Dict[str, Any]: The model description.
"""
for model in cls.list_supported_models():
if model_name == model["model"]:
return model
raise ValueError(f"Model {model_name} is not supported in FlagEmbedding.")
def __init__(
self,
model_name: str = "BAAI/bge-small-en-v1.5",
@@ -210,20 +187,7 @@ class OnnxTextEmbedding(TextEmbeddingBase):
self._model_dir = self.download_model(self._model_description, self._cache_dir)
self._max_length = 512
model_path = locate_model_file(self._model_dir, ["model.onnx", "model_optimized.onnx"])
# List of Execution Providers: https://onnxruntime.ai/docs/execution-providers
onnx_providers = ["CPUExecutionProvider"]
so = ort.SessionOptions()
so.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
if self.threads is not None:
so.intra_op_num_threads = self.threads
so.inter_op_num_threads = self.threads
self.tokenizer = load_tokenizer(model_dir=self._model_dir, max_length=self._max_length)
self.model = ort.InferenceSession(str(model_path), providers=onnx_providers, sess_options=so)
self.load_onnx_model(self._model_dir, self.threads, self._max_length)
def embed(
self,
@@ -247,31 +211,13 @@ class OnnxTextEmbedding(TextEmbeddingBase):
Returns:
List of embeddings, one per document
"""
is_small = False
if isinstance(documents, str):
documents = [documents]
is_small = True
if isinstance(documents, list):
if len(documents) < batch_size:
is_small = True
if parallel == 0:
parallel = os.cpu_count()
if parallel is None or is_small:
for batch in iter_batch(documents, batch_size):
yield from self._post_process_onnx_output(self.onnx_embed(batch))
else:
start_method = "forkserver" if "forkserver" in get_all_start_methods() else "spawn"
params = {
"model_name": self.model_name,
"cache_dir": str(self._cache_dir),
}
pool = ParallelWorkerPool(parallel, self._get_worker_class(), start_method=start_method)
for batch in pool.ordered_map(iter_batch(documents, batch_size), **params):
yield from self._post_process_onnx_output(batch)
yield from self._embed_documents(
model_name=self.model_name,
cache_dir=str(self._cache_dir),
documents=documents,
batch_size=batch_size,
parallel=parallel,
)
@classmethod
def _get_worker_class(cls) -> Type["EmbeddingWorker"]:
@@ -284,55 +230,10 @@ class OnnxTextEmbedding(TextEmbeddingBase):
return onnx_input
@classmethod
def _post_process_onnx_output(cls, output: Tuple[np.ndarray, np.ndarray]):
def _post_process_onnx_output(cls, output: Tuple[np.ndarray, np.ndarray]) -> Iterable[np.ndarray]:
embeddings, _ = output
return normalize(embeddings[:, 0]).astype(np.float32)
def onnx_embed(self, documents: List[str]) -> Tuple[np.ndarray, np.ndarray]:
encoded = self.tokenizer.encode_batch(documents)
input_ids = np.array([e.ids for e in encoded])
attention_mask = np.array([e.attention_mask for e in encoded])
onnx_input = {
"input_ids": np.array(input_ids, dtype=np.int64),
"attention_mask": np.array(attention_mask, dtype=np.int64),
"token_type_ids": np.array([np.zeros(len(e), dtype=np.int64) for e in input_ids], dtype=np.int64),
}
onnx_input = self._preprocess_onnx_input(onnx_input)
model_output = self.model.run(None, onnx_input)
embeddings = model_output[0]
return embeddings, attention_mask
class EmbeddingWorker(Worker):
def init_embedding(
self,
model_name: str,
cache_dir: str,
) -> OnnxTextEmbedding:
raise NotImplementedError()
def __init__(
self,
model_name: str,
cache_dir: str,
):
self.model = self.init_embedding(model_name, cache_dir)
@classmethod
def start(cls, model_name: str, cache_dir: str, **kwargs: Any) -> "EmbeddingWorker":
return cls(
model_name=model_name,
cache_dir=cache_dir,
)
def process(self, items: Iterable[Tuple[int, Any]]) -> Iterable[Tuple[int, Any]]:
for idx, batch in items:
embeddings, attn_mask = self.model.onnx_embed(batch)
yield idx, (embeddings, attn_mask)
class OnnxTextEmbeddingWorker(EmbeddingWorker):
def init_embedding(
+5 -5
View File
@@ -1,4 +1,4 @@
from typing import Optional, Union, Iterable, List, Dict, Any, Type
from typing import Any, Dict, Iterable, List, Optional, Type, Union
import numpy as np
@@ -53,10 +53,10 @@ class TextEmbedding(TextEmbeddingBase):
):
super().__init__(model_name, cache_dir, threads, **kwargs)
for embedding in self.EMBEDDINGS_REGISTRY:
supported_models = embedding.list_supported_models()
if any(model_name == model["model"] for model in supported_models):
self.model = embedding(model_name, cache_dir, threads, **kwargs)
for EMBEDDING_MODEL_TYPE in self.EMBEDDINGS_REGISTRY:
supported_models = EMBEDDING_MODEL_TYPE.list_supported_models()
if any(model_name.lower() == model["model"].lower() for model in supported_models):
self.model = EMBEDDING_MODEL_TYPE(model_name, cache_dir, threads, **kwargs)
return
raise ValueError(
+1 -5
View File
@@ -1,4 +1,4 @@
from typing import Any, Dict, Iterable, List, Optional, Union
from typing import Iterable, Optional, Union
import numpy as np
@@ -6,10 +6,6 @@ from fastembed.common.model_management import ModelManagement
class TextEmbeddingBase(ModelManagement):
@classmethod
def list_supported_models(cls) -> List[Dict[str, Any]]:
raise NotImplementedError()
def __init__(self, model_name: str, cache_dir: Optional[str] = None, threads: Optional[int] = None, **kwargs):
self.model_name = model_name
self.cache_dir = cache_dir
Generated
+165 -153
View File
@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
[[package]]
name = "anyio"
@@ -497,13 +497,13 @@ cron = ["capturer (>=2.4)"]
[[package]]
name = "comm"
version = "0.2.1"
version = "0.2.2"
description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc."
optional = false
python-versions = ">=3.8"
files = [
{file = "comm-0.2.1-py3-none-any.whl", hash = "sha256:87928485c0dfc0e7976fd89fc1e187023cf587e7c353e4a9b417555b44adf021"},
{file = "comm-0.2.1.tar.gz", hash = "sha256:0bc91edae1344d39d3661dcbc36937181fdaddb304790458f8b044dbc064b89a"},
{file = "comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3"},
{file = "comm-0.2.2.tar.gz", hash = "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e"},
]
[package.dependencies]
@@ -655,13 +655,13 @@ typing = ["typing-extensions (>=4.8)"]
[[package]]
name = "flatbuffers"
version = "23.5.26"
version = "24.3.7"
description = "The FlatBuffers serialization format for Python"
optional = false
python-versions = "*"
files = [
{file = "flatbuffers-23.5.26-py2.py3-none-any.whl", hash = "sha256:c0ff356da363087b915fde4b8b45bdda73432fc17cddb3c8157472eab1422ad1"},
{file = "flatbuffers-23.5.26.tar.gz", hash = "sha256:9ea1144cac05ce5d86e2859f431c6cd5e66cd9c78c558317c7955fb8d4c78d89"},
{file = "flatbuffers-24.3.7-py2.py3-none-any.whl", hash = "sha256:80c4f5dcad0ee76b7e349671a0d657f2fbba927a0244f88dd3f5ed6a3694e1fc"},
{file = "flatbuffers-24.3.7.tar.gz", hash = "sha256:0895c22b9a6019ff2f4de2e5e2f7cd15914043e6e7033a94c0c6369422690f22"},
]
[[package]]
@@ -771,13 +771,13 @@ files = [
[[package]]
name = "httpcore"
version = "1.0.3"
version = "1.0.4"
description = "A minimal low-level HTTP client."
optional = false
python-versions = ">=3.8"
files = [
{file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"},
{file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"},
{file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"},
{file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"},
]
[package.dependencies]
@@ -788,17 +788,17 @@ h11 = ">=0.13,<0.15"
asyncio = ["anyio (>=4.0,<5.0)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
trio = ["trio (>=0.22.0,<0.24.0)"]
trio = ["trio (>=0.22.0,<0.25.0)"]
[[package]]
name = "httpx"
version = "0.26.0"
version = "0.27.0"
description = "The next generation HTTP client."
optional = false
python-versions = ">=3.8"
files = [
{file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"},
{file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"},
{file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"},
{file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"},
]
[package.dependencies]
@@ -887,32 +887,32 @@ files = [
[[package]]
name = "importlib-metadata"
version = "7.0.1"
version = "7.0.2"
description = "Read metadata from Python packages"
optional = false
python-versions = ">=3.8"
files = [
{file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"},
{file = "importlib_metadata-7.0.1.tar.gz", hash = "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc"},
{file = "importlib_metadata-7.0.2-py3-none-any.whl", hash = "sha256:f4bc4c0c070c490abf4ce96d715f68e95923320370efb66143df00199bb6c100"},
{file = "importlib_metadata-7.0.2.tar.gz", hash = "sha256:198f568f3230878cb1b44fbd7975f87906c22336dba2e4a7f05278c281fbd792"},
]
[package.dependencies]
zipp = ">=0.5"
[package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
perf = ["ipython"]
testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"]
testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"]
[[package]]
name = "importlib-resources"
version = "6.1.1"
version = "6.3.0"
description = "Read resources from Python packages"
optional = false
python-versions = ">=3.8"
files = [
{file = "importlib_resources-6.1.1-py3-none-any.whl", hash = "sha256:e8bf90d8213b486f428c9c39714b920041cb02c184686a3dee24905aaa8105d6"},
{file = "importlib_resources-6.1.1.tar.gz", hash = "sha256:3893a00122eafde6894c59914446a512f728a0c1a45f9bb9b63721b6bacf0b4a"},
{file = "importlib_resources-6.3.0-py3-none-any.whl", hash = "sha256:783407aa1cd05550e3aa123e8f7cfaebee35ffa9cb0242919e2d1e4172222705"},
{file = "importlib_resources-6.3.0.tar.gz", hash = "sha256:166072a97e86917a9025876f34286f549b9caf1d10b35a1b372bffa1600c6569"},
]
[package.dependencies]
@@ -920,7 +920,7 @@ zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""}
[package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff", "zipp (>=3.17)"]
testing = ["jaraco.collections", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)", "zipp (>=3.17)"]
[[package]]
name = "iniconfig"
@@ -935,13 +935,13 @@ files = [
[[package]]
name = "ipykernel"
version = "6.29.2"
version = "6.29.3"
description = "IPython Kernel for Jupyter"
optional = false
python-versions = ">=3.8"
files = [
{file = "ipykernel-6.29.2-py3-none-any.whl", hash = "sha256:50384f5c577a260a1d53f1f59a828c7266d321c9b7d00d345693783f66616055"},
{file = "ipykernel-6.29.2.tar.gz", hash = "sha256:3bade28004e3ff624ed57974948116670604ac5f676d12339693f3142176d3f0"},
{file = "ipykernel-6.29.3-py3-none-any.whl", hash = "sha256:5aa086a4175b0229d4eca211e181fb473ea78ffd9869af36ba7694c947302a21"},
{file = "ipykernel-6.29.3.tar.gz", hash = "sha256:e14c250d1f9ea3989490225cc1a542781b095a18a19447fcf2b5eaf7d0ac5bd2"},
]
[package.dependencies]
@@ -964,7 +964,7 @@ cov = ["coverage[toml]", "curio", "matplotlib", "pytest-cov", "trio"]
docs = ["myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "trio"]
pyqt5 = ["pyqt5"]
pyside6 = ["pyside6"]
test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio (==0.23.4)", "pytest-cov", "pytest-timeout"]
test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio (>=0.23.5)", "pytest-cov", "pytest-timeout"]
[[package]]
name = "ipython"
@@ -1057,13 +1057,13 @@ i18n = ["Babel (>=2.7)"]
[[package]]
name = "json5"
version = "0.9.14"
version = "0.9.22"
description = "A Python implementation of the JSON5 data format."
optional = false
python-versions = "*"
python-versions = ">=3.8"
files = [
{file = "json5-0.9.14-py2.py3-none-any.whl", hash = "sha256:740c7f1b9e584a468dbb2939d8d458db3427f2c93ae2139d05f47e453eae964f"},
{file = "json5-0.9.14.tar.gz", hash = "sha256:9ed66c3a6ca3510a976a9ef9b8c0787de24802724ab1860bc0153c7fdd589b02"},
{file = "json5-0.9.22-py3-none-any.whl", hash = "sha256:6621007c70897652f8b5d03885f732771c48d1925591ad989aa80c7e0e5ad32f"},
{file = "json5-0.9.22.tar.gz", hash = "sha256:b729bde7650b2196a35903a597d2b704b8fdf8648bfb67368cfb79f1174a17bd"},
]
[package.extras]
@@ -1128,13 +1128,13 @@ referencing = ">=0.31.0"
[[package]]
name = "jupyter-client"
version = "8.6.0"
version = "8.6.1"
description = "Jupyter protocol implementation and client libraries"
optional = false
python-versions = ">=3.8"
files = [
{file = "jupyter_client-8.6.0-py3-none-any.whl", hash = "sha256:909c474dbe62582ae62b758bca86d6518c85234bdee2d908c778db6d72f39d99"},
{file = "jupyter_client-8.6.0.tar.gz", hash = "sha256:0642244bb83b4764ae60d07e010e15f0e2d275ec4e918a8f7b80fbbef3ca60c7"},
{file = "jupyter_client-8.6.1-py3-none-any.whl", hash = "sha256:3b7bd22f058434e3b9a7ea4b1500ed47de2713872288c0d511d19926f99b459f"},
{file = "jupyter_client-8.6.1.tar.gz", hash = "sha256:e842515e2bab8e19186d89fdfea7abd15e39dd581f94e399f00e2af5a1652d3f"},
]
[package.dependencies]
@@ -1151,13 +1151,13 @@ test = ["coverage", "ipykernel (>=6.14)", "mypy", "paramiko", "pre-commit", "pyt
[[package]]
name = "jupyter-core"
version = "5.7.1"
version = "5.7.2"
description = "Jupyter core package. A base package on which Jupyter projects rely."
optional = false
python-versions = ">=3.8"
files = [
{file = "jupyter_core-5.7.1-py3-none-any.whl", hash = "sha256:c65c82126453a723a2804aa52409930434598fd9d35091d63dfb919d2b765bb7"},
{file = "jupyter_core-5.7.1.tar.gz", hash = "sha256:de61a9d7fc71240f688b2fb5ab659fbb56979458dc66a71decd098e03c79e218"},
{file = "jupyter_core-5.7.2-py3-none-any.whl", hash = "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409"},
{file = "jupyter_core-5.7.2.tar.gz", hash = "sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9"},
]
[package.dependencies]
@@ -1167,17 +1167,17 @@ traitlets = ">=5.3"
[package.extras]
docs = ["myst-parser", "pydata-sphinx-theme", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "traitlets"]
test = ["ipykernel", "pre-commit", "pytest", "pytest-cov", "pytest-timeout"]
test = ["ipykernel", "pre-commit", "pytest (<8)", "pytest-cov", "pytest-timeout"]
[[package]]
name = "jupyter-events"
version = "0.9.0"
version = "0.9.1"
description = "Jupyter Event System library"
optional = false
python-versions = ">=3.8"
files = [
{file = "jupyter_events-0.9.0-py3-none-any.whl", hash = "sha256:d853b3c10273ff9bc8bb8b30076d65e2c9685579db736873de6c2232dde148bf"},
{file = "jupyter_events-0.9.0.tar.gz", hash = "sha256:81ad2e4bc710881ec274d31c6c50669d71bbaa5dd9d01e600b56faa85700d399"},
{file = "jupyter_events-0.9.1-py3-none-any.whl", hash = "sha256:e51f43d2c25c2ddf02d7f7a5045f71fc1d5cb5ad04ef6db20da961c077654b9b"},
{file = "jupyter_events-0.9.1.tar.gz", hash = "sha256:a52e86f59eb317ee71ff2d7500c94b963b8a24f0b7a1517e2e653e24258e15c7"},
]
[package.dependencies]
@@ -1196,13 +1196,13 @@ test = ["click", "pre-commit", "pytest (>=7.0)", "pytest-asyncio (>=0.19.0)", "p
[[package]]
name = "jupyter-lsp"
version = "2.2.2"
version = "2.2.4"
description = "Multi-Language Server WebSocket proxy for Jupyter Notebook/Lab server"
optional = false
python-versions = ">=3.8"
files = [
{file = "jupyter-lsp-2.2.2.tar.gz", hash = "sha256:256d24620542ae4bba04a50fc1f6ffe208093a07d8e697fea0a8d1b8ca1b7e5b"},
{file = "jupyter_lsp-2.2.2-py3-none-any.whl", hash = "sha256:3b95229e4168355a8c91928057c1621ac3510ba98b2a925e82ebd77f078b1aa5"},
{file = "jupyter-lsp-2.2.4.tar.gz", hash = "sha256:5e50033149344065348e688608f3c6d654ef06d9856b67655bd7b6bac9ee2d59"},
{file = "jupyter_lsp-2.2.4-py3-none-any.whl", hash = "sha256:da61cb63a16b6dff5eac55c2699cc36eac975645adee02c41bdfc03bf4802e77"},
]
[package.dependencies]
@@ -1211,13 +1211,13 @@ jupyter-server = ">=1.1.2"
[[package]]
name = "jupyter-server"
version = "2.12.5"
version = "2.13.0"
description = "The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications."
optional = false
python-versions = ">=3.8"
files = [
{file = "jupyter_server-2.12.5-py3-none-any.whl", hash = "sha256:184a0f82809a8522777cfb6b760ab6f4b1bb398664c5860a27cec696cb884923"},
{file = "jupyter_server-2.12.5.tar.gz", hash = "sha256:0edb626c94baa22809be1323f9770cf1c00a952b17097592e40d03e6a3951689"},
{file = "jupyter_server-2.13.0-py3-none-any.whl", hash = "sha256:77b2b49c3831fbbfbdb5048cef4350d12946191f833a24e5f83e5f8f4803e97b"},
{file = "jupyter_server-2.13.0.tar.gz", hash = "sha256:c80bfb049ea20053c3d9641c2add4848b38073bf79f1729cea1faed32fc1c78e"},
]
[package.dependencies]
@@ -1243,17 +1243,17 @@ websocket-client = "*"
[package.extras]
docs = ["ipykernel", "jinja2", "jupyter-client", "jupyter-server", "myst-parser", "nbformat", "prometheus-client", "pydata-sphinx-theme", "send2trash", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-openapi (>=0.8.0)", "sphinxcontrib-spelling", "sphinxemoji", "tornado", "typing-extensions"]
test = ["flaky", "ipykernel", "pre-commit", "pytest (>=7.0)", "pytest-console-scripts", "pytest-jupyter[server] (>=0.4)", "pytest-timeout", "requests"]
test = ["flaky", "ipykernel", "pre-commit", "pytest (>=7.0)", "pytest-console-scripts", "pytest-jupyter[server] (>=0.7)", "pytest-timeout", "requests"]
[[package]]
name = "jupyter-server-terminals"
version = "0.5.2"
version = "0.5.3"
description = "A Jupyter Server Extension Providing Terminals."
optional = false
python-versions = ">=3.8"
files = [
{file = "jupyter_server_terminals-0.5.2-py3-none-any.whl", hash = "sha256:1b80c12765da979513c42c90215481bbc39bd8ae7c0350b4f85bc3eb58d0fa80"},
{file = "jupyter_server_terminals-0.5.2.tar.gz", hash = "sha256:396b5ccc0881e550bf0ee7012c6ef1b53edbde69e67cab1d56e89711b46052e8"},
{file = "jupyter_server_terminals-0.5.3-py3-none-any.whl", hash = "sha256:41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa"},
{file = "jupyter_server_terminals-0.5.3.tar.gz", hash = "sha256:5ae0295167220e9ace0edcfdb212afd2b01ee8d179fe6f23c899590e9b8a5269"},
]
[package.dependencies]
@@ -1266,13 +1266,13 @@ test = ["jupyter-server (>=2.0.0)", "pytest (>=7.0)", "pytest-jupyter[server] (>
[[package]]
name = "jupyterlab"
version = "4.1.1"
version = "4.1.4"
description = "JupyterLab computational environment"
optional = false
python-versions = ">=3.8"
files = [
{file = "jupyterlab-4.1.1-py3-none-any.whl", hash = "sha256:fa3e8c18b804eac04e51ceebd9dd3dd396e08106816f0d09cc426799d7087632"},
{file = "jupyterlab-4.1.1.tar.gz", hash = "sha256:8acc9f561729d8f32c14c294c397917cddfeeb13a5d46f811979b71b4911a9fd"},
{file = "jupyterlab-4.1.4-py3-none-any.whl", hash = "sha256:f92c3f2b12b88efcf767205f49be9b2f86b85544f9c4f342bb5e9904a16cf931"},
{file = "jupyterlab-4.1.4.tar.gz", hash = "sha256:e03c82c124ad8a0892e498b9dde79c50868b2c267819aca3f55ce47c57ebeb1d"},
]
[package.dependencies]
@@ -1311,13 +1311,13 @@ files = [
[[package]]
name = "jupyterlab-server"
version = "2.25.3"
version = "2.25.4"
description = "A set of server components for JupyterLab and JupyterLab like applications."
optional = false
python-versions = ">=3.8"
files = [
{file = "jupyterlab_server-2.25.3-py3-none-any.whl", hash = "sha256:c48862519fded9b418c71645d85a49b2f0ec50d032ba8316738e9276046088c1"},
{file = "jupyterlab_server-2.25.3.tar.gz", hash = "sha256:846f125a8a19656611df5b03e5912c8393cea6900859baa64fa515eb64a8dc40"},
{file = "jupyterlab_server-2.25.4-py3-none-any.whl", hash = "sha256:eb645ecc8f9b24bac5decc7803b6d5363250e16ec5af814e516bc2c54dd88081"},
{file = "jupyterlab_server-2.25.4.tar.gz", hash = "sha256:2098198e1e82e0db982440f9b5136175d73bea2cd42a6480aa6fd502cb23c4f9"},
]
[package.dependencies]
@@ -1333,7 +1333,7 @@ requests = ">=2.31"
[package.extras]
docs = ["autodoc-traits", "jinja2 (<3.2.0)", "mistune (<4)", "myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-copybutton", "sphinxcontrib-openapi (>0.8)"]
openapi = ["openapi-core (>=0.18.0,<0.19.0)", "ruamel-yaml"]
test = ["hatch", "ipykernel", "openapi-core (>=0.18.0,<0.19.0)", "openapi-spec-validator (>=0.6.0,<0.8.0)", "pytest (>=7.0)", "pytest-console-scripts", "pytest-cov", "pytest-jupyter[server] (>=0.6.2)", "pytest-timeout", "requests-mock", "ruamel-yaml", "sphinxcontrib-spelling", "strict-rfc3339", "werkzeug"]
test = ["hatch", "ipykernel", "openapi-core (>=0.18.0,<0.19.0)", "openapi-spec-validator (>=0.6.0,<0.8.0)", "pytest (>=7.0,<8)", "pytest-console-scripts", "pytest-cov", "pytest-jupyter[server] (>=0.6.2)", "pytest-timeout", "requests-mock", "ruamel-yaml", "sphinxcontrib-spelling", "strict-rfc3339", "werkzeug"]
[[package]]
name = "loguru"
@@ -1509,28 +1509,29 @@ min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-imp
[[package]]
name = "mkdocs-autorefs"
version = "0.5.0"
version = "1.0.1"
description = "Automatically link across pages in MkDocs."
optional = false
python-versions = ">=3.8"
files = [
{file = "mkdocs_autorefs-0.5.0-py3-none-any.whl", hash = "sha256:7930fcb8ac1249f10e683967aeaddc0af49d90702af111a5e390e8b20b3d97ff"},
{file = "mkdocs_autorefs-0.5.0.tar.gz", hash = "sha256:9a5054a94c08d28855cfab967ada10ed5be76e2bfad642302a610b252c3274c0"},
{file = "mkdocs_autorefs-1.0.1-py3-none-any.whl", hash = "sha256:aacdfae1ab197780fb7a2dac92ad8a3d8f7ca8049a9cbe56a4218cd52e8da570"},
{file = "mkdocs_autorefs-1.0.1.tar.gz", hash = "sha256:f684edf847eced40b570b57846b15f0bf57fb93ac2c510450775dcf16accb971"},
]
[package.dependencies]
Markdown = ">=3.3"
markupsafe = ">=2.0.1"
mkdocs = ">=1.1"
[[package]]
name = "mkdocs-material"
version = "9.5.10"
version = "9.5.13"
description = "Documentation that simply works"
optional = false
python-versions = ">=3.8"
files = [
{file = "mkdocs_material-9.5.10-py3-none-any.whl", hash = "sha256:3c6c46b57d2ee3c8890e6e0406e68b6863cf65768f0f436990a742702d198442"},
{file = "mkdocs_material-9.5.10.tar.gz", hash = "sha256:6ad626dbb31070ebbaedff813323a16a406629620e04b96458f16e6e9c7008fe"},
{file = "mkdocs_material-9.5.13-py3-none-any.whl", hash = "sha256:5cbe17fee4e3b4980c8420a04cc762d8dc052ef1e10532abd4fce88e5ea9ce6a"},
{file = "mkdocs_material-9.5.13.tar.gz", hash = "sha256:d8e4caae576312a88fd2609b81cf43d233cdbe36860d67a68702b018b425bd87"},
]
[package.dependencies]
@@ -1564,13 +1565,13 @@ files = [
[[package]]
name = "mkdocstrings"
version = "0.24.0"
version = "0.24.1"
description = "Automatic documentation from sources, for MkDocs."
optional = false
python-versions = ">=3.8"
files = [
{file = "mkdocstrings-0.24.0-py3-none-any.whl", hash = "sha256:f4908560c10f587326d8f5165d1908817b2e280bbf707607f601c996366a2264"},
{file = "mkdocstrings-0.24.0.tar.gz", hash = "sha256:222b1165be41257b494a9d29b14135d2b7ca43f38161d5b10caae03b87bd4f7e"},
{file = "mkdocstrings-0.24.1-py3-none-any.whl", hash = "sha256:b4206f9a2ca8a648e222d5a0ca1d36ba7dee53c88732818de183b536f9042b5d"},
{file = "mkdocstrings-0.24.1.tar.gz", hash = "sha256:cc83f9a1c8724fc1be3c2fa071dd73d91ce902ef6a79710249ec8d0ee1064401"},
]
[package.dependencies]
@@ -1626,13 +1627,13 @@ tests = ["pytest (>=4.6)"]
[[package]]
name = "nbclient"
version = "0.9.0"
version = "0.10.0"
description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor."
optional = false
python-versions = ">=3.8.0"
files = [
{file = "nbclient-0.9.0-py3-none-any.whl", hash = "sha256:a3a1ddfb34d4a9d17fc744d655962714a866639acd30130e9be84191cd97cd15"},
{file = "nbclient-0.9.0.tar.gz", hash = "sha256:4b28c207877cf33ef3a9838cdc7a54c5ceff981194a82eac59d558f05487295e"},
{file = "nbclient-0.10.0-py3-none-any.whl", hash = "sha256:f13e3529332a1f1f81d82a53210322476a168bb7090a0289c795fe9cc11c9d3f"},
{file = "nbclient-0.10.0.tar.gz", hash = "sha256:4b3f1b7dba531e498449c4db4f53da339c91d449dc11e9af3a43b4eb5c5abb09"},
]
[package.dependencies]
@@ -1644,17 +1645,17 @@ traitlets = ">=5.4"
[package.extras]
dev = ["pre-commit"]
docs = ["autodoc-traits", "mock", "moto", "myst-parser", "nbclient[test]", "sphinx (>=1.7)", "sphinx-book-theme", "sphinxcontrib-spelling"]
test = ["flaky", "ipykernel (>=6.19.3)", "ipython", "ipywidgets", "nbconvert (>=7.0.0)", "pytest (>=7.0)", "pytest-asyncio", "pytest-cov (>=4.0)", "testpath", "xmltodict"]
test = ["flaky", "ipykernel (>=6.19.3)", "ipython", "ipywidgets", "nbconvert (>=7.0.0)", "pytest (>=7.0,<8)", "pytest-asyncio", "pytest-cov (>=4.0)", "testpath", "xmltodict"]
[[package]]
name = "nbconvert"
version = "7.16.0"
description = "Converting Jupyter Notebooks"
version = "7.16.2"
description = "Converting Jupyter Notebooks (.ipynb files) to other formats. Output formats include asciidoc, html, latex, markdown, pdf, py, rst, script. nbconvert can be used both as a Python library (`import nbconvert`) or as a command line tool (invoked as `jupyter nbconvert ...`)."
optional = false
python-versions = ">=3.8"
files = [
{file = "nbconvert-7.16.0-py3-none-any.whl", hash = "sha256:ad3dc865ea6e2768d31b7eb6c7ab3be014927216a5ece3ef276748dd809054c7"},
{file = "nbconvert-7.16.0.tar.gz", hash = "sha256:813e6553796362489ae572e39ba1bff978536192fb518e10826b0e8cadf03ec8"},
{file = "nbconvert-7.16.2-py3-none-any.whl", hash = "sha256:0c01c23981a8de0220255706822c40b751438e32467d6a686e26be08ba784382"},
{file = "nbconvert-7.16.2.tar.gz", hash = "sha256:8310edd41e1c43947e4ecf16614c61469ebc024898eb808cce0999860fc9fb16"},
]
[package.dependencies]
@@ -1686,13 +1687,13 @@ webpdf = ["playwright"]
[[package]]
name = "nbformat"
version = "5.9.2"
version = "5.10.2"
description = "The Jupyter Notebook format"
optional = false
python-versions = ">=3.8"
files = [
{file = "nbformat-5.9.2-py3-none-any.whl", hash = "sha256:1c5172d786a41b82bcfd0c23f9e6b6f072e8fb49c39250219e4acfff1efe89e9"},
{file = "nbformat-5.9.2.tar.gz", hash = "sha256:5f98b5ba1997dff175e77e0c17d5c10a96eaed2cbd1de3533d1fc35d5e111192"},
{file = "nbformat-5.10.2-py3-none-any.whl", hash = "sha256:7381189a0d537586b3f18bae5dbad347d7dd0a7cf0276b09cdcd5c24d38edd99"},
{file = "nbformat-5.10.2.tar.gz", hash = "sha256:c535b20a0d4310167bf4d12ad31eccfb0dc61e6392d6f8c570ab5b45a06a49a3"},
]
[package.dependencies]
@@ -1732,13 +1733,13 @@ setuptools = "*"
[[package]]
name = "notebook"
version = "7.1.0"
version = "7.1.1"
description = "Jupyter Notebook - A web-based notebook environment for interactive computing"
optional = false
python-versions = ">=3.8"
files = [
{file = "notebook-7.1.0-py3-none-any.whl", hash = "sha256:a8fa4ccb5e5fe220f29d9900337efd7752bc6f2efe004d6f320db01f7743adc9"},
{file = "notebook-7.1.0.tar.gz", hash = "sha256:99caf01ff166b1cc86355c9b37c1ba9bf566c1d7fc4ab57bb6f8f24e36c4260e"},
{file = "notebook-7.1.1-py3-none-any.whl", hash = "sha256:197d8e0595acabf4005851c8716e952a81b405f7aefb648067a761fbde267ce7"},
{file = "notebook-7.1.1.tar.gz", hash = "sha256:818e7420fa21f402e726afb9f02df7f3c10f294c02e383ed19852866c316108b"},
]
[package.dependencies]
@@ -1895,36 +1896,36 @@ reference = ["Pillow", "google-re2"]
[[package]]
name = "onnxruntime"
version = "1.17.0"
version = "1.17.1"
description = "ONNX Runtime is a runtime accelerator for Machine Learning models"
optional = false
python-versions = "*"
files = [
{file = "onnxruntime-1.17.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:d2b22a25a94109cc983443116da8d9805ced0256eb215c5e6bc6dcbabefeab96"},
{file = "onnxruntime-1.17.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4c87d83c6f58d1af2675fc99e3dc810f2dbdb844bcefd0c1b7573632661f6fc"},
{file = "onnxruntime-1.17.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dba55723bf9b835e358f48c98a814b41692c393eb11f51e02ece0625c756b797"},
{file = "onnxruntime-1.17.0-cp310-cp310-win32.whl", hash = "sha256:ee48422349cc500273beea7607e33c2237909f58468ae1d6cccfc4aecd158565"},
{file = "onnxruntime-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:f34cc46553359293854e38bdae2ab1be59543aad78a6317e7746d30e311110c3"},
{file = "onnxruntime-1.17.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:16d26badd092c8c257fa57c458bb600d96dc15282c647ccad0ed7b2732e6c03b"},
{file = "onnxruntime-1.17.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6f1273bebcdb47ed932d076c85eb9488bc4768fcea16d5f2747ca692fad4f9d3"},
{file = "onnxruntime-1.17.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cb60fd3c2c1acd684752eb9680e89ae223e9801a9b0e0dc7b28adabe45a2e380"},
{file = "onnxruntime-1.17.0-cp311-cp311-win32.whl", hash = "sha256:4b038324586bc905299e435f7c00007e6242389c856b82fe9357fdc3b1ef2bdc"},
{file = "onnxruntime-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:93d39b3fa1ee01f034f098e1c7769a811a21365b4883f05f96c14a2b60c6028b"},
{file = "onnxruntime-1.17.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:90c0890e36f880281c6c698d9bc3de2afbeee2f76512725ec043665c25c67d21"},
{file = "onnxruntime-1.17.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7466724e809a40e986b1637cba156ad9fc0d1952468bc00f79ef340bc0199552"},
{file = "onnxruntime-1.17.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d47bee7557a8b99c8681b6882657a515a4199778d6d5e24e924d2aafcef55b0a"},
{file = "onnxruntime-1.17.0-cp312-cp312-win32.whl", hash = "sha256:bb1bf1ee575c665b8bbc3813ab906e091a645a24ccc210be7932154b8260eca1"},
{file = "onnxruntime-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:ac2f286da3494b29b4186ca193c7d4e6a2c1f770c4184c7192c5da142c3dec28"},
{file = "onnxruntime-1.17.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1ec485643b93e0a3896c655eb2426decd63e18a278bb7ccebc133b340723624f"},
{file = "onnxruntime-1.17.0-cp38-cp38-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83c35809cda898c5a11911c69ceac8a2ac3925911854c526f73bad884582f911"},
{file = "onnxruntime-1.17.0-cp38-cp38-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fa464aa4d81df818375239e481887b656e261377d5b6b9a4692466f5f3261edc"},
{file = "onnxruntime-1.17.0-cp38-cp38-win32.whl", hash = "sha256:b7b337cd0586f7836601623cbd30a443df9528ef23965860d11c753ceeb009f2"},
{file = "onnxruntime-1.17.0-cp38-cp38-win_amd64.whl", hash = "sha256:fbb9faaf51d01aa2c147ef52524d9326744c852116d8005b9041809a71838878"},
{file = "onnxruntime-1.17.0-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:5a06ab84eaa350bf64b1d747b33ccf10da64221ed1f38f7287f15eccbec81603"},
{file = "onnxruntime-1.17.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d3d11db2c8242766212a68d0b139745157da7ce53bd96ba349a5c65e5a02357"},
{file = "onnxruntime-1.17.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5632077c3ab8b0cd4f74b0af9c4e924be012b1a7bcd7daa845763c6c6bf14b7d"},
{file = "onnxruntime-1.17.0-cp39-cp39-win32.whl", hash = "sha256:61a12732cba869b3ad2d4e29ab6cb62c7a96f61b8c213f7fcb961ba412b70b37"},
{file = "onnxruntime-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:461fa0fc7d9c392c352b6cccdedf44d818430f3d6eacd924bb804fdea2dcfd02"},
{file = "onnxruntime-1.17.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:d43ac17ac4fa3c9096ad3c0e5255bb41fd134560212dc124e7f52c3159af5d21"},
{file = "onnxruntime-1.17.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55b5e92a4c76a23981c998078b9bf6145e4fb0b016321a8274b1607bd3c6bd35"},
{file = "onnxruntime-1.17.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ebbcd2bc3a066cf54e6f18c75708eb4d309ef42be54606d22e5bdd78afc5b0d7"},
{file = "onnxruntime-1.17.1-cp310-cp310-win32.whl", hash = "sha256:5e3716b5eec9092e29a8d17aab55e737480487deabfca7eac3cd3ed952b6ada9"},
{file = "onnxruntime-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:fbb98cced6782ae1bb799cc74ddcbbeeae8819f3ad1d942a74d88e72b6511337"},
{file = "onnxruntime-1.17.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:36fd6f87a1ecad87e9c652e42407a50fb305374f9a31d71293eb231caae18784"},
{file = "onnxruntime-1.17.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99a8bddeb538edabc524d468edb60ad4722cff8a49d66f4e280c39eace70500b"},
{file = "onnxruntime-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd7fddb4311deb5a7d3390cd8e9b3912d4d963efbe4dfe075edbaf18d01c024e"},
{file = "onnxruntime-1.17.1-cp311-cp311-win32.whl", hash = "sha256:606a7cbfb6680202b0e4f1890881041ffc3ac6e41760a25763bd9fe146f0b335"},
{file = "onnxruntime-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:53e4e06c0a541696ebdf96085fd9390304b7b04b748a19e02cf3b35c869a1e76"},
{file = "onnxruntime-1.17.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:40f08e378e0f85929712a2b2c9b9a9cc400a90c8a8ca741d1d92c00abec60843"},
{file = "onnxruntime-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac79da6d3e1bb4590f1dad4bb3c2979d7228555f92bb39820889af8b8e6bd472"},
{file = "onnxruntime-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ae9ba47dc099004e3781f2d0814ad710a13c868c739ab086fc697524061695ea"},
{file = "onnxruntime-1.17.1-cp312-cp312-win32.whl", hash = "sha256:2dff1a24354220ac30e4a4ce2fb1df38cb1ea59f7dac2c116238d63fe7f4c5ff"},
{file = "onnxruntime-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:6226a5201ab8cafb15e12e72ff2a4fc8f50654e8fa5737c6f0bd57c5ff66827e"},
{file = "onnxruntime-1.17.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:cd0c07c0d1dfb8629e820b05fda5739e4835b3b82faf43753d2998edf2cf00aa"},
{file = "onnxruntime-1.17.1-cp38-cp38-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:617ebdf49184efa1ba6e4467e602fbfa029ed52c92f13ce3c9f417d303006381"},
{file = "onnxruntime-1.17.1-cp38-cp38-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9dae9071e3facdf2920769dceee03b71c684b6439021defa45b830d05e148924"},
{file = "onnxruntime-1.17.1-cp38-cp38-win32.whl", hash = "sha256:835d38fa1064841679433b1aa8138b5e1218ddf0cfa7a3ae0d056d8fd9cec713"},
{file = "onnxruntime-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:96621e0c555c2453bf607606d08af3f70fbf6f315230c28ddea91754e17ad4e6"},
{file = "onnxruntime-1.17.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:7a9539935fb2d78ebf2cf2693cad02d9930b0fb23cdd5cf37a7df813e977674d"},
{file = "onnxruntime-1.17.1-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45c6a384e9d9a29c78afff62032a46a993c477b280247a7e335df09372aedbe9"},
{file = "onnxruntime-1.17.1-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4e19f966450f16863a1d6182a685ca33ae04d7772a76132303852d05b95411ea"},
{file = "onnxruntime-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e2ae712d64a42aac29ed7a40a426cb1e624a08cfe9273dcfe681614aa65b07dc"},
{file = "onnxruntime-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:f7e9f7fb049825cdddf4a923cfc7c649d84d63c0134315f8e0aa9e0c3004672c"},
]
[package.dependencies]
@@ -1948,13 +1949,13 @@ files = [
[[package]]
name = "packaging"
version = "23.2"
version = "24.0"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.7"
files = [
{file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"},
{file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
{file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"},
{file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"},
]
[[package]]
@@ -2302,13 +2303,13 @@ windows-terminal = ["colorama (>=0.4.6)"]
[[package]]
name = "pymdown-extensions"
version = "10.7"
version = "10.7.1"
description = "Extension pack for Python Markdown."
optional = false
python-versions = ">=3.8"
files = [
{file = "pymdown_extensions-10.7-py3-none-any.whl", hash = "sha256:6ca215bc57bc12bf32b414887a68b810637d039124ed9b2e5bd3325cbb2c050c"},
{file = "pymdown_extensions-10.7.tar.gz", hash = "sha256:c0d64d5cf62566f59e6b2b690a4095c931107c250a8c8e1351c1de5f6b036deb"},
{file = "pymdown_extensions-10.7.1-py3-none-any.whl", hash = "sha256:f5cc7000d7ff0d1ce9395d216017fa4df3dde800afb1fb72d1c7d3fd35e710f4"},
{file = "pymdown_extensions-10.7.1.tar.gz", hash = "sha256:c70e146bdd83c744ffc766b4671999796aba18842b268510a329f7f64700d584"},
]
[package.dependencies]
@@ -2353,13 +2354,13 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no
[[package]]
name = "python-dateutil"
version = "2.8.2"
version = "2.9.0.post0"
description = "Extensions to the standard Python datetime module"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
files = [
{file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
{file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
{file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
{file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
]
[package.dependencies]
@@ -2412,17 +2413,17 @@ files = [
[[package]]
name = "pywinpty"
version = "2.0.12"
version = "2.0.13"
description = "Pseudo terminal support for Windows from Python."
optional = false
python-versions = ">=3.8"
files = [
{file = "pywinpty-2.0.12-cp310-none-win_amd64.whl", hash = "sha256:21319cd1d7c8844fb2c970fb3a55a3db5543f112ff9cfcd623746b9c47501575"},
{file = "pywinpty-2.0.12-cp311-none-win_amd64.whl", hash = "sha256:853985a8f48f4731a716653170cd735da36ffbdc79dcb4c7b7140bce11d8c722"},
{file = "pywinpty-2.0.12-cp312-none-win_amd64.whl", hash = "sha256:1617b729999eb6713590e17665052b1a6ae0ad76ee31e60b444147c5b6a35dca"},
{file = "pywinpty-2.0.12-cp38-none-win_amd64.whl", hash = "sha256:189380469ca143d06e19e19ff3fba0fcefe8b4a8cc942140a6b863aed7eebb2d"},
{file = "pywinpty-2.0.12-cp39-none-win_amd64.whl", hash = "sha256:7520575b6546db23e693cbd865db2764097bd6d4ef5dc18c92555904cd62c3d4"},
{file = "pywinpty-2.0.12.tar.gz", hash = "sha256:8197de460ae8ebb7f5d1701dfa1b5df45b157bb832e92acba316305e18ca00dd"},
{file = "pywinpty-2.0.13-cp310-none-win_amd64.whl", hash = "sha256:697bff211fb5a6508fee2dc6ff174ce03f34a9a233df9d8b5fe9c8ce4d5eaf56"},
{file = "pywinpty-2.0.13-cp311-none-win_amd64.whl", hash = "sha256:b96fb14698db1284db84ca38c79f15b4cfdc3172065b5137383910567591fa99"},
{file = "pywinpty-2.0.13-cp312-none-win_amd64.whl", hash = "sha256:2fd876b82ca750bb1333236ce98488c1be96b08f4f7647cfdf4129dfad83c2d4"},
{file = "pywinpty-2.0.13-cp38-none-win_amd64.whl", hash = "sha256:61d420c2116c0212808d31625611b51caf621fe67f8a6377e2e8b617ea1c1f7d"},
{file = "pywinpty-2.0.13-cp39-none-win_amd64.whl", hash = "sha256:71cb613a9ee24174730ac7ae439fd179ca34ccb8c5349e8d7b72ab5dea2c6f4b"},
{file = "pywinpty-2.0.13.tar.gz", hash = "sha256:c34e32351a3313ddd0d7da23d27f835c860d32fe4ac814d372a3ea9594f41dde"},
]
[[package]]
@@ -2437,6 +2438,7 @@ files = [
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"},
{file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"},
{file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"},
{file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"},
{file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"},
@@ -2444,8 +2446,16 @@ files = [
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"},
{file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"},
{file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"},
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"},
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
{file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"},
{file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"},
@@ -2462,6 +2472,7 @@ files = [
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"},
{file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"},
{file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"},
{file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"},
{file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"},
@@ -2469,6 +2480,7 @@ files = [
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"},
{file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"},
{file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"},
{file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"},
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
@@ -2908,19 +2920,19 @@ win32 = ["pywin32"]
[[package]]
name = "setuptools"
version = "69.1.0"
version = "69.2.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false
python-versions = ">=3.8"
files = [
{file = "setuptools-69.1.0-py3-none-any.whl", hash = "sha256:c054629b81b946d63a9c6e732bc8b2513a7c3ea645f11d0139a2191d735c60c6"},
{file = "setuptools-69.1.0.tar.gz", hash = "sha256:850894c4195f09c4ed30dba56213bf7c3f21d86ed6bdaafb5df5972593bfc401"},
{file = "setuptools-69.2.0-py3-none-any.whl", hash = "sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c"},
{file = "setuptools-69.2.0.tar.gz", hash = "sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e"},
]
[package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
[[package]]
name = "six"
@@ -2946,13 +2958,13 @@ files = [
[[package]]
name = "sniffio"
version = "1.3.0"
version = "1.3.1"
description = "Sniff out which async library your code is running under"
optional = false
python-versions = ">=3.7"
files = [
{file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
{file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
{file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
]
[[package]]
@@ -3001,13 +3013,13 @@ mpmath = ">=0.19"
[[package]]
name = "terminado"
version = "0.18.0"
version = "0.18.1"
description = "Tornado websocket backend for the Xterm.js Javascript terminal emulator library."
optional = false
python-versions = ">=3.8"
files = [
{file = "terminado-0.18.0-py3-none-any.whl", hash = "sha256:87b0d96642d0fe5f5abd7783857b9cab167f221a39ff98e3b9619a788a3c0f2e"},
{file = "terminado-0.18.0.tar.gz", hash = "sha256:1ea08a89b835dd1b8c0c900d92848147cef2537243361b2e3f4dc15df9b6fded"},
{file = "terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0"},
{file = "terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e"},
]
[package.dependencies]
@@ -3218,39 +3230,39 @@ telegram = ["requests"]
[[package]]
name = "traitlets"
version = "5.14.1"
version = "5.14.2"
description = "Traitlets Python configuration system"
optional = false
python-versions = ">=3.8"
files = [
{file = "traitlets-5.14.1-py3-none-any.whl", hash = "sha256:2e5a030e6eff91737c643231bfcf04a65b0132078dad75e4936700b213652e74"},
{file = "traitlets-5.14.1.tar.gz", hash = "sha256:8585105b371a04b8316a43d5ce29c098575c2e477850b62b848b964f1444527e"},
{file = "traitlets-5.14.2-py3-none-any.whl", hash = "sha256:fcdf85684a772ddeba87db2f398ce00b40ff550d1528c03c14dbf6a02003cd80"},
{file = "traitlets-5.14.2.tar.gz", hash = "sha256:8cdd83c040dab7d1dee822678e5f5d100b514f7b72b01615b26fc5718916fdf9"},
]
[package.extras]
docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"]
test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<7.5)", "pytest-mock", "pytest-mypy-testing"]
test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.1)", "pytest-mock", "pytest-mypy-testing"]
[[package]]
name = "types-python-dateutil"
version = "2.8.19.20240106"
version = "2.8.19.20240311"
description = "Typing stubs for python-dateutil"
optional = false
python-versions = ">=3.8"
files = [
{file = "types-python-dateutil-2.8.19.20240106.tar.gz", hash = "sha256:1f8db221c3b98e6ca02ea83a58371b22c374f42ae5bbdf186db9c9a76581459f"},
{file = "types_python_dateutil-2.8.19.20240106-py3-none-any.whl", hash = "sha256:efbbdc54590d0f16152fa103c9879c7d4a00e82078f6e2cf01769042165acaa2"},
{file = "types-python-dateutil-2.8.19.20240311.tar.gz", hash = "sha256:51178227bbd4cbec35dc9adffbf59d832f20e09842d7dcb8c73b169b8780b7cb"},
{file = "types_python_dateutil-2.8.19.20240311-py3-none-any.whl", hash = "sha256:ef813da0809aca76472ca88807addbeea98b19339aebe56159ae2f4b4f70857a"},
]
[[package]]
name = "typing-extensions"
version = "4.9.0"
version = "4.10.0"
description = "Backported and Experimental Type Hints for Python 3.8+"
optional = false
python-versions = ">=3.8"
files = [
{file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"},
{file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"},
{file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"},
{file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"},
]
[[package]]
@@ -3286,13 +3298,13 @@ zstd = ["zstandard (>=0.18.0)"]
[[package]]
name = "virtualenv"
version = "20.25.0"
version = "20.25.1"
description = "Virtual Python Environment builder"
optional = false
python-versions = ">=3.7"
files = [
{file = "virtualenv-20.25.0-py3-none-any.whl", hash = "sha256:4238949c5ffe6876362d9c0180fc6c3a824a7b12b80604eeb8085f2ed7460de3"},
{file = "virtualenv-20.25.0.tar.gz", hash = "sha256:bf51c0d9c7dd63ea8e44086fa1e4fb1093a31e963b86959257378aef020e1f1b"},
{file = "virtualenv-20.25.1-py3-none-any.whl", hash = "sha256:961c026ac520bac5f69acb8ea063e8a4f071bcc9457b9c1f28f6b085c511583a"},
{file = "virtualenv-20.25.1.tar.gz", hash = "sha256:e08e13ecdca7a0bd53798f356d5831434afa5b07b93f0abdf0797b7a06ffe197"},
]
[package.dependencies]
@@ -3414,20 +3426,20 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"]
[[package]]
name = "zipp"
version = "3.17.0"
version = "3.18.0"
description = "Backport of pathlib-compatible object wrapper for zip files"
optional = false
python-versions = ">=3.8"
files = [
{file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"},
{file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"},
{file = "zipp-3.18.0-py3-none-any.whl", hash = "sha256:c1bb803ed69d2cce2373152797064f7e79bc43f0a3748eb494096a867e0ebf79"},
{file = "zipp-3.18.0.tar.gz", hash = "sha256:df8d042b02765029a09b157efd8e820451045890acc30f8e37dd2f94a060221f"},
]
[package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"]
[metadata]
lock-version = "2.0"
python-versions = ">=3.8.0,<3.13"
content-hash = "8bc18a20773e3e2530564b9e0ba8a31c6f3dd7ab710c87acfe446db5e785ca3c"
content-hash = "791d690524cb9f690de5e42822ef6f7a5a3d1179e464eb2f36824a433406cf15"
+4 -3
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "fastembed"
version = "0.2.2"
version = "0.2.5"
description = "Fast, light, accurate library built for retrieval embedding generation"
authors = ["NirantK <nirant.bits@gmail.com>"]
license = "Apache License"
@@ -28,13 +28,14 @@ numpy = [
pytest = "^7.4.2"
ruff = "^0.2.2"
notebook = ">=7.0.2"
pre-commit = {version = "^3.6.2", python = ">=3.9,<3.12" }
[tool.poetry.group.docs.dependencies]
mkdocs-material = "^9.5.10"
mkdocstrings = "^0.24.0"
pillow = "^10.2.0"
cairosvg = "^2.7.1"
mknotebooks = "^0.8.0"
pre-commit = {version = "^3.6.2", python = ">=3.9,<3.12" }
click = "^8.1.7"
[build-system]
+73
View File
@@ -0,0 +1,73 @@
import pytest
from fastembed.sparse.sparse_text_embedding import SparseTextEmbedding
CANONICAL_COLUMN_VALUES = {
"prithvida/Splade_PP_en_v1": {
"indices": [
2040,
2047,
2088,
2299,
2748,
3011,
3376,
3795,
4774,
5304,
5798,
6160,
7592,
7632,
8484,
],
"values": [
0.4219532012939453,
0.4320072531700134,
2.766580104827881,
0.3314574658870697,
1.395172119140625,
0.021595917642116547,
0.43770670890808105,
0.0008370947907678783,
0.5187209844589233,
0.17124654352664948,
0.14742016792297363,
0.8142819404602051,
2.803262710571289,
2.1904349327087402,
1.0531445741653442,
],
}
}
docs = ["Hello World"]
def test_batch_embedding():
docs_to_embed = docs * 10
for model_name, expected_result in CANONICAL_COLUMN_VALUES.items():
print("evaluating", model_name)
model = SparseTextEmbedding(model_name=model_name)
result = next(iter(model.embed(docs_to_embed, batch_size=6)))
print(result.indices)
assert result.indices.tolist() == expected_result["indices"]
for i, value in enumerate(result.values):
assert pytest.approx(value, abs=0.001) == expected_result["values"][i]
def test_single_embedding():
docs_to_embed = docs
for model_name, expected_result in CANONICAL_COLUMN_VALUES.items():
print("evaluating", model_name)
model = SparseTextEmbedding(model_name=model_name)
result = next(iter(model.embed(docs_to_embed, batch_size=6)))
print(result.indices)
assert result.indices.tolist() == expected_result["indices"]
for i, value in enumerate(result.values):
assert pytest.approx(value, abs=0.001) == expected_result["values"][i]